public async Task <Response <string> > PeekIntoUnreadTextMessageWithoutSideEffects(Message message) { var response = new Response <string>(); if (message.MessageType == MessageType.Text && message.LocalMessageState == LocalMessageState.JustReceived) { GetE2EDecryptionKeyResult getE2EDecryptionKeyResult = await this.e2ERatchet.GetEndToEndDecryptionKeyAsync(message.SenderId, message.DynamicPublicKey, message.PrivateKeyHint); if (getE2EDecryptionKeyResult.E2EDecryptionKeyType != E2EDecryptionKeyType.UnavailableDynamicPrivateKey) { response.Result = DecryptBytesToText(message.TextCipher, getE2EDecryptionKeyResult.E2EDecryptionKeyMaterial); response.SetSuccess(); } else { message.LocalMessageState = LocalMessageState.RatchetMismatchError; response.SetError(nameof(LocalMessageState.RatchetMismatchError)); } } return(response); }
/// <summary> /// Fully decrypts a message of all types, both control and content messages (including images), if an end-to-en-encryption key can be determined. /// </summary> async Task <Message> TryDecryptMessageToFindSenderEnryptDecryptionKeyAndSaveIt(XMessage xmessage) { try { IReadOnlyList <Identity> contacts = await this.repo.GetAllContacts(); var decryptedMessage = new Message { // XMessage fields RecipientId = "1", // drop my Id TextCipher = xmessage.TextCipher, ImageCipher = xmessage.ImageCipher, DynamicPublicKey = xmessage.DynamicPublicKey, DynamicPublicKeyId = xmessage.DynamicPublicKeyId, PrivateKeyHint = xmessage.PrivateKeyHint, LocalMessageState = LocalMessageState.JustReceived, SendMessageState = SendMessageState.None, Side = MessageSide.You, // This is what we try to decrypt here MessageType = MessageType.None, SenderId = null, }; Response <CipherV2> decodeMetaResponse = this.ixdsCryptoService.BinaryDecodeXDSSec(xmessage.MetaCipher, null); if (!decodeMetaResponse.IsSuccess) { return(null); // garbled, no chance, ignore! } foreach (Identity identity in contacts) { GetE2EDecryptionKeyResult getE2EDecryptionKeyResult = await this.e2eRatchet.GetEndToEndDecryptionKeyAsync(identity.Id, xmessage.DynamicPublicKey, xmessage.PrivateKeyHint); if (getE2EDecryptionKeyResult.E2EDecryptionKeyType == E2EDecryptionKeyType.UnavailableDynamicPrivateKey) { continue; // there was a privateKeyHint != 0 in the message, but the dynamic private key was not in the ratchet for user in the loop } KeyMaterial64 keyMaterial64 = getE2EDecryptionKeyResult.E2EDecryptionKeyMaterial; var decryptMetaResponse = this.ixdsCryptoService.BinaryDecrypt(decodeMetaResponse.Result, keyMaterial64, null); if (!decryptMetaResponse.IsSuccess) { continue; } await this.e2eRatchet.SaveIncomingDynamicPublicKeyOnSuccessfulDecryptionAsync(identity.Id, xmessage.DynamicPublicKey, xmessage.DynamicPublicKeyId); XMessageMetaData metadata = decryptMetaResponse.Result.GetBytes().DeserializeMessageMetadata(); decryptedMessage.SenderId = identity.Id; decryptedMessage.MessageType = metadata.MessageType; decryptedMessage.SenderLocalMessageId = metadata.SenderLocalMessageId.ToString(); if (decryptedMessage.MessageType.IsReceipt()) { return(decryptedMessage); } if (decryptedMessage.MessageType.IsContent()) { if (decryptedMessage.MessageType == MessageType.Text || decryptedMessage.MessageType == MessageType.TextAndMedia || decryptedMessage.MessageType == MessageType.File) { // if the message has text, decrypt all text var decodeTextResponse = this.ixdsCryptoService.BinaryDecodeXDSSec(xmessage.TextCipher, null); if (!decodeTextResponse.IsSuccess) { return(null); // something is wrong, should have worked } var decrpytTextResponse = this.ixdsCryptoService.Decrypt(decodeTextResponse.Result, keyMaterial64, null); if (!decrpytTextResponse.IsSuccess) { return(null); // something is wrong, should have worked } decryptedMessage.ThreadText = decrpytTextResponse.Result.Text; } if (decryptedMessage.MessageType == MessageType.Media || decryptedMessage.MessageType == MessageType.TextAndMedia || decryptedMessage.MessageType == MessageType.File) { // if the message has image content, decrypt the image content var decodeImageResponse = this.ixdsCryptoService.BinaryDecodeXDSSec(xmessage.ImageCipher, null); if (!decodeImageResponse.IsSuccess) { return(null); // something is wrong, should have worked } var decrpytImageResponse = this.ixdsCryptoService.BinaryDecrypt(decodeImageResponse.Result, keyMaterial64, null); if (!decrpytImageResponse.IsSuccess) { return(null); // something is wrong, should have worked } decryptedMessage.ThreadMedia = decrpytImageResponse.Result.GetBytes(); } decryptedMessage.EncryptedE2EEncryptionKey = this.ixdsCryptoService.DefaultEncrypt(keyMaterial64.GetBytes(), this.ixdsCryptoService.SymmetricKeyRepository.GetMasterRandomKey()); decryptedMessage.LocalMessageState = LocalMessageState.Integrated; await this.repo.AddMessage(decryptedMessage); } else { throw new Exception($"Invalid MessageType {decryptedMessage.MessageType}"); } return(decryptedMessage); } // foreach // If we are here, assume it's a new contact's message or RESENT message: CipherV2 encryptedMetaData = decodeMetaResponse.Result; KeyMaterial64 initialKey = this.e2eRatchet.GetInitialE2EDecryptionKey(xmessage.DynamicPublicKey); var decryptInitialMetaResponse = this.ixdsCryptoService.BinaryDecrypt(encryptedMetaData, initialKey, null); if (decryptInitialMetaResponse.IsSuccess) { XMessageMetaData initialMessageMetadata = decryptInitialMetaResponse.Result.GetBytes().DeserializeMessageMetadata(); var incomingPublicKey = initialMessageMetadata.SenderPublicKey; // If we received several resent messages, the first from that incoming contact has already produced that contact: var contact = contacts.SingleOrDefault(c => ByteArrays.AreAllBytesEqual(c.StaticPublicKey, incomingPublicKey)); bool isIncomingContact = false; if (contact == null) { // Create new contact and save it to make the ratchet work normally isIncomingContact = true; var date = DateTime.UtcNow; var incomingContact = new Identity { Id = Guid.NewGuid().ToString(), StaticPublicKey = incomingPublicKey, ContactState = ContactState.Valid, Name = "Anonymous", FirstSeenUtc = date, LastSeenUtc = date }; await this.repo.AddContact(incomingContact); contact = incomingContact; } await this.e2eRatchet.SaveIncomingDynamicPublicKeyOnSuccessfulDecryptionAsync(contact.Id, xmessage.DynamicPublicKey, xmessage.DynamicPublicKeyId); // add metadata to message decryptedMessage.SenderId = contact.Id; decryptedMessage.MessageType = initialMessageMetadata.MessageType; decryptedMessage.SenderLocalMessageId = initialMessageMetadata.SenderLocalMessageId.ToString(); if (decryptedMessage.MessageType.IsContent()) { var success = true; GetE2EDecryptionKeyResult getE2EDecryptionKeyResult = await this.e2eRatchet.GetEndToEndDecryptionKeyAsync(contact.Id, xmessage.DynamicPublicKey, xmessage.PrivateKeyHint); KeyMaterial64 keyMaterial64 = getE2EDecryptionKeyResult.E2EDecryptionKeyMaterial; await this.e2eRatchet.GetEndToEndDecryptionKeyAsync(contact.Id, xmessage.DynamicPublicKey, xmessage.PrivateKeyHint); if (decryptedMessage.MessageType == MessageType.Text || decryptedMessage.MessageType == MessageType.TextAndMedia) { // if the message has text, decrypt all text var decodeTextResponse = this.ixdsCryptoService.BinaryDecodeXDSSec(xmessage.TextCipher, null); if (!decodeTextResponse.IsSuccess) { success = false; // something is wrong, should have worked } else { var decrpytTextResponse = this.ixdsCryptoService.Decrypt(decodeTextResponse.Result, keyMaterial64, null); if (!decrpytTextResponse.IsSuccess) { success = false; // something is wrong, should have worked } else { decryptedMessage.ThreadText = decrpytTextResponse.Result.Text; } } } if (decryptedMessage.MessageType == MessageType.Media || decryptedMessage.MessageType == MessageType.TextAndMedia) { // if the message has image content, decrypt the image content var decodeImageResponse = this.ixdsCryptoService.BinaryDecodeXDSSec(xmessage.ImageCipher, null); if (!decodeImageResponse.IsSuccess) { success = false; // something is wrong, should have worked } else { var decrpytImageResponse = this.ixdsCryptoService.BinaryDecrypt(decodeImageResponse.Result, keyMaterial64, null); if (!decrpytImageResponse.IsSuccess) { success = false; // something is wrong, should have worked } else { decryptedMessage.ThreadMedia = decrpytImageResponse.Result.GetBytes(); } } } if (success) { decryptedMessage.EncryptedE2EEncryptionKey = this.ixdsCryptoService.DefaultEncrypt(keyMaterial64.GetBytes(), this.ixdsCryptoService.SymmetricKeyRepository.GetMasterRandomKey()); decryptedMessage.LocalMessageState = LocalMessageState.Integrated; await this.contactListManager.ChatWorker_ContactUpdateReceived(null, contact.Id); await this.repo.AddMessage(decryptedMessage); return(decryptedMessage); } else { if (isIncomingContact) { await this.repo.DeleteContacts(new[] { contact.Id }); // delete the just incoming contact if the was an error anyway. } return(null); } } else { return(null); } } // nope, resend request (I hope we don't loop here!) string networkPayloadHash = NetworkPayloadHash.ComputeAsGuidString(xmessage.SerializedPayload); if (!this._resendsRequested.Contains(networkPayloadHash)) { var response = await this.chatClient.UploadResendRequest(new XResendRequest { Id = networkPayloadHash, RecipientId = null }); if (response.IsSuccess) { this._resendsRequested.Add(networkPayloadHash); } } } catch (Exception e) { this.logger.LogError(e.Message); } return(null); }
public async Task <Response> DecryptCipherTextInVisibleBubble(Message message) { var response = new Response(); try { if (message.ThreadText != null && message.MessageType == MessageType.Text) // TODO: do this properly ....nothing to do, already decrypted { response.SetSuccess(); return(response); } KeyMaterial64 decryptionkey; if (message.LocalMessageState == LocalMessageState.JustReceived) // this is an incoming message { GetE2EDecryptionKeyResult getE2EDecryptionKeyResult = await this.e2ERatchet.GetEndToEndDecryptionKeyAsync(message.SenderId, message.DynamicPublicKey, message.PrivateKeyHint); if (getE2EDecryptionKeyResult.E2EDecryptionKeyType != E2EDecryptionKeyType.UnavailableDynamicPrivateKey) { decryptionkey = getE2EDecryptionKeyResult.E2EDecryptionKeyMaterial; } else { message.LocalMessageState = LocalMessageState.RatchetMismatchError; // When adding a contact and sending the first message to him, the contact can immediately read the message, // because the message is encrypted with the static public key of that contact. // But when one side deletes the contact, and the contact sends a message again, that's not possible, // because the sender dosn't know he would need to use only the static public key. // Instead, he's also use the last dynamic key, expecting the other side has matching material. // Then, we land here. A recovery would require that we ask the other side to resend the message. // Instead, just a delivery receipt is sent automatically, which repairs the ratchet. // The problem is, the other side does not know message.LocalMessageState = LocalMessageState.RatchetMismatchError; // create anothe enum member for this case? await this.repo.UpdateMessage(message); response.SetError(nameof(LocalMessageState.RatchetMismatchError)); return(response); } await Task.Run(() => DecryptToCache(message, decryptionkey)); await this.e2ERatchet.SaveIncomingDynamicPublicKeyOnSuccessfulDecryptionAsync(message.SenderId, message.DynamicPublicKey, message.DynamicPublicKeyId); message.EncryptedE2EEncryptionKey = this.ixdsCryptoService.DefaultEncrypt(decryptionkey.GetBytes(), this.ixdsCryptoService.SymmetricKeyRepository.GetMasterRandomKey()); message.LocalMessageState = LocalMessageState.Integrated; Debug.Assert(message.MessageType != MessageType.ReadReceipt && message.MessageType != MessageType.DeliveryReceipt); await this.repo.UpdateMessage(message); await this.ChatWorker.SendReceipt(message, MessageType.ReadReceipt); } else // this is a message we have stored locally { if (message.EncryptedE2EEncryptionKey == null) { message.LocalMessageState = LocalMessageState.LocalDecryptionError; } else { decryptionkey = new KeyMaterial64(this.ixdsCryptoService.DefaultDecrypt(message.EncryptedE2EEncryptionKey, this.ixdsCryptoService.SymmetricKeyRepository.GetMasterRandomKey())); await Task.Run(() => DecryptToCache(message, decryptionkey)); } // should we check that a read receipt has really been sent and retry till we are sure? } response.SetSuccess(); } catch (Exception e) { response.SetError(e.Message); this.logger.LogError(e.Message); } return(response); }