public async Task AddRecord(PasswordRecord record, string folderUid) { IFolderNode node = null; if (!string.IsNullOrEmpty(folderUid)) { userFolders.TryGetValue(folderUid, out node); } var recordKey = CryptoUtils.GenerateEncryptionKey(); var recordAdd = new RecordAddCommand { recordUid = CryptoUtils.GenerateUid(), recordKey = CryptoUtils.EncryptAesV1(recordKey, Auth.DataKey).Base64UrlEncode(), recordType = "password" }; if (node == null) { recordAdd.folderType = "user_folder"; } else { switch (node.Type) { case FolderType.UserFolder: recordAdd.folderType = "user_folder"; recordAdd.folderUid = node.FolderUid; break; case FolderType.SharedFolder: case FolderType.SharedFolderForder: recordAdd.folderUid = node.FolderUid; recordAdd.folderType = node.Type == FolderType.SharedFolder ? "shared_folder" : "shared_folder_folder"; if (node is ISharedFolderNode sfn) { if (sharedFolders.TryGetValue(sfn.SharedFolderUid, out SyncDownSharedFolder sf)) { recordAdd.folderKey = CryptoUtils.EncryptAesV1(recordKey, sf.unencryptedSharedFolderKey).Base64UrlEncode(); } } if (string.IsNullOrEmpty(recordAdd.folderKey)) { throw new Exception(string.Format("Cannot resolve shared folder for folder UID", folderUid)); } break; } } var settings = new DataContractJsonSerializerSettings { UseSimpleDictionaryFormat = true }; var dataSerializer = new DataContractJsonSerializer(typeof(RecordData), settings); var data = record.ExtractRecordData(); using (var ms = new MemoryStream()) { dataSerializer.WriteObject(ms, data); recordAdd.data = CryptoUtils.EncryptAesV1(ms.ToArray(), recordKey).Base64UrlEncode(); } await Auth.ExecuteAuthCommand <RecordAddCommand>(recordAdd); await this.SyncDown(); }
public async Task SaveRecord(PasswordRecord record, bool skipData = false, bool skipExtra = true) { SyncDownRecord existingRecord = null; if (!string.IsNullOrEmpty(record.Uid)) { records.TryGetValue(record.Uid, out existingRecord); } var updateRecord = new RecordUpdateRecord(); byte[] recordKey = null; if (existingRecord != null) { updateRecord.recordUid = existingRecord.recordUid; recordKey = existingRecord.unencryptedRecordKey; if (metaData.TryGetValue(existingRecord.recordUid, out SyncDownRecordMetaData sdrmd)) { if (sdrmd.recordKeyType == 2) { updateRecord.recordKey = CryptoUtils.EncryptAesV1(recordKey, Auth.DataKey).Base64UrlEncode(); } } updateRecord.revision = existingRecord.revision; ResolveRecordAccessPath(updateRecord); } else { updateRecord.recordUid = CryptoUtils.GenerateUid(); recordKey = CryptoUtils.GenerateEncryptionKey(); updateRecord.recordKey = CryptoUtils.EncryptAesV1(recordKey, Auth.DataKey).Base64UrlEncode(); updateRecord.revision = 0; } var settings = new DataContractJsonSerializerSettings { UseSimpleDictionaryFormat = true }; if (!skipData) { var dataSerializer = new DataContractJsonSerializer(typeof(RecordData), settings); RecordData existingData = null; if (existingRecord != null) { try { var unencrypted_data = CryptoUtils.DecryptAesV1(existingRecord.data.Base64UrlDecode(), existingRecord.unencryptedRecordKey); using (var ms = new MemoryStream(unencrypted_data)) { existingData = (RecordData)dataSerializer.ReadObject(ms); } } catch (Exception e) { Trace.TraceError("Decrypt Record: UID: {0}, {1}: \"{2}\"", existingRecord.recordUid, e.GetType().Name, e.Message); } } var data = record.ExtractRecordData(existingData); using (var ms = new MemoryStream()) { dataSerializer.WriteObject(ms, data); updateRecord.data = CryptoUtils.EncryptAesV1(ms.ToArray(), recordKey).Base64UrlEncode(); } } if (!skipExtra) { var extraSerializer = new DataContractJsonSerializer(typeof(RecordExtra), settings); RecordExtra existingExtra = null; if (existingRecord != null) { try { var unencrypted_extra = CryptoUtils.DecryptAesV1(existingRecord.extra.Base64UrlDecode(), existingRecord.unencryptedRecordKey); using (var ms = new MemoryStream(unencrypted_extra)) { existingExtra = (RecordExtra)extraSerializer.ReadObject(ms); } } catch (Exception e) { Trace.TraceError("Decrypt Record: UID: {0}, {1}: \"{2}\"", existingRecord.recordUid, e.GetType().Name, e.Message); } } var extra = record.ExtractRecordExtra(existingExtra); using (var ms = new MemoryStream()) { extraSerializer.WriteObject(ms, extra); updateRecord.extra = CryptoUtils.EncryptAesV1(ms.ToArray(), recordKey).Base64UrlEncode(); } var udata = new RecordUpdateUData(); var ids = new HashSet <string>(); if (record.Attachments != null) { foreach (var atta in record.Attachments) { ids.Add(atta.Id); if (atta.Thumbnails != null) { foreach (var thumb in atta.Thumbnails) { ids.Add(thumb.Id); } } } } udata.fileIds = ids.ToArray(); updateRecord.udata = udata; } var command = new RecordUpdateCommand(); if (existingRecord != null) { command.updateRecords = new RecordUpdateRecord[] { updateRecord }; } else { command.addRecords = new RecordUpdateRecord[] { updateRecord }; } var rs = await Auth.ExecuteAuthCommand <RecordUpdateCommand, RecordUpdateResponse>(command); await this.SyncDown(); }
public async Task Login(IUserConfiguration user = null) { var configuration = Api.Storage.Get(); user = await ResolveUserConfiguration(user, configuration); var username = user?.Username; var password = user?.Password; if (string.IsNullOrEmpty(username) || string.IsNullOrEmpty(password)) { return; } var token = user.TwoFactorToken; var tokenType = "device_token"; string authHash = null; PreLoginResponse preLogin = null; while (true) { if (preLogin == null) { preLogin = await Api.GetPreLogin(username); authHash = null; } var authParams = preLogin.Salt[0]; int iterations = authParams.Iterations; byte[] salt = authParams.Salt_.ToByteArray(); if (authHash == null) { authHash = CryptoUtils.DeriveV1KeyHash(password, salt, iterations).Base64UrlEncode(); } var command = new LoginCommand(); command.username = username; command.authResponse = authHash; command.include = new[] { "keys", "settings", "enforcements", "is_enterprise_admin" }; command.twoFactorToken = token; command.twoFactorType = !string.IsNullOrEmpty(token) ? tokenType : null; command.deviceTokenExpiresInDays = !string.IsNullOrEmpty(token) && tokenType != "device_token" ? 9999 : (int?)null; var loginRs = await Api.ExecuteV2Command <LoginCommand, LoginResponse>(command); if (!loginRs.IsSuccess && loginRs.resultCode == "auth_failed") // invalid password { throw new Exception("Invalid user name or password"); } else { if (!string.IsNullOrEmpty(loginRs.deviceToken)) { token = loginRs.deviceToken; tokenType = "device_token"; } SessionToken = loginRs.sessionToken; Username = username; accountSettings = loginRs.accountSettings; if (loginRs.keys != null) { if (loginRs.keys.encryptedDataKey != null) { var key = CryptoUtils.DeriveKeyV2("data_key", password, salt, iterations); DataKey = CryptoUtils.DecryptAesV2(loginRs.keys.encryptedDataKey.Base64UrlDecode(), key); } else if (loginRs.keys.encryptionParams != null) { DataKey = CryptoUtils.DecryptEncryptionParams(password, loginRs.keys.encryptionParams.Base64UrlDecode()); } else { throw new Exception("Missing data key"); } if (loginRs.keys.encryptedPrivateKey != null) { privateKeyData = CryptoUtils.DecryptAesV1(loginRs.keys.encryptedPrivateKey.Base64UrlDecode(), DataKey); privateKey = null; } } if (loginRs.IsSuccess) { EncryptedPassword = CryptoUtils.EncryptAesV2(Encoding.UTF8.GetBytes(password), DataKey); TwoFactorToken = token; authResponse = authHash; IsEnterpriseAdmin = loginRs.isEnterpriseAdmin ?? false; enforcements = loginRs.enforcements; StoreConfigurationIfChanged(configuration); break; } switch (loginRs.resultCode) { case "need_totp": case "invalid_device_token": case "invalid_totp": token = await Ui.GetTwoFactorCode(); if (!string.IsNullOrEmpty(token)) { tokenType = "one_time"; continue; } break; case "auth_expired": await Ui.DisplayDialog(DialogType.Information, loginRs.message); password = await this.ChangeMasterPassword(iterations); if (!string.IsNullOrEmpty(password)) { preLogin = null; continue; } break; case "auth_expired_transfer": var shareAccountTo = loginRs.accountSettings.shareAccountTo; if (await Ui.DisplayDialog(DialogType.Confirmation, "Do you accept Account Transfer policy?")) { await this.ShareAccount(); continue; } break; } throw new KeeperApiException(loginRs.resultCode, loginRs.message); } } }