//--------------------------------------------------------Constructor:----------------------------------------------------------------\\ #region --Constructors-- /// <summary> /// Basic Constructor /// </summary> /// <history> /// 10/08/2018 Created [Fabian Sauter] /// </history> internal OmemoSessionBuildHelper(string chatJid, string bareAccountJid, string fullAccountJid, XMPPConnection2 connection, OmemoHelper omemoHelper) { this.CONNECTION = connection; this.CHAT_JID = chatJid; this.BARE_ACCOUNT_JID = bareAccountJid; this.FULL_ACCOUNT_JID = fullAccountJid; this.OMEMO_HELPER = omemoHelper; this.STATE = OmemoSessionBuildHelperState.NOT_STARTED; }
private async Task InitOmemoAsync() { state = SetupState.INITIALISING_OMEMO_KEYS; OmemoHelper omemoHelper = ccHandler.client.getOmemoHelper(); if (!(omemoHelper is null)) { await omemoHelper.initAsync(); }
//--------------------------------------------------------Constructor:----------------------------------------------------------------\\ #region --Constructors-- /// <summary> /// Basic Constructor /// </summary> /// <history> /// 10/08/2018 Created [Fabian Sauter] /// </history> internal OmemoSessionBuildHelper(string chatJid, string bareAccountJid, string fullAccountJid, XmppConnection connection, OmemoHelper omemoHelper) { CONNECTION = connection; CHAT_JID = chatJid; BARE_ACCOUNT_JID = bareAccountJid; FULL_ACCOUNT_JID = fullAccountJid; OMEMO_HELPER = omemoHelper; STATE = OmemoSessionBuildHelperState.NOT_STARTED; }
//--------------------------------------------------------Constructor:----------------------------------------------------------------\\ #region --Constructors-- internal OmemoSessionBuildHelper(string srcBareJid, string dstBareJid, XmppConnection connection, OmemoHelper omemoHelper, bool trustedSrcKeysOnly, bool trustedDstKeysOnly) { CONNECTION = connection; SRC_BARE_JID = srcBareJid; DST_BARE_JID = dstBareJid; OMEMO_HELPER = omemoHelper; TRUSTED_SRC_KEYS_ONLY = trustedSrcKeysOnly; TRUSTED_DST_KEYS_ONLY = trustedDstKeysOnly; STATE = OmemoSessionBuildHelperState.NOT_STARTED; }
//--------------------------------------------------------Constructor:----------------------------------------------------------------\\ #region --Constructors-- /// <summary> /// Basic Constructor /// </summary> /// <history> /// 10/08/2018 Created [Fabian Sauter] /// </history> internal OmemoSessionBuildHelper(string chatJid, string bareAccountJid, string fullAccountJid, Action <OmemoSessionBuildHelper, OmemoSessionBuildResult> onSessionResult, XMPPConnection2 connection, OmemoHelper omemoHelper) { this.CONNECTION = connection; this.ON_SESSION_RESULT = onSessionResult; this.CHAT_JID = chatJid; this.BARE_ACCOUNT_JID = bareAccountJid; this.FULL_ACCOUNT_JID = fullAccountJid; this.OMEMO_HELPER = omemoHelper; this.STATE = OmemoSessionBuildHelperState.NOT_STARTED; this.requestDeviceListHelper = null; this.requestBundleInfoHelper = null; this.SESSION = new OmemoSession(chatJid); this.curAddress = null; }
private async void C_NewChatMessage(XMPPClient client, XMPP_API.Classes.Network.Events.NewChatMessageEventArgs args) { MessageMessage msg = args.getMessage(); // Handel MUC room subject messages: if (msg is MUCRoomSubjectMessage) { MUCHandler.INSTANCE.onMUCRoomSubjectMessage(msg as MUCRoomSubjectMessage); return; } string from = Utils.getBareJidFromFullJid(msg.getFrom()); string to = Utils.getBareJidFromFullJid(msg.getTo()); string id; if (msg.CC_TYPE == CarbonCopyType.SENT) { id = ChatTable.generateId(to, from); } else { id = ChatTable.generateId(from, to); } // Check if device id is valid and if, decrypt the OMEMO messages: if (msg is OmemoMessageMessage omemoMessage) { OmemoHelper helper = client.getOmemoHelper(); if (helper is null) { C_OmemoSessionBuildError(client, new OmemoSessionBuildErrorEventArgs(id, OmemoSessionBuildError.KEY_ERROR, new List <OmemoMessageMessage> { omemoMessage })); Logger.Error("Failed to decrypt OMEMO message - OmemoHelper is null"); return; } else if (!client.getXMPPAccount().checkOmemoKeys()) { C_OmemoSessionBuildError(client, new OmemoSessionBuildErrorEventArgs(id, OmemoSessionBuildError.KEY_ERROR, new List <OmemoMessageMessage> { omemoMessage })); Logger.Error("Failed to decrypt OMEMO message - keys are corrupted"); return; } else if (!await omemoMessage.decryptAsync(client.getOmemoHelper(), client.getXMPPAccount().omemoDeviceId)) { return; } } ChatTable chat = ChatDBManager.INSTANCE.getChat(id); bool chatChanged = false; // Spam detection: if (Settings.getSettingBoolean(SettingsConsts.SPAM_DETECTION_ENABLED)) { if (Settings.getSettingBoolean(SettingsConsts.SPAM_DETECTION_FOR_ALL_CHAT_MESSAGES) || chat is null) { if (SpamDBManager.INSTANCE.isSpam(msg.MESSAGE)) { Logger.Warn("Received spam message from " + from); return; } } } if (chat is null) { chatChanged = true; chat = new ChatTable(from, to) { lastActive = msg.getDelay(), chatType = string.Equals(msg.TYPE, MessageMessage.TYPE_GROUPCHAT) ? ChatType.MUC : ChatType.CHAT }; } ChatMessageTable message = new ChatMessageTable(msg, chat); // Handle MUC invite messages: if (msg is DirectMUCInvitationMessage) { DirectMUCInvitationMessage inviteMessage = msg as DirectMUCInvitationMessage; bool doesRoomExist = ChatDBManager.INSTANCE.doesMUCExist(ChatTable.generateId(inviteMessage.ROOM_JID, msg.getTo())); bool doesOutstandingInviteExist = ChatDBManager.INSTANCE.doesOutstandingMUCInviteExist(id, inviteMessage.ROOM_JID); if (doesRoomExist && doesOutstandingInviteExist) { return; } MUCDirectInvitationTable inviteTable = new MUCDirectInvitationTable(inviteMessage, message.id); ChatDBManager.INSTANCE.setMUCDirectInvitation(inviteTable); } bool isMUCMessage = string.Equals(MessageMessage.TYPE_GROUPCHAT, message.type); ChatMessageTable existingMessage = ChatDBManager.INSTANCE.getChatMessageById(message.id); bool doesMessageExist = existingMessage != null; if (isMUCMessage) { MUCChatInfoTable mucInfo = MUCDBManager.INSTANCE.getMUCInfo(chat.id); if (mucInfo != null) { if (Equals(message.fromUser, mucInfo.nickname)) { // Filter MUC messages that already exist: // ToDo: Allow MUC messages being edited and detect it if (doesMessageExist) { return; } else { message.state = MessageState.SEND; } } else { if (doesMessageExist) { message.state = existingMessage.state; } } } } if (chat.lastActive.CompareTo(msg.getDelay()) < 0) { chatChanged = true; chat.lastActive = msg.getDelay(); } if (chatChanged) { ChatDBManager.INSTANCE.setChat(chat, false, true); } // Send XEP-0184 (Message Delivery Receipts) reply: if (msg.RECIPT_REQUESTED && id != null && !Settings.getSettingBoolean(SettingsConsts.DONT_SEND_CHAT_MESSAGE_RECEIVED_MARKERS)) { await Task.Run(async() => { DeliveryReceiptMessage receiptMessage = new DeliveryReceiptMessage(client.getXMPPAccount().getFullJid(), from, msg.ID); await client.sendAsync(receiptMessage); }); } ChatDBManager.INSTANCE.setChatMessage(message, !doesMessageExist, doesMessageExist && !isMUCMessage); // Show toast: if (!doesMessageExist && !chat.muted) { await Task.Run(() => { try { switch (msg.TYPE) { case MessageMessage.TYPE_GROUPCHAT: case MessageMessage.TYPE_CHAT: if (!message.isCC) { if (message.isImage) { ToastHelper.showChatTextImageToast(message, chat); } else { ToastHelper.showChatTextToast(message, chat); } } break; default: break; } } catch (Exception e) { Logger.Error("Failed to send toast notification!", e); } }); } }
/// <summary> /// Decrypts the content of BASE_64_PAYLOAD with the given SessionCipher and saves the result in MESSAGE. /// Sets ENCRYPTED to false. /// </summary> /// <param name="cipher">The SessionCipher for decrypting the content of BASE_64_PAYLOAD.</param> /// <param name="remoteAddress">The SignalProtocolAddress of the sender.</param> /// <param name="localeDeciceId">The local device id.</param> /// <param name="helper">The current OmemoHelper object of the current account. If null, won't remove used PreKey.</param> /// <returns>True on success.</returns> public async Task <bool> decryptAsync(SessionCipher cipher, SignalProtocolAddress remoteAddress, uint localeDeciceId, OmemoHelper helper) { try { // 1. Check if the message contains a key for the local device: OmemoKey key = getOmemoKey(localeDeciceId); if (key is null) { Logger.Info("Discarded received OMEMO message - doesn't contain device id!"); return(false); } // 2. Load the cipher: SignalProtocolAddress address = new SignalProtocolAddress(Utils.getBareJidFromFullJid(FROM), SOURCE_DEVICE_ID); byte[] encryptedKeyAuthTag = Convert.FromBase64String(key.BASE_64_KEY); byte[] decryptedKeyAuthTag = null; if (key.IS_PRE_KEY) { PreKeySignalMessage preKeySignalMessage = new PreKeySignalMessage(encryptedKeyAuthTag); decryptedKeyAuthTag = cipher.decrypt(preKeySignalMessage); if (!(helper is null)) { May <uint> preKey = preKeySignalMessage.getPreKeyId(); if (preKey.HasValue) { Logger.Info("Removing used PreKey."); await helper.removePreKeyAndRepublishAsync(preKey.ForceGetValue()); } else { Logger.Error("Failed to get value from PreKeySignalMessage."); } } } else { decryptedKeyAuthTag = cipher.decrypt(new SignalMessage(encryptedKeyAuthTag)); } // 3. Check if the cipher got loaded successfully: if (decryptedKeyAuthTag is null) { Logger.Info("Discarded received OMEMO message - failed to decrypt keyAuthTag is null!"); return(false); } // 4. Decrypt the payload: byte[] aesIv = Convert.FromBase64String(BASE_64_IV); byte[] aesKey = new byte[16]; byte[] aesAuthTag = new byte[decryptedKeyAuthTag.Length - aesKey.Length]; Buffer.BlockCopy(decryptedKeyAuthTag, 0, aesKey, 0, aesKey.Length); Buffer.BlockCopy(decryptedKeyAuthTag, aesKey.Length, aesAuthTag, 0, aesAuthTag.Length); Aes128GcmCpp aes128Gcm = new Aes128GcmCpp() { key = aesKey, authTag = aesAuthTag, iv = aesIv }; byte[] encryptedData = Convert.FromBase64String(BASE_64_PAYLOAD); byte[] decryptedData = aes128Gcm.decrypt(encryptedData); // 5. Convert decrypted data to Unicode string: MESSAGE = Encoding.UTF8.GetString(decryptedData); ENCRYPTED = false; return(true); } catch (Exception e) { Logger.Info("Discarded received OMEMO message - failed to decrypt with: " + e.Message); } return(false); }
/// <summary> /// Decrypts the content of BASE_64_PAYLOAD. Loads the SessionCipher from the given OmemoHelper object and saves the result in MESSAGE. /// Sets ENCRYPTED to false. /// </summary> /// <param name="helper">The current OmemoHelper object of the current account.</param> /// <param name="localeDeciceId">The local device id.</param> /// <returns>True on success.</returns> public async Task <bool> decryptAsync(OmemoHelper helper, uint localeDeciceId) { SignalProtocolAddress remoteAddress = new SignalProtocolAddress(Utils.getBareJidFromFullJid(FROM), SOURCE_DEVICE_ID); return(await decryptAsync(helper.loadCipher(remoteAddress), remoteAddress, localeDeciceId, helper)); }
public async Task HandleNewChatMessageAsync(MessageMessage msg) { // Handel MUC room subject messages: if (msg is MUCRoomSubjectMessage) { MucHandler.INSTANCE.OnMUCRoomSubjectMessage(msg as MUCRoomSubjectMessage); return; } string from = Utils.getBareJidFromFullJid(msg.getFrom()); string to = Utils.getBareJidFromFullJid(msg.getTo()); string chatBareJid = string.Equals(from, client.dbAccount.bareJid) ? to : from; SemaLock semaLock = DataCache.INSTANCE.NewChatSemaLock(); ChatModel chat = DataCache.INSTANCE.GetChat(client.dbAccount.bareJid, chatBareJid, semaLock); bool newChat = chat is null; bool chatChanged = false; // Spam detection: if (Settings.GetSettingBoolean(SettingsConsts.SPAM_DETECTION_ENABLED)) { if (Settings.GetSettingBoolean(SettingsConsts.SPAM_DETECTION_FOR_ALL_CHAT_MESSAGES) || newChat) { if (SpamHelper.INSTANCE.IsSpam(msg.MESSAGE)) { Logger.Warn("Received spam message from " + chatBareJid); return; } } } // Detect invalid chat messages: if (!string.Equals(msg.TYPE, MessageMessage.TYPE_CHAT) && !string.Equals(msg.TYPE, MessageMessage.TYPE_ERROR) && !string.Equals(msg.TYPE, MessageMessage.TYPE_GROUPCHAT)) { Logger.Warn($"Received an unknown message type ('{msg.TYPE}') form '{chatBareJid}'. Dropping it."); return; } // Add the new chat to the DB since it's expected to be there by for example our OMEMO encryption: if (newChat) { chat = new ChatModel(chatBareJid, client.dbAccount) { lastActive = msg.delay, chatType = string.Equals(msg.TYPE, MessageMessage.TYPE_GROUPCHAT) ? ChatType.MUC : ChatType.CHAT, isChatActive = false // Mark chat as inactive until we can be sure, it's a valid message }; DataCache.INSTANCE.AddChatUnsafe(chat, client); } else { // Mark chat as active: chat.isChatActive = true; chatChanged = true; } semaLock.Dispose(); // Check if device id is valid and if, decrypt the OMEMO messages: if (msg is OmemoEncryptedMessage omemoMessage) { OmemoHelper helper = client.xmppClient.getOmemoHelper(); if (helper is null) { OnOmemoSessionBuildError(client.xmppClient, new OmemoSessionBuildErrorEventArgs(chatBareJid, OmemoSessionBuildError.KEY_ERROR, new List <OmemoEncryptedMessage> { omemoMessage })); Logger.Error("Failed to decrypt OMEMO message - OmemoHelper is null"); return; } else if (!await DecryptOmemoEncryptedMessageAsync(omemoMessage, !newChat && chat.omemoInfo.trustedKeysOnly)) { if (newChat) { // We failed to decrypt, so this chat could be spam. Delete it again... DataCache.INSTANCE.DeleteChat(chat, false, false); Logger.Debug($"Deleting chat '{chat.bareJid}' again, since decrypting the initial OMEMO message failed."); } return; } else if (omemoMessage.IS_PURE_KEY_EXCHANGE_MESSAGE) { return; } } // Valid new chat, so we can change it to active now: chat.isChatActive = true; chatChanged = true; ChatMessageModel message = null; if (!newChat) { message = DataCache.INSTANCE.GetChatMessage(chat.id, msg.ID); } // Filter messages that already exist: // ToDo: Allow MUC messages being edited and detect it if (!(message is null)) { Logger.Debug("Duplicate message received from '" + chatBareJid + "'. Dropping it..."); return; } message = new ChatMessageModel(msg, chat); // Set the image path and file name: if (message.isImage) { await DataCache.PrepareImageModelPathAndNameAsync(message.image); } DataCache.INSTANCE.AddChatMessage(message, chat); // Handle MUC invite messages: if (msg is DirectMUCInvitationMessage inviteMessage) { if (!newChat) { Logger.Info("Ignoring received MUC direct invitation form '" + chatBareJid + "' since we already joined this MUC (" + inviteMessage.ROOM_JID + ")."); return; } // Ensure we add the message to the DB before we add the invite since the invite has the message as a foreign key: using (MainDbContext ctx = new MainDbContext()) { ctx.Add(new MucDirectInvitationModel(inviteMessage, message)); } } bool isMUCMessage = string.Equals(MessageMessage.TYPE_GROUPCHAT, message.type); if (chat.lastActive.CompareTo(msg.delay) < 0) { chatChanged = true; chat.lastActive = msg.delay; } // Send XEP-0184 (Message Delivery Receipts) reply: if (msg.RECIPT_REQUESTED && !Settings.GetSettingBoolean(SettingsConsts.DONT_SEND_CHAT_MESSAGE_RECEIVED_MARKERS)) { await Task.Run(async() => { DeliveryReceiptMessage receiptMessage = new DeliveryReceiptMessage(client.dbAccount.fullJid.FullJid(), from, msg.ID); await client.xmppClient.SendAsync(receiptMessage); }); } if (chatChanged) { chat.Update(); chatChanged = false; } // Show toast: if (!chat.muted) { await Task.Run(() => { try { switch (msg.TYPE) { case MessageMessage.TYPE_GROUPCHAT: case MessageMessage.TYPE_CHAT: if (!message.isCC) { // Create toast: if (message.isImage) { ToastHelper.ShowChatTextImageToast(message, chat); } else { ToastHelper.ShowChatTextToast(message, chat); } // Update badge notification count: ToastHelper.UpdateBadgeNumber(); } break; default: break; } } catch (Exception e) { Logger.Error("Failed to send toast notification!", e); } }); } }
/// <summary> /// Decrypts the content of BASE_64_PAYLOAD with the given SessionCipher and saves the result in MESSAGE. /// Sets ENCRYPTED to false. /// </summary> /// <param name="cipher">The SessionCipher for decrypting the content of BASE_64_PAYLOAD.</param> public bool decrypt(OmemoHelper omemoHelper, uint localOmemoDeviceId) { try { // 1. Check if the message contains a key for the local device: OmemoKey key = getOmemoKey(localOmemoDeviceId); if (key == null) { Logger.Info("Discarded received OMEMO message - doesn't contain device id!"); return(false); } // 2. Load the cipher: SignalProtocolAddress address = new SignalProtocolAddress(Utils.getBareJidFromFullJid(FROM), SOURCE_DEVICE_ID); SessionCipher cipher = omemoHelper.loadCipher(address); byte[] encryptedKeyAuthTag = Convert.FromBase64String(key.BASE_64_KEY); byte[] decryptedKeyAuthTag = null; if (key.IS_PRE_KEY) { decryptedKeyAuthTag = cipher.decrypt(new PreKeySignalMessage(encryptedKeyAuthTag)); // ToDo republish the bundle info and remove used pre key } else { decryptedKeyAuthTag = cipher.decrypt(new SignalMessage(encryptedKeyAuthTag)); } // 3. Check if the cipher got loaded successfully: if (decryptedKeyAuthTag == null) { Logger.Info("Discarded received OMEMO message - failed to decrypt keyAuthTag is null!"); return(false); } // 4. Decrypt the payload: byte[] aesIv = Convert.FromBase64String(BASE_64_IV); byte[] aesKey = new byte[16]; byte[] aesAuthTag = new byte[decryptedKeyAuthTag.Length - aesKey.Length]; Buffer.BlockCopy(decryptedKeyAuthTag, 0, aesKey, 0, aesKey.Length); Buffer.BlockCopy(decryptedKeyAuthTag, aesKey.Length, aesAuthTag, 0, aesAuthTag.Length); Aes128Gcm aes = new Aes128Gcm() { key = aesKey, authTag = aesAuthTag, iv = aesIv }; byte[] encryptedData = Convert.FromBase64String(BASE_64_PAYLOAD); byte[] decryptedData = aes.decrypt(encryptedData); // 5. Convert decrypted data to Unicode string: MESSAGE = Encoding.Unicode.GetString(decryptedData); ENCRYPTED = false; return(true); } catch (Exception e) { Logger.Info("Discarded received OMEMO message - failed to decrypt with:" + e.Message); } return(false); }