diff --git a/api/client.go b/api/client.go index 6560fe67353d905c77fe072ff371b9e3fa4b4f37..9d2bfe9795f391cc8bb4e409ec6a1c21f8423aa2 100644 --- a/api/client.go +++ b/api/client.go @@ -169,15 +169,15 @@ func NewVanityClient(ndfJSON, storageDir string, password []byte, // NewClientFromBackup constructs a new Client from an encrypted backup. The backup // is decrypted using the backupPassphrase. On success a successful client creation, -//// the function will return a JSON encoded list of the E2E partners -//// contained in the backup. +// the function will return a JSON encoded list of the E2E partners +// contained in the backup and a json-encoded string containing parameters stored in the backup func NewClientFromBackup(ndfJSON, storageDir string, sessionPassword, - backupPassphrase []byte, backupFileContents []byte) ([]*id.ID, error) { + backupPassphrase []byte, backupFileContents []byte) ([]*id.ID, string, error) { backUp := &backup.Backup{} err := backUp.Decrypt(string(backupPassphrase), backupFileContents) if err != nil { - return nil, errors.WithMessage(err, "Failed to unmarshal decrypted client contents.") + return nil, "", errors.WithMessage(err, "Failed to unmarshal decrypted client contents.") } usr := user.NewUserFromBackup(backUp) @@ -185,7 +185,7 @@ func NewClientFromBackup(ndfJSON, storageDir string, sessionPassword, // Parse the NDF def, err := parseNDF(ndfJSON) if err != nil { - return nil, err + return nil, "", err } cmixGrp, e2eGrp := decodeGroups(def) @@ -206,10 +206,10 @@ func NewClientFromBackup(ndfJSON, storageDir string, sessionPassword, //move the registration state to indicate registered with registration on proto client err = storageSess.ForwardRegistrationStatus(storage.PermissioningComplete) if err != nil { - return nil, err + return nil, "", err } - return backUp.Contacts.Identities, nil + return backUp.Contacts.Identities, backUp.JSONParams, nil } // OpenClient session, but don't connect to the network or log in diff --git a/backup/backup.go b/backup/backup.go index 866aa394ea525f7fd2af89a486365d7fa8579c10..b8b72f2563844980c21d8d0ebb9309b85c996221 100644 --- a/backup/backup.go +++ b/backup/backup.go @@ -53,6 +53,8 @@ type Backup struct { store *storage.Session backupContainer *interfaces.BackupContainer rng *fastRNG.StreamGenerator + + jsonParams string } // UpdateBackupFn is the callback that encrypted backup data is returned on @@ -141,6 +143,7 @@ func resumeBackup(updateBackupCb UpdateBackupFn, c *api.Client, store: store, backupContainer: backupContainer, rng: rng, + jsonParams: loadJson(store.GetKV()), } // Setting backup trigger in client @@ -208,6 +211,19 @@ func (b *Backup) TriggerBackup(reason string) { } } +func (b *Backup) AddJson(newJson string) { + b.mux.Lock() + defer b.mux.Unlock() + + if newJson != b.jsonParams { + b.jsonParams = newJson + if err := storeJson(newJson, b.store.GetKV()); err != nil { + jww.FATAL.Panicf("Failed to store json: %+v", err) + } + go b.TriggerBackup("New Json") + } +} + // StopBackup stops the backup processes and deletes the user's password, key, // salt, and parameters from storage. func (b *Backup) StopBackup() error { @@ -287,5 +303,8 @@ func (b *Backup) assembleBackup() backup.Backup { // Get contacts bu.Contacts.Identities = b.store.E2e().GetPartners() + //add the memoized json params + bu.JSONParams = b.jsonParams + return bu } diff --git a/backup/backup_test.go b/backup/backup_test.go index 2cf59bd022cb81bfbe3b3d2c58e1fdc84278fbc1..4b6d964b1ea1a681b8ed0d75149a11817355a638 100644 --- a/backup/backup_test.go +++ b/backup/backup_test.go @@ -135,7 +135,7 @@ func Test_resumeBackup(t *testing.T) { select { case r := <-cbChan1: - t.Errorf("Callback of first Backup called: %q", r) + t.Errorf("Callback of first Backup called: %q", r) // TODO: i think there is a race condition here case r := <-cbChan2: if !bytes.Equal(encryptedBackup, r) { t.Errorf("Callback has unexepected data."+ @@ -282,6 +282,82 @@ func TestBackup_IsBackupRunning(t *testing.T) { } } +func TestBackup_AddJson(t *testing.T) { + b := newTestBackup("MySuperSecurePassword", nil, t) + s := b.store + json := "{'data': {'one': 1}}" + + expectedCollatedBackup := backup.Backup{ + RegistrationTimestamp: s.GetUser().RegistrationTimestamp, + TransmissionIdentity: backup.TransmissionIdentity{ + RSASigningPrivateKey: s.GetUser().TransmissionRSA, + RegistrarSignature: s.User().GetTransmissionRegistrationValidationSignature(), + Salt: s.GetUser().TransmissionSalt, + ComputedID: s.GetUser().TransmissionID, + }, + ReceptionIdentity: backup.ReceptionIdentity{ + RSASigningPrivateKey: s.GetUser().ReceptionRSA, + RegistrarSignature: s.User().GetReceptionRegistrationValidationSignature(), + Salt: s.GetUser().ReceptionSalt, + ComputedID: s.GetUser().ReceptionID, + DHPrivateKey: s.GetUser().E2eDhPrivateKey, + DHPublicKey: s.GetUser().E2eDhPublicKey, + }, + UserDiscoveryRegistration: backup.UserDiscoveryRegistration{ + FactList: s.GetUd().GetFacts(), + }, + Contacts: backup.Contacts{Identities: s.E2e().GetPartners()}, + JSONParams: json, + } + + b.AddJson(json) + + collatedBackup := b.assembleBackup() + if !reflect.DeepEqual(expectedCollatedBackup, collatedBackup) { + t.Errorf("Collated backup does not match expected."+ + "\nexpected: %+v\nreceived: %+v", + expectedCollatedBackup, collatedBackup) + } +} + +func TestBackup_AddJson_badJson(t *testing.T) { + b := newTestBackup("MySuperSecurePassword", nil, t) + s := b.store + json := "abc{'i'm a bad json: 'one': 1'''}}" + + expectedCollatedBackup := backup.Backup{ + RegistrationTimestamp: s.GetUser().RegistrationTimestamp, + TransmissionIdentity: backup.TransmissionIdentity{ + RSASigningPrivateKey: s.GetUser().TransmissionRSA, + RegistrarSignature: s.User().GetTransmissionRegistrationValidationSignature(), + Salt: s.GetUser().TransmissionSalt, + ComputedID: s.GetUser().TransmissionID, + }, + ReceptionIdentity: backup.ReceptionIdentity{ + RSASigningPrivateKey: s.GetUser().ReceptionRSA, + RegistrarSignature: s.User().GetReceptionRegistrationValidationSignature(), + Salt: s.GetUser().ReceptionSalt, + ComputedID: s.GetUser().ReceptionID, + DHPrivateKey: s.GetUser().E2eDhPrivateKey, + DHPublicKey: s.GetUser().E2eDhPublicKey, + }, + UserDiscoveryRegistration: backup.UserDiscoveryRegistration{ + FactList: s.GetUd().GetFacts(), + }, + Contacts: backup.Contacts{Identities: s.E2e().GetPartners()}, + JSONParams: json, + } + + b.AddJson(json) + + collatedBackup := b.assembleBackup() + if !reflect.DeepEqual(expectedCollatedBackup, collatedBackup) { + t.Errorf("Collated backup does not match expected."+ + "\nexpected: %+v\nreceived: %+v", + expectedCollatedBackup, collatedBackup) + } +} + // Tests that Backup.assembleBackup returns the backup.Backup with the expected // results. func TestBackup_assembleBackup(t *testing.T) { diff --git a/backup/jsonStorage.go b/backup/jsonStorage.go new file mode 100644 index 0000000000000000000000000000000000000000..8ce778b56aff9c76847caf1953451d7cb6b39d5d --- /dev/null +++ b/backup/jsonStorage.go @@ -0,0 +1,30 @@ +package backup + +import ( + "gitlab.com/elixxir/client/storage/versioned" + "gitlab.com/xx_network/primitives/netTime" +) + +const ( + jsonStorageVersion = 0 + jsonStorageKey = "JsonStorage" +) + +func storeJson(json string, kv *versioned.KV) error { + obj := &versioned.Object{ + Version: jsonStorageVersion, + Timestamp: netTime.Now(), + Data: []byte(json), + } + + return kv.Set(jsonStorageKey, jsonStorageVersion, obj) +} + +func loadJson(kv *versioned.KV) string { + obj, err := kv.Get(jsonStorageKey, jsonStorageVersion) + if err != nil { + return "" + } + + return string(obj.Data) +} diff --git a/backup/jsonStorage_test.go b/backup/jsonStorage_test.go new file mode 100644 index 0000000000000000000000000000000000000000..d0207d910d76ffd73b5dfa1ff623996bf14c916b --- /dev/null +++ b/backup/jsonStorage_test.go @@ -0,0 +1,22 @@ +package backup + +import ( + "gitlab.com/elixxir/client/storage/versioned" + "gitlab.com/elixxir/ekv" + "testing" +) + +func Test_storeJson_loadJson(t *testing.T) { + kv := versioned.NewKV(make(ekv.Memstore)) + json := "{'data': {'one': 1}}" + + err := storeJson(json, kv) + if err != nil { + t.Errorf("Failed to store JSON: %+v", err) + } + + loaded := loadJson(kv) + if loaded != json { + t.Errorf("Did not receive expected data from KV.\n\tExpected: %s, Received: %s\n", json, loaded) + } +} diff --git a/bindings/backup.go b/bindings/backup.go index 9621fdfe69a2117a3939550490ae25fc3d5562dd..955839e8cf326ecda83e36fd35f2ba68fdb657f9 100644 --- a/bindings/backup.go +++ b/bindings/backup.go @@ -65,3 +65,8 @@ func (b *Backup) StopBackup() error { func (b *Backup) IsBackupRunning() bool { return b.b.IsBackupRunning() } + +// AddJson stores a passed in json string in the backup structure +func (b *Backup) AddJson(json string) { + b.b.AddJson(json) +} diff --git a/bindings/client.go b/bindings/client.go index 1d27eebecf9dda16a8ed8b3e69f55ba4d52b4007..edda4a31dfa78239b4f7593f0a3db71af0a2021d 100644 --- a/bindings/client.go +++ b/bindings/client.go @@ -83,20 +83,30 @@ func NewPrecannedClient(precannedID int, network, storageDir string, password [] return nil } +type BackupReport struct { + RestoredContacts []*id.ID + Params string +} + // NewClientFromBackup constructs a new Client from an encrypted backup. The backup // is decrypted using the backupPassphrase. On success a successful client creation, // the function will return a JSON encoded list of the E2E partners -// contained in the backup. +// contained in the backup and a json-encoded string of the parameters stored in the backup func NewClientFromBackup(ndfJSON, storageDir string, sessionPassword, backupPassphrase, backupFileContents []byte) ([]byte, error) { - backupPartnerIds, err := api.NewClientFromBackup(ndfJSON, storageDir, + backupPartnerIds, jsonParams, err := api.NewClientFromBackup(ndfJSON, storageDir, sessionPassword, backupPassphrase, backupFileContents) if err != nil { return nil, errors.New(fmt.Sprintf("Failed to create new "+ "client from backup: %+v", err)) } - return json.Marshal(backupPartnerIds) + report := BackupReport{ + RestoredContacts: backupPartnerIds, + Params: jsonParams, + } + + return json.Marshal(report) } // Login will load an existing client from the storageDir diff --git a/cmd/root.go b/cmd/root.go index 28e3a988faa5a7613644d6f18c82a76205ac80b7..4427073ab096b3ad070f71db3ed53b0c0b880d07 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -572,7 +572,7 @@ func createClient() *api.Client { } // Construct client from backup data - backupIdList, err := api.NewClientFromBackup(string(ndfJSON), storeDir, + backupIdList, _, err := api.NewClientFromBackup(string(ndfJSON), storeDir, []byte(pass), backupPass, backupFile) backupIdListPath := viper.GetString("backupIdList") diff --git a/go.mod b/go.mod index 91329129941a9939cf4e367a3f489d8832afad1a..a2446e93a6cf797f53c2c21ea67239ff751c45fd 100644 --- a/go.mod +++ b/go.mod @@ -13,7 +13,7 @@ require ( github.com/spf13/viper v1.7.1 gitlab.com/elixxir/bloomfilter v0.0.0-20200930191214-10e9ac31b228 gitlab.com/elixxir/comms v0.0.4-0.20220308183624-c2183e687a03 - gitlab.com/elixxir/crypto v0.0.7-0.20220222221347-95c7ae58da6b + gitlab.com/elixxir/crypto v0.0.7-0.20220308234138-3b0743539e7d gitlab.com/elixxir/ekv v0.1.6 gitlab.com/elixxir/primitives v0.0.3-0.20220222212109-d412a6e46623 gitlab.com/xx_network/comms v0.0.4-0.20220223205228-7c4974139569 diff --git a/go.sum b/go.sum index c58adf1fd69a2e0d3f2cca2847827ca8bf9ed115..1a8a6a22e6efed0e6773d33056e8cfd65b426f25 100644 --- a/go.sum +++ b/go.sum @@ -278,6 +278,8 @@ gitlab.com/elixxir/crypto v0.0.0-20200804182833-984246dea2c4/go.mod h1:ucm9SFKJo gitlab.com/elixxir/crypto v0.0.3/go.mod h1:ZNgBOblhYToR4m8tj4cMvJ9UsJAUKq+p0gCp07WQmhA= gitlab.com/elixxir/crypto v0.0.7-0.20220222221347-95c7ae58da6b h1:m80Ub5mshPbMzYjRC0nXuI8vtm6e5crISczRsP2YUJ4= gitlab.com/elixxir/crypto v0.0.7-0.20220222221347-95c7ae58da6b/go.mod h1:tD6XjtQh87T2nKZL5I/pYPck5M2wLpkZ1Oz7H/LqO10= +gitlab.com/elixxir/crypto v0.0.7-0.20220308234138-3b0743539e7d h1:c8GJzgdJQPm2ckd2U5uGVPkfF1qMBy8edE4LUlYJMx8= +gitlab.com/elixxir/crypto v0.0.7-0.20220308234138-3b0743539e7d/go.mod h1:tD6XjtQh87T2nKZL5I/pYPck5M2wLpkZ1Oz7H/LqO10= gitlab.com/elixxir/ekv v0.1.6 h1:M2hUSNhH/ChxDd+s8xBqSEKgoPtmE6hOEBqQ73KbN6A= gitlab.com/elixxir/ekv v0.1.6/go.mod h1:e6WPUt97taFZe5PFLPb1Dupk7tqmDCTQu1kkstqJvw4= gitlab.com/elixxir/primitives v0.0.0-20200731184040-494269b53b4d/go.mod h1:OQgUZq7SjnE0b+8+iIAT2eqQF+2IFHn73tOo+aV11mg=