Beispiel #1
0
 //--------------------------------------------------------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;
 }
Beispiel #4
0
 //--------------------------------------------------------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;
 }
Beispiel #5
0
 //--------------------------------------------------------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);
                    }
                });
            }
        }
Beispiel #7
0
        /// <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);
        }
Beispiel #8
0
        /// <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));
        }
Beispiel #9
0
        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);
                    }
                });
            }
        }
Beispiel #10
0
        /// <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);
        }