private bool ProcessMessageRequestEntrySecrets( GetMessages.ResponseParamsMessage messageItem, DeviceToDeviceMessages.IMessage message) { var messageContents = (DeviceToDeviceMessages.RequestEntrySecrets)message; var accountSettings = _model.ServerAccountSettings.Query().First(); var entry = _model.Entries.Query().FirstOrDefault(r => r.Identifier == messageContents.EntryIdentifier); if (entry == null) return false; var linkedDeviceCryptoKeyId = accountSettings.LinkedDeviceCryptoKeyId; var linkedDeviceCryptoKey = _model.CryptoKeys.Query().First(r => r.Id == linkedDeviceCryptoKeyId); var entrySecrets = new Dictionary<string, string>(); foreach (var secretIdentifier in messageContents.SecretIdentifiers) { var entrySecretsResult = _model.EntriesSharedSecrets.Query() .FirstOrDefault(r => r.EntryId == entry.Id && r.SecretIdentifier == secretIdentifier); if (entrySecretsResult == null) continue; var entrySecretData = _model.EntriesSharedSecretsData.Query().First(r => r.Id == entrySecretsResult.EntrySecretDataId); entrySecrets.Add(entrySecretsResult.SecretIdentifier, entrySecretData.Secret); } if (entrySecrets.Count != messageContents.SecretIdentifiers.Count) { // We don't appear to have all the requested secrets. return false; } var requestConfirmed = _controller.PromptSecretShareRequestSafe(); var apiRequest = new SendLinkedDeviceMessage { //LinkIdentifier = link.Identifier, SecondsValidFor = 30 }; if (!requestConfirmed) { var deniedMessage = new DeviceToDeviceMessages.SendEntrySecrets { OriginalMessageIdentifier = messageItem.Identifier, RequestAccepted = false }; apiRequest.SetMessage(deniedMessage, linkedDeviceCryptoKey.PublicKeyPem); apiRequest.GetResponse(GetApiClient()); return true; } var acceptedMessage = new DeviceToDeviceMessages.SendEntrySecrets { OriginalMessageIdentifier = messageItem.Identifier, RequestAccepted = true, Secrets = entrySecrets }; apiRequest.SetMessage(acceptedMessage, linkedDeviceCryptoKey.PublicKeyPem); apiRequest.GetResponse(GetApiClient()); return true; }
private bool ProcessMessageRequestEntrySecrets( GetMessages.ResponseParamsMessage messageItem, DeviceToDeviceMessages.IMessage message) { var messageContents = (DeviceToDeviceMessages.RequestEntrySecrets)message; var accountSettings = _model.ServerAccountSettings.Query().First(); var entry = _model.Entries.Query().FirstOrDefault(r => r.Identifier == messageContents.EntryIdentifier); if (entry == null) { return(false); } var linkedDeviceCryptoKeyId = accountSettings.LinkedDeviceCryptoKeyId; var linkedDeviceCryptoKey = _model.CryptoKeys.Query().First(r => r.Id == linkedDeviceCryptoKeyId); var entrySecrets = new Dictionary <string, string>(); foreach (var secretIdentifier in messageContents.SecretIdentifiers) { var entrySecretsResult = _model.EntriesSharedSecrets.Query() .FirstOrDefault(r => r.EntryId == entry.Id && r.SecretIdentifier == secretIdentifier); if (entrySecretsResult == null) { continue; } var entrySecretData = _model.EntriesSharedSecretsData.Query().First(r => r.Id == entrySecretsResult.EntrySecretDataId); entrySecrets.Add(entrySecretsResult.SecretIdentifier, entrySecretData.Secret); } if (entrySecrets.Count != messageContents.SecretIdentifiers.Count) { // We don't appear to have all the requested secrets. return(false); } var requestConfirmed = _controller.PromptSecretShareRequestSafe(); var apiRequest = new SendLinkedDeviceMessage { //LinkIdentifier = link.Identifier, SecondsValidFor = 30 }; if (!requestConfirmed) { var deniedMessage = new DeviceToDeviceMessages.SendEntrySecrets { OriginalMessageIdentifier = messageItem.Identifier, RequestAccepted = false }; apiRequest.SetMessage(deniedMessage, linkedDeviceCryptoKey.PublicKeyPem); apiRequest.GetResponse(GetApiClient()); return(true); } var acceptedMessage = new DeviceToDeviceMessages.SendEntrySecrets { OriginalMessageIdentifier = messageItem.Identifier, RequestAccepted = true, Secrets = entrySecrets }; apiRequest.SetMessage(acceptedMessage, linkedDeviceCryptoKey.PublicKeyPem); apiRequest.GetResponse(GetApiClient()); return(true); }
private void SyncDatabaseUploadEntries(int databaseId) { if (_databaseEntriesUploadRunning) return; if (_requestErrors > 0) return; var serverAccount = GetServerAccount(); var linkedClientCryptoKey = Model.CryptoKeys.Get(serverAccount.LinkedDeviceCryptoKeyId); var database = Model.Databases.Get(databaseId); var entriesToBeDeleted = Model.DatabasesEntries.Find(new DatabaseEntry {ToBeDeleted = true}).ToList(); foreach (var entryToBeDeleted in entriesToBeDeleted) { if (_stopSyncLoop || _processMessagesOnly) return; var deleteRequest = new DeleteDatabaseEntry { LinkIdentifier = database.Identifier, EntryIdentifier = entryToBeDeleted.Identifier }; var deleteOnDeviceMessageContent = new DeviceToDeviceMessages.DeleteEntry { LinkIdentifier = database.Identifier, EntryIdentifier = entryToBeDeleted.Identifier }; var deleteOnDeviceMessageRequest = new SendLinkedDeviceMessage(); deleteOnDeviceMessageRequest.SetMessage(deleteOnDeviceMessageContent, linkedClientCryptoKey.PublicKeyPem); try { deleteRequest.GetResponse(GetApiClient()); deleteOnDeviceMessageRequest.GetResponse(GetApiClient()); _requestErrors = 0; } catch (NetworkErrorException) { _requestErrors++; return; } catch (RequestException) { // Try again next time. continue; } var versions = Model.DatabasesEntriesDataVersions.Find(new DatabaseEntryDataVersion { DatabaseEntryId = entryToBeDeleted.Id }); foreach (var version in versions) { Model.DatabasesEntriesData.Delete(version.DatabaseEntryDataId); Model.DatabasesEntriesDataVersions.Delete(version.Id); } Model.DatabasesEntries.Delete(entryToBeDeleted.Id); } _databaseEntriesUploadRunning = true; Task.Run(() => { var databaseEntries = Model.DatabasesEntries.Find(new DatabaseEntry { DatabaseId = databaseId }); var entriesToSend = new List<DatabaseEntry>(); var entriesDataToSend = new List<DatabaseEntryData>(); var entriesDataSerialized = new List<string>(); var entriesSyncRequest = new List<DatabaseEntryDataSync>(); var entriesUploadItems = new List<SetDatabaseEntries.EntryItem>(); foreach (var entry in databaseEntries) { if (_stopSyncLoop || _processMessagesOnly) { _databaseEntriesUploadRunning = false; return; } var entryData = Model.DatabasesEntriesData.Get(entry.DatabaseEntryDataId); if (!entryData.PasswordShared) continue; // Is there an update request for this entry? var updateRequests = Model.DatabasesEntriesDataSync.Find(new DatabaseEntryDataSync { DatabaseEntryId = entry.Id }); var updateRequestsSorted = updateRequests?.OrderByDescending(update => update.CreatedAt); var latestUpdateRequest = updateRequestsSorted?.FirstOrDefault(); if (latestUpdateRequest == null) continue; // Remove duplicate update requests foreach (var updateRequest in updateRequestsSorted) { if (updateRequest.Id == latestUpdateRequest.Id) continue; Model.DatabasesEntriesDataSync.Delete(updateRequest.Id); } var newVersion = entry.Version + 1; var serializedEntry = JsonConvert.SerializeObject(entryData); var encryptedData = new EncryptedDataWithPassword( Util.CompressData(serializedEntry), serverAccount.BackupEncryptionPassword).ToString(); var group = Model.DatabasesGroups.Get(entry.DatabaseGroupId); entriesUploadItems.Add(new SetDatabaseEntries.EntryItem { Version = newVersion, GroupIdentifier = group.Identifier, EntryIdentifier = entry.Identifier, DataType = "ModelJsonGz", Data = encryptedData }); entriesToSend.Add(entry); entriesDataToSend.Add(entryData); entriesDataSerialized.Add(serializedEntry); entriesSyncRequest.Add(latestUpdateRequest); } if (entriesToSend.Count == 0) { _databaseEntriesUploadRunning = false; return; } _controller.SetDatabaseSyncStatus(databaseId, Statuses.Syncing); var request = new SetDatabaseEntries { LinkIdentifier = database.Identifier, Entries = entriesUploadItems }; try { request.GetResponse(GetApiClient()); _requestErrors = 0; } catch (ConflictException) { if (Program.AppEnvDebug) throw new NotImplementedException("TODO: Download update to group"); _databaseEntriesUploadRunning = false; return; } catch (NetworkErrorException) { _requestErrors++; _databaseEntriesUploadRunning = false; return; } catch (RequestException) { _databaseEntriesUploadRunning = false; return; } for (var i = 0; i < entriesToSend.Count; i++) { var entry = entriesToSend[i]; var entryUploadItem = entriesUploadItems[i]; var serializedEntry = entriesDataSerialized[i]; var latestUpdateRequest = entriesSyncRequest[i]; var newVersion = entryUploadItem.Version; // Create archive version var archiveData = JsonConvert.DeserializeObject<DatabaseEntryData>(serializedEntry); archiveData.RemoveId(); var archiveDataId = Model.DatabasesEntriesData.Create(archiveData); Model.DatabasesEntriesDataVersions.Create(new DatabaseEntryDataVersion { DatabaseEntryId = entry.Id, Version = newVersion, DatabaseEntryDataId = archiveDataId }); // Update current record Model.DatabasesEntries.Update(entry.Id, new DatabaseEntry { Version = newVersion }); // Sync completed Model.DatabasesEntriesDataSync.Delete(latestUpdateRequest.Id); } _controller.SetDatabaseSyncStatus(databaseId, Statuses.UpToDate); _databaseEntriesUploadRunning = false; }); }
public string GetSecret(int databaseId, int entryId, string secretIdentifier, string secretEncryptedData) { var database = Model.Databases.Get(databaseId); var serverAccount = Model.ServerAccounts.Get(database.ServerAccountId); var linkedDeviceCryptoKey = Model.CryptoKeys.Get(serverAccount.LinkedDeviceCryptoKeyId); var entry = Model.DatabasesEntries.Get(entryId); var deviceMessage = new ServerAPI.DeviceToDeviceMessages.RequestEntrySecrets { EntryIdentifier = entry.Identifier, SecretIdentifiers = new List<string> { secretIdentifier } }; var request = new SendLinkedDeviceMessage { //LinkIdentifier = database.Identifier, SecondsValidFor = 30 }; request.SetMessage(deviceMessage, linkedDeviceCryptoKey.PublicKeyPem); SendLinkedDeviceMessage.ResponseParams response; try { response = request.GetResponse(GetApiClient(serverAccount.Id)); } catch (RequestException) { return null; } var messageHandler = new SendSecretShareMessageHandler( _syncServers[serverAccount.Id], response.MessageIdentifier); // Give the sync process a kick up the arse _syncServers[serverAccount.Id].ProcessMessagesOnly(); var replyReceived = messageHandler.WaitForReply(); // Give the sync process a kick up the arse _syncServers[serverAccount.Id].ProcessMessagesOnlyStop(); if (!replyReceived) { MessageBox.Show(@"Secret share request timed out"); return null; } var secretShareMessage = messageHandler.Reply; if (!secretShareMessage.RequestAccepted) { MessageBox.Show(@"Secret share request denied"); return null; } string secretShare; secretShareMessage.Secrets.TryGetValue(secretIdentifier, out secretShare); if (string.IsNullOrEmpty(secretShare)) { messageHandler.MarkAsProcessed(false); MessageBox.Show(@"Error obtaining secret share"); return null; } var decryptedData = EncryptedData.DecryptData(secretShare, secretEncryptedData); messageHandler.MarkAsProcessed(true); return decryptedData; }
private void ProcessShares(int databaseId) { if (_processingSecretShares) return; var serverAccount = GetServerAccount(); if (serverAccount.LinkedDeviceCryptoKeyId == 0) return; var apiClient = GetApiClient(); var database = Model.Databases.Get(databaseId); var linkedClientCryptoKey = Model.CryptoKeys.Get(serverAccount.LinkedDeviceCryptoKeyId); _processingSecretShares = true; Task.Run(() => { bool moreToProcess; do { moreToProcess = false; var entries = Model.DatabasesEntries.Find(new DatabaseEntry { DatabaseId = databaseId }); var entriesToSend = new List<DatabaseEntry>(); var entriesDataToSend = new List<DatabaseEntryData>(); foreach (var entry in entries) { var entryData = Model.DatabasesEntriesData.Get(entry.DatabaseEntryDataId); if (entryData.PasswordShared) continue; if (entryData.Password == "") continue; if (entriesToSend.Count > 50) { moreToProcess = true; break; } entriesToSend.Add(entry); entriesDataToSend.Add(entryData); } if (entriesToSend.Count == 0) { _processingSecretShares = false; return; } var entrySecretsToSend = new List<DeviceToDeviceMessages.NewSecretItem>(); var entriesEncryptedPassword = new List<EncryptedData>(); for (var i = 0; i < entriesToSend.Count; i++) { var entry = entriesToSend[i]; var entryData = entriesDataToSend[i]; var encryptedPassword = new EncryptedData(entryData.Password); var newSecretIdentifier = Guid.NewGuid().ToString(); entrySecretsToSend.Add(new DeviceToDeviceMessages.NewSecretItem { EntryIdentifier = entry.Identifier, SecretIdentifier = newSecretIdentifier, Secret = encryptedPassword.Key }); entriesEncryptedPassword.Add(encryptedPassword); } var message = new DeviceToDeviceMessages.NewSecret { LinkIdentifier = database.Identifier, Secrets = entrySecretsToSend }; var request = new SendLinkedDeviceMessage { SecondsValidFor = 120 }; request.SetMessage(message, linkedClientCryptoKey.PublicKeyPem); SendLinkedDeviceMessage.ResponseParams response; try { response = request.GetResponse(apiClient); } catch (RequestException) { // Try again later _processingSecretShares = false; return; } var processedSuccess = ProcessSharesCheckReceived(response.MessageIdentifier); if (!processedSuccess) { _processingSecretShares = false; return; } for (var i = 0; i < entriesToSend.Count; i++) { var entry = entriesToSend[i]; var entryData = entriesDataToSend[i]; var entrySecret = entrySecretsToSend[i]; var entryEncryptedPassword = entriesEncryptedPassword[i]; var originalSecretIdentifier = entryData.PasswordSharedIdentifier; var newSecretIdentifier = entrySecret.SecretIdentifier; Model.DatabasesEntriesData.Update(entryData.Id, new DatabaseEntryData { Password = "", PasswordShared = true, PasswordSharedIdentifier = newSecretIdentifier, PasswordEncryptedData = entryEncryptedPassword.ToString() }); Model.DatabasesEntriesDataSync.Create(new DatabaseEntryDataSync { DatabaseEntryId = entry.Id }); if (!string.IsNullOrEmpty(originalSecretIdentifier)) { // Tell the device the previous secret can be deleted. var deleteSecretMessage = new DeviceToDeviceMessages.DeleteSecret { SecretIdentifier = originalSecretIdentifier }; var deleteSecretRequest = new SendLinkedDeviceMessage(); deleteSecretRequest.SetMessage(deleteSecretMessage, linkedClientCryptoKey.PublicKeyPem); try { deleteSecretRequest.GetResponse(apiClient); } catch (RequestException) { // Errr, probably should retry this another time, but lots of effort for little gain. // I'll come back to it later. } } } } while (moreToProcess); _processingSecretShares = false; RestartSyncLoop(); }); }