public async Task sendOmemoMessageAsync(OmemoEncryptedMessage msg, string srcBareJid, string dstBareJid, bool trustedSrcKeysOnly, bool trustedDstKeysOnly) { // Check if already trying to build a new session: if (MESSAGE_CACHE.ContainsKey(dstBareJid)) { MESSAGE_CACHE[dstBareJid].Item1.Add(msg); } else { // If not start a new session build helper: OmemoSessionBuildHelper sessionHelper = new OmemoSessionBuildHelper(srcBareJid, dstBareJid, CONNECTION, this, trustedSrcKeysOnly, trustedDstKeysOnly); MESSAGE_CACHE[dstBareJid] = new Tuple <List <OmemoEncryptedMessage>, OmemoSessionBuildHelper>(new List <OmemoEncryptedMessage>(), sessionHelper); MESSAGE_CACHE[dstBareJid].Item1.Add(msg); Tuple <OmemoDeviceListSubscriptionState, DateTime> subscription = OMEMO_STORAGE.LoadDeviceListSubscription(dstBareJid); OmemoSessionBuildResult result = await sessionHelper.buildSessionAsync(subscription.Item1); if (result.SUCCESS) { OMEMO_SESSIONS[dstBareJid] = result.SESSIONS; await sendAllOutstandingMessagesAsync(result.SESSIONS); } else { OmemoSessionBuildErrorEventArgs args = new OmemoSessionBuildErrorEventArgs(dstBareJid, result.ERROR, MESSAGE_CACHE[dstBareJid]?.Item1 ?? new List <OmemoEncryptedMessage>()); MESSAGE_CACHE.Remove(dstBareJid); CONNECTION.OnOmemoSessionBuildError(args); Logger.Error("Failed to build OMEMO session for: " + dstBareJid + " with: " + result.ERROR); } } }
public async Task decryptOmemoEncryptedMessageAsync(OmemoEncryptedMessage msg, bool trustedKeysOnly) { XMPPAccount account = CONNECTION.account; OmemoProtocolAddress receiverAddress = new OmemoProtocolAddress(account.getBareJid(), account.omemoDeviceId); // Try to decrypt the message, in case no exception occurred, everything went fine: OmemoDecryptionContext decryptCtx = new OmemoDecryptionContext(receiverAddress, account.omemoIdentityKey, account.omemoSignedPreKey, account.OMEMO_PRE_KEYS, trustedKeysOnly, OMEMO_STORAGE); msg.decrypt(decryptCtx); Debug.Assert(!msg.ENCRYPTED); Logger.Debug("Successfully decrypted an " + nameof(OmemoEncryptedMessage) + " for '" + receiverAddress.BARE_JID + "'."); // Republish bundle information in case the message is a key exchange message and used a PreKey: if (decryptCtx.keyExchange) { Logger.Info("Received a OMEMO key exchange message. Republishing bundle for '" + receiverAddress.BARE_JID + "'..."); PreKeyModel newPreKey = decryptCtx.STORAGE.ReplaceOmemoPreKey(decryptCtx.usedPreKey); account.OMEMO_PRE_KEYS.Remove(decryptCtx.usedPreKey); account.OMEMO_PRE_KEYS.Add(newPreKey); await announceBundleInfoAsync(); Logger.Info("Bundle for '" + receiverAddress.BARE_JID + "' republished."); } // Reply with an empty message to confirm the successful key exchange: // TODO: This is no good way, since there it would be possible to stalk people without them knowing. }
private async Task SendChatMessageAsync(ChatDataTemplate chat, string message) { MessageMessage toSendMsg; string from = chat.Client.dbAccount.fullJid.FullJid(); string to = chat.Chat.bareJid; string chatType = chat.Chat.chatType == ChatType.CHAT ? MessageMessage.TYPE_CHAT : MessageMessage.TYPE_GROUPCHAT; bool reciptRequested = true; if (chat.Chat.omemoInfo.enabled) { if (chat.Chat.chatType == ChatType.CHAT) { toSendMsg = new OmemoEncryptedMessage(from, to, message, chatType, reciptRequested); } else { // ToDo: Add MUC OMEMO support throw new NotImplementedException("Sending encrypted messages for MUC is not supported right now!"); } } else { toSendMsg = chat.Chat.chatType == ChatType.CHAT ? new MessageMessage(from, to, message, chatType, reciptRequested) : new MessageMessage(from, to, message, chatType, chat.Chat.muc.nickname, reciptRequested); } // Create a copy for the DB: ChatMessageModel toSendMsgDB = new ChatMessageModel(toSendMsg, chat.Chat) { state = toSendMsg is OmemoEncryptedMessage ? MessageState.TO_ENCRYPT : MessageState.SENDING }; // Update chat last active: chat.Chat.lastActive = DateTime.Now; // Update DB: chat.Chat.Update(); await DataCache.INSTANCE.AddChatMessageAsync(toSendMsgDB, chat.Chat); // Set the chat message id for later identification: toSendMsg.chatMessageId = toSendMsgDB.id; // Send the message: if (toSendMsg is OmemoEncryptedMessage toSendOmemoMsg) { await chat.Client.xmppClient.sendOmemoMessageAsync(toSendOmemoMsg, chat.Chat.bareJid, chat.Client.dbAccount.bareJid, chat.Client.dbAccount.omemoInfo.trustedKeysOnly, chat.Chat.omemoInfo.trustedKeysOnly); } else { await chat.Client.xmppClient.SendAsync(toSendMsg); } }
public async Task sendOmemoMessageAsync(OmemoEncryptedMessage msg, string chatJid, string accountJid, bool trustedSrcKeysOnly, bool trustedDstKeysOnly) { if (connection.omemoHelper is null) { OmemoSessionBuildError?.Invoke(this, new OmemoSessionBuildErrorEventArgs(chatJid, Network.XML.Messages.XEP_0384.Session.OmemoSessionBuildError.KEY_ERROR, new List <OmemoEncryptedMessage> { msg })); Logger.Error("Failed to send OMEMO message - OmemoHelper is null"); } else { await connection.omemoHelper.sendOmemoMessageAsync(msg, accountJid, chatJid, trustedSrcKeysOnly, trustedDstKeysOnly); } }
private async Task <bool> DecryptOmemoEncryptedMessageAsync(OmemoEncryptedMessage msg, bool trustedKeysOnly) { try { await client.xmppClient.connection.omemoHelper.decryptOmemoEncryptedMessageAsync(msg, trustedKeysOnly); return(true); } catch (Exception e) { Logger.Error("Failed to decrypt " + nameof(OmemoEncryptedMessage) + " for '" + client.dbAccount.bareJid + "' with: ", e); return(false); } }
public async Task decryptOmemoEncryptedMessageAsync(OmemoEncryptedMessage msg, bool trustedKeysOnly) { XMPPAccount account = CONNECTION.account; OmemoProtocolAddress receiverAddress = new OmemoProtocolAddress(account.getBareJid(), account.omemoDeviceId); // Try to decrypt the message, in case no exception occurred, everything went fine: OmemoDecryptionContext decryptCtx = new OmemoDecryptionContext(receiverAddress, account.omemoIdentityKey, account.omemoSignedPreKey, account.OMEMO_PRE_KEYS, trustedKeysOnly, OMEMO_STORAGE); msg.decrypt(decryptCtx); Debug.Assert(!msg.ENCRYPTED); Logger.Debug("Successfully decrypted an " + nameof(OmemoEncryptedMessage) + " for '" + receiverAddress.BARE_JID + "'."); // Republish bundle information in case the message is a key exchange message and used a PreKey: if (decryptCtx.keyExchange) { Logger.Info("Received a OMEMO key exchange message. Republishing bundle for '" + receiverAddress.BARE_JID + "'..."); PreKeyModel newPreKey = decryptCtx.STORAGE.ReplaceOmemoPreKey(decryptCtx.usedPreKey); account.OMEMO_PRE_KEYS.Remove(decryptCtx.usedPreKey); account.OMEMO_PRE_KEYS.Add(newPreKey); await announceBundleInfoAsync(); Logger.Info("Bundle for '" + receiverAddress.BARE_JID + "' republished."); // Reply with an empty message to confirm the successful key exchange: // TODO: This is no good way, since there it would be possible to stalk people without them knowing. OmemoEncryptedMessage reply = new OmemoEncryptedMessage(msg.getTo(), msg.getFrom(), null, msg.TYPE, false); OmemoDeviceGroup deviceGroup = new OmemoDeviceGroup(decryptCtx.senderAddress.BARE_JID); deviceGroup.SESSIONS.Add(decryptCtx.senderAddress.DEVICE_ID, decryptCtx.session); try { reply.encrypt(CONNECTION.account.omemoDeviceId, CONNECTION.account.omemoIdentityKey, OMEMO_STORAGE, new List <OmemoDeviceGroup> { deviceGroup }); await CONNECTION.SendAsync(reply); } catch (Exception e) { Logger.Error("[OMEMO HELPER] Failed to encrypt and the empty OMEMO message reply with: ", e); } Logger.Info($"Send an empty OMEMO message to confirm the successful key exchange with '{msg.getFrom()}'."); } }
public void Test_Omemo_Send_Receive_Send() { // Generate Alices keys: IdentityKeyPairModel aliceIdentKey = KeyHelper.GenerateIdentityKeyPair(); List <PreKeyModel> alicePreKeys = KeyHelper.GeneratePreKeys(0, 1); SignedPreKeyModel aliceSignedPreKey = KeyHelper.GenerateSignedPreKey(0, aliceIdentKey.privKey); Bundle aliceBundle = new Bundle() { identityKey = aliceIdentKey.pubKey, preKeys = alicePreKeys.Select(key => new PreKeyModel(null, key.pubKey, key.keyId)).ToList(), preKeySignature = aliceSignedPreKey.signature, signedPreKey = aliceSignedPreKey.preKey.pubKey, signedPreKeyId = aliceSignedPreKey.preKey.keyId }; InMemmoryOmemoStorage aliceStorage = new InMemmoryOmemoStorage(); DoubleRachet aliceRachet = new DoubleRachet(aliceIdentKey); // Generate Bobs keys: IdentityKeyPairModel bobIdentKey = KeyHelper.GenerateIdentityKeyPair(); List <PreKeyModel> bobPreKeys = KeyHelper.GeneratePreKeys(0, 1); SignedPreKeyModel bobSignedPreKey = KeyHelper.GenerateSignedPreKey(0, bobIdentKey.privKey); Bundle bobBundle = new Bundle() { identityKey = bobIdentKey.pubKey, preKeys = bobPreKeys.Select(key => new PreKeyModel(null, key.pubKey, key.keyId)).ToList(), preKeySignature = bobSignedPreKey.signature, signedPreKey = bobSignedPreKey.preKey.pubKey, signedPreKeyId = bobSignedPreKey.preKey.keyId }; InMemmoryOmemoStorage bobStorage = new InMemmoryOmemoStorage(); DoubleRachet bobRachet = new DoubleRachet(bobIdentKey); //-----------------OMEOMO Session Building:----------------- MessageParser2 parser = new MessageParser2(); string deviceListMsg = GetBobsDeviceListMsg(); List <AbstractMessage> messages = parser.parseMessages(ref deviceListMsg); Assert.IsTrue(messages.Count == 1); Assert.IsTrue(messages[0] is OmemoDeviceListResultMessage); OmemoDeviceListResultMessage devList = messages[0] as OmemoDeviceListResultMessage; uint selectedBobDeviceId = devList.DEVICES.getRandomDeviceId(); Assert.IsTrue(selectedBobDeviceId == BOB_ADDRESS.DEVICE_ID); // Alice builds a session to Bob: string bundleInfoMsg = GetBobsBundleInfoMsg(bobBundle); messages = parser.parseMessages(ref bundleInfoMsg); Assert.IsTrue(messages.Count == 1); Assert.IsTrue(messages[0] is OmemoBundleInformationResultMessage); OmemoBundleInformationResultMessage bundleInfo = messages[0] as OmemoBundleInformationResultMessage; Assert.IsTrue(bundleInfo.BUNDLE_INFO.deviceId == BOB_ADDRESS.DEVICE_ID); aliceStorage.StoreFingerprint(new OmemoFingerprint(bobBundle.identityKey, BOB_ADDRESS)); // Encrypt Message 1: string msg1 = "Hello OMEMO"; OmemoEncryptedMessage omemoEncryptedMessage = new OmemoEncryptedMessage(ALICE_ADDRESS.BARE_JID, BOB_ADDRESS.BARE_JID, msg1, MessageMessage.TYPE_CHAT, false); List <OmemoDeviceGroup> bobDevices = new List <OmemoDeviceGroup>(); OmemoDeviceGroup bobDeviceGroup = new OmemoDeviceGroup(BOB_ADDRESS.BARE_JID); bobDeviceGroup.SESSIONS[BOB_ADDRESS.DEVICE_ID] = new OmemoSessionModel(bobBundle, 0, aliceIdentKey); bobDevices.Add(bobDeviceGroup); omemoEncryptedMessage.encrypt(ALICE_ADDRESS.DEVICE_ID, aliceIdentKey, aliceStorage, bobDevices); Assert.IsTrue(omemoEncryptedMessage.ENCRYPTED); Assert.IsNotNull(aliceStorage.LoadFingerprint(BOB_ADDRESS)); Assert.IsNotNull(aliceStorage.LoadSession(BOB_ADDRESS)); // Decrypt Message 1: // Throws an exception in case something goes wrong: OmemoDecryptionContext decryptCtx1 = new OmemoDecryptionContext(BOB_ADDRESS, bobIdentKey, bobSignedPreKey, bobPreKeys, false, bobStorage); omemoEncryptedMessage.decrypt(decryptCtx1); Assert.AreEqual(msg1, omemoEncryptedMessage.MESSAGE); Assert.IsFalse(omemoEncryptedMessage.IS_PURE_KEY_EXCHANGE_MESSAGE); Assert.IsFalse(omemoEncryptedMessage.ENCRYPTED); Assert.IsNotNull(bobStorage.LoadFingerprint(ALICE_ADDRESS)); Assert.IsNotNull(bobStorage.LoadSession(ALICE_ADDRESS)); // Encrypt Message 2: string msg2 = "Hello OMEMO 2"; OmemoEncryptedMessage omemoEncryptedMessage2 = new OmemoEncryptedMessage(BOB_ADDRESS.BARE_JID, ALICE_ADDRESS.BARE_JID, msg2, MessageMessage.TYPE_CHAT, false); List <OmemoDeviceGroup> aliceDevices = new List <OmemoDeviceGroup>(); OmemoDeviceGroup aliceDeviceGroup = new OmemoDeviceGroup(ALICE_ADDRESS.BARE_JID); aliceDeviceGroup.SESSIONS[ALICE_ADDRESS.DEVICE_ID] = bobStorage.LoadSession(ALICE_ADDRESS); aliceDevices.Add(aliceDeviceGroup); omemoEncryptedMessage2.encrypt(BOB_ADDRESS.DEVICE_ID, bobIdentKey, bobStorage, aliceDevices); Assert.IsTrue(omemoEncryptedMessage2.ENCRYPTED); // Decrypt Message 2: // Throws an exception in case something goes wrong: OmemoDecryptionContext decryptCtx2 = new OmemoDecryptionContext(ALICE_ADDRESS, aliceIdentKey, aliceSignedPreKey, alicePreKeys, false, aliceStorage); omemoEncryptedMessage2.decrypt(decryptCtx2); Assert.AreEqual(msg2, omemoEncryptedMessage2.MESSAGE); Assert.IsFalse(omemoEncryptedMessage2.IS_PURE_KEY_EXCHANGE_MESSAGE); Assert.IsFalse(omemoEncryptedMessage2.ENCRYPTED); // Encrypt Message 3: string msg3 = "Hello OMEMO 3"; OmemoEncryptedMessage omemoEncryptedMessage3 = new OmemoEncryptedMessage(ALICE_ADDRESS.BARE_JID, BOB_ADDRESS.BARE_JID, msg3, MessageMessage.TYPE_CHAT, false); bobDeviceGroup.SESSIONS[BOB_ADDRESS.DEVICE_ID] = aliceStorage.LoadSession(BOB_ADDRESS); omemoEncryptedMessage3.encrypt(ALICE_ADDRESS.DEVICE_ID, aliceIdentKey, aliceStorage, bobDevices); Assert.IsFalse(omemoEncryptedMessage3.IS_PURE_KEY_EXCHANGE_MESSAGE); Assert.IsTrue(omemoEncryptedMessage3.ENCRYPTED); Assert.IsNotNull(aliceStorage.LoadFingerprint(BOB_ADDRESS)); Assert.IsNotNull(aliceStorage.LoadSession(BOB_ADDRESS)); // Decrypt Message 3: // Throws an exception in case something goes wrong: OmemoDecryptionContext decryptCtx3 = new OmemoDecryptionContext(BOB_ADDRESS, bobIdentKey, bobSignedPreKey, bobPreKeys, false, bobStorage); omemoEncryptedMessage3.decrypt(decryptCtx3); Assert.AreEqual(msg3, omemoEncryptedMessage3.MESSAGE); Assert.IsFalse(omemoEncryptedMessage3.IS_PURE_KEY_EXCHANGE_MESSAGE); Assert.IsFalse(omemoEncryptedMessage3.ENCRYPTED); Assert.IsNotNull(bobStorage.LoadFingerprint(ALICE_ADDRESS)); Assert.IsNotNull(bobStorage.LoadSession(ALICE_ADDRESS)); }
private async Task SendChatMessageAsync(ChatModel chat, string message) { string fromFullJid; using (MainDbContext ctx = new MainDbContext()) { fromFullJid = ctx.Accounts.Where(a => string.Equals(a.bareJid, chat.accountBareJid)).Select(a => a.fullJid.FullJid()).FirstOrDefault(); } if (fromFullJid is null) { Logger.Error($"Failed to send message from background. Account '{chat.bareJid}' does not exist."); return; } string to = chat.bareJid; string chatType = chat.chatType == ChatType.CHAT ? MessageMessage.TYPE_CHAT : MessageMessage.TYPE_GROUPCHAT; bool reciptRequested = true; MessageMessage toSendMsg; if (chat.omemoInfo.enabled) { if (chat.chatType == ChatType.CHAT) { toSendMsg = new OmemoEncryptedMessage(fromFullJid, to, message, chatType, reciptRequested); } else { // ToDo: Add MUC OMEMO support throw new NotImplementedException("Sending encrypted messages for MUC is not supported right now!"); } } else if (chat.chatType == ChatType.CHAT) { toSendMsg = new MessageMessage(fromFullJid, to, message, chatType, reciptRequested); } else { toSendMsg = new MessageMessage(fromFullJid, to, message, chatType, chat.muc.nickname, reciptRequested); } // Create a copy for the DB: ChatMessageModel toSendMsgDB = new ChatMessageModel(toSendMsg, chat) { state = toSendMsg is OmemoEncryptedMessage ? MessageState.TO_ENCRYPT : MessageState.SENDING }; // Set the image path and file name: if (toSendMsgDB.isImage) { await DataCache.PrepareImageModelPathAndNameAsync(toSendMsgDB.image); } // Set the chat message id for later identification: toSendMsg.chatMessageId = toSendMsgDB.id; // Update chat last active: chat.lastActive = DateTime.Now; // Update DB: chat.Update(); DataCache.INSTANCE.AddChatMessage(toSendMsgDB, chat); Logger.Info("Added to send message in background"); if (isRunning) { Client client = ConnectionHandler.INSTANCE.GetClient(chat.bareJid).client; if (client is null) { Logger.Error($"Failed to send message from background. Client '{chat.bareJid}' does not exist."); } // Send the message: else if (toSendMsg is OmemoEncryptedMessage toSendOmemoMsg) { await client.xmppClient.sendOmemoMessageAsync(toSendOmemoMsg, chat.bareJid, client.dbAccount.bareJid, client.dbAccount.omemoInfo.trustedKeysOnly, chat.omemoInfo.trustedKeysOnly); } else { await client.xmppClient.SendAsync(toSendMsg); } } else { ToastHelper.ShowWillBeSendLaterToast(chat); } }