public static byte[] SerializeCore(this XMessageMetaData m) { byte[] serialized = PocoSerializer.Begin() .Append((byte)m.MessageType) .Append(m.SenderLocalMessageId) .Append(m.SenderPublicKey) .Finish(); return(serialized); }
public static XMessageMetaData DeserializeMessageMetadata(this byte[] messageMetadata) { var m = new XMessageMetaData(); var ser = PocoSerializer.GetDeserializer(messageMetadata); m.MessageType = (MessageType)ser.MakeByte(0); m.SenderLocalMessageId = ser.MakeInt32(1); m.SenderPublicKey = ser.MakeByteArray(2); return(m); }
void EncryptWithStrategy(Message message, KeyMaterial64 keyMaterial, RoundsExponent roundsExponent, KeyMaterial64 initialMetadataKeyMaterial) { XMessageMetaData messageMetadata = new XMessageMetaData { MessageType = message.MessageType }; switch (message.MessageType) { case MessageType.Text: message.TextCipher = EncryptTextToBytes(message.ThreadText, keyMaterial, roundsExponent); messageMetadata.SenderLocalMessageId = int.Parse(message.Id); break; case MessageType.Media: message.ImageCipher = this.ixdsCryptoService.DefaultEncrypt(message.ThreadMedia, keyMaterial); messageMetadata.SenderLocalMessageId = int.Parse(message.Id); break; case MessageType.TextAndMedia: case MessageType.File: message.TextCipher = EncryptTextToBytes(message.ThreadText, keyMaterial, roundsExponent); message.ImageCipher = this.ixdsCryptoService.DefaultEncrypt(message.ThreadMedia, keyMaterial); messageMetadata.SenderLocalMessageId = int.Parse(message.Id); break; case MessageType.DeliveryReceipt: case MessageType.ReadReceipt: messageMetadata.SenderLocalMessageId = int.Parse(message.SenderLocalMessageId); break; default: throw new Exception("Invalid MessageType"); } if (initialMetadataKeyMaterial != null) // initialMetadataKeyMaterial is only for resend requests { messageMetadata.SenderPublicKey = this.profileViewModel.PublicKey; message.MetaCipher = this.ixdsCryptoService.DefaultEncrypt(messageMetadata.SerializeCore(), initialMetadataKeyMaterial); } else { messageMetadata.SenderPublicKey = this.ixdsCryptoService.GetRandom(32).Result.X; message.MetaCipher = this.ixdsCryptoService.DefaultEncrypt(messageMetadata.SerializeCore(), keyMaterial); } if (!message.MessageType.IsReceipt()) { message.EncryptedE2EEncryptionKey = this.ixdsCryptoService.DefaultEncrypt(keyMaterial.GetBytes(), this.ixdsCryptoService.SymmetricKeyRepository.GetMasterRandomKey()); } }
/// <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); }