Exemple #1
0
        /// <summary>
        /// Encrypts the given <paramref name="keyHmac"/> combination for all given devices and returns the resulting messages.
        /// Uses the given <see cref="OmemoSessionModel"/> for encrypting.
        /// </summary>
        /// <param name="keyHmac">The key HMAC combination, that should be encrypted.</param>
        /// <param name="devices">A collection of devices, we should encrypt the message for.</param>
        /// <returns>A collection of encrypted messages for each device grouped by their bare JID (List[Tuple[bareJid, List[Tuple[deviceId, IOmemoMessage]]]]).</returns>
        public List <Tuple <string, List <Tuple <uint, IOmemoMessage> > > > EncryptKeyHmacForDevices(byte[] keyHmac, List <OmemoDeviceGroup> devices)
        {
            List <Tuple <string, List <Tuple <uint, IOmemoMessage> > > > msgs = new List <Tuple <string, List <Tuple <uint, IOmemoMessage> > > >();

            foreach (OmemoDeviceGroup group in devices)
            {
                List <Tuple <uint, IOmemoMessage> > groupMsgs = new List <Tuple <uint, IOmemoMessage> >();
                foreach (KeyValuePair <uint, OmemoSessionModel> device in group.SESSIONS)
                {
                    try
                    {
                        OmemoSessionModel         session = device.Value;
                        OmemoAuthenticatedMessage authMsg = EncryptKeyHmacForDevices(keyHmac, device.Value, session.assData);

                        // To account for lost and out-of-order messages during the key exchange, OmemoKeyExchange structures are sent until a response by the recipient confirms that the key exchange was successfully completed.
                        if (session.nS == 0 || session.nR == 0)
                        {
                            OmemoKeyExchangeMessage kexMsg = new OmemoKeyExchangeMessage(session.preKeyId, session.signedPreKeyId, OWN_IDENTITY_KEY.pubKey, session.ek, authMsg);
                            groupMsgs.Add(new Tuple <uint, IOmemoMessage>(device.Key, kexMsg));
                        }
                        else
                        {
                            groupMsgs.Add(new Tuple <uint, IOmemoMessage>(device.Key, authMsg));
                        }
                    }
                    catch (OmemoException e)
                    {
                        Logger.Error("Failed to encrypt message for device " + group.ToString() + " with: " + e.ToString());
                    }
                }
                msgs.Add(new Tuple <string, List <Tuple <uint, IOmemoMessage> > >(group.BARE_JID, groupMsgs));
            }
            return(msgs);
        }
Exemple #2
0
        /// <summary>
        /// Decrypts the key and HMAC concatenation (<see cref="OmemoMessage.cipherText"/>) with the given <paramref name="session"/>.
        /// </summary>
        /// <param name="authMsg">The <see cref="OmemoAuthenticatedMessage"/> containing the key and HMAC.</param>
        /// <param name="session">The <see cref="OmemoSessionModel"/> that should be used for decryption.</param>
        /// <returns>key || HMAC</returns>
        public byte[] DecryptKeyHmacForDevice(OmemoAuthenticatedMessage authMsg, OmemoSessionModel session)
        {
            OmemoMessage msg = new OmemoMessage(authMsg.OMEMO_MESSAGE);

            byte[] plainText = TrySkippedMessageKeys(msg, authMsg.HMAC, session);
            if (!(plainText is null))
            {
                return(plainText);
            }

            if (session.dhR is null || !msg.DH.Equals(session.dhR))
            {
                SkipMessageKeys(session, msg.PN);
                session.InitDhRatchet(msg);
            }
            SkipMessageKeys(session, msg.N);

            byte[] mk = LibSignalUtils.KDF_CK(session.ckR, 0x01);
            Logger.Trace("[" + nameof(DecryptKeyHmacForDevice) + "] " + nameof(session.ckR) + ": " + CryptoUtils.ToHexString(session.ckR));
            session.ckR = LibSignalUtils.KDF_CK(session.ckR, 0x02);
            ++session.nR;
            byte[] result = DecryptKeyHmacForDevice(mk, msg, authMsg.HMAC, session.assData);

            // Update the session state:
            if (session.state == SessionState.SEND)
            {
                session.state = SessionState.READY;
            }
            Logger.Trace("[" + nameof(DecryptKeyHmacForDevice) + "] " + nameof(session.state) + ": " + session.state);

            return(result);
        }
Exemple #3
0
        /// <summary>
        /// Decrypts the key and HMAC concatenation (<see cref="OmemoMessage.cipherText"/>) with the given <paramref name="session"/>.
        /// </summary>
        /// <param name="authMsg">The <see cref="OmemoAuthenticatedMessage"/> containing the key and HMAC.</param>
        /// <param name="session">The <see cref="OmemoSessionModel"/> that should be used for decryption.</param>
        /// <returns>key || HMAC</returns>
        private byte[] DecryptKeyHmacForDevice(OmemoAuthenticatedMessage authMsg, OmemoSessionModel session)
        {
            OmemoMessage msg = new OmemoMessage(authMsg.OMEMO_MESSAGE);

            byte[] plainText = TrySkippedMessageKeys(msg, authMsg.HMAC, session);
            if (!(plainText is null))
            {
                return(plainText);
            }

            if (session.dhR is null || !msg.DH.Equals(session.dhR.pubKey))
            {
                SkipMessageKeys(session, msg.PN);
                session.InitDhRatchet(msg);
            }
            SkipMessageKeys(session, msg.N);

            // If no receive chain has been initialized yet, initialize it now.
            // This happens in case we received a key exchange message with a new session and now would like to send our first message.
            if (session.ckR is null)
            {
                session.InitReceiverKeyChain();
            }

            byte[] mk = LibSignalUtils.KDF_CK(session.ckR, 0x01);
            session.ckR = LibSignalUtils.KDF_CK(session.ckR, 0x02);
            ++session.nR;
            return(DecryptKeyHmacForDevice(mk, msg, authMsg.HMAC, session.assData));
        }
Exemple #4
0
 /// <summary>
 /// Skippes receiving message keys <paramref name="until"/>.
 /// </summary>
 /// <param name="session">The <see cref="OmemoSessionModel"/> the keys should be skipped for.</param>
 /// <param name="until">Until which key should be skipped.</param>
 private void SkipMessageKeys(OmemoSessionModel session, uint until)
 {
     if (session.nR + OmemoSessionModel.MAX_SKIP < until)
     {
         throw new OmemoException("Failed to decrypt. Would skip to many message keys from " + session.nR + " to " + until + ", which is more than " + OmemoSessionModel.MAX_SKIP + '.');
     }
     if (!(session.ckR is null))
     {
         while (session.nR < until)
         {
             byte[] mk = LibSignalUtils.KDF_CK(session.ckR, 0x01);
             session.ckR = LibSignalUtils.KDF_CK(session.ckR, 0x02);
             session.MK_SKIPPED.SetMessageKey(session.dhR.pubKey, session.nR, mk);
             ++session.nR;
         }
     }
 }
Exemple #5
0
        /// <summary>
        /// Encrypts the given key and HMAC concatenation and returns the result.
        /// </summary>
        /// <param name="keyHmac">The key, HMAC concatenation result.</param>
        /// <param name="session">The <see cref="OmemoSessionModel"/> between the sender and receiver.</param>
        /// <param name="assData">Encode(IK_A) || Encode(IK_B) => Concatenation of Alices and Bobs public part of their identity key.</param>
        private OmemoAuthenticatedMessage EncryptKeyHmacForDevices(byte[] keyHmac, OmemoSessionModel session, byte[] assData)
        {
            byte[] mk = LibSignalUtils.KDF_CK(session.ckS, 0x01);
            session.ckS = LibSignalUtils.KDF_CK(session.ckS, 0x02);
            OmemoMessage omemoMessage = new OmemoMessage(session);

            ++session.nS;
            // 32 byte (256 bit) of salt. Initialized with 0s.
            byte[] hkdfOutput = CryptoUtils.HkdfSha256(mk, new byte[32], "OMEMO Message Key Material", 80);
            CryptoUtils.SplitKey(hkdfOutput, out byte[] encKey, out byte[] authKey, out byte[] iv);
            omemoMessage.cipherText = CryptoUtils.Aes256CbcEncrypt(encKey, iv, keyHmac);
            byte[] omemoMessageBytes = omemoMessage.ToByteArray();
            byte[] hmacInput         = CryptoUtils.Concat(assData, omemoMessageBytes);
            byte[] hmacResult        = CryptoUtils.HmacSha256(authKey, hmacInput);
            byte[] hmacTruncated     = CryptoUtils.Truncate(hmacResult, 16);
            return(new OmemoAuthenticatedMessage(hmacTruncated, omemoMessageBytes));
        }
Exemple #6
0
        /// <summary>
        /// Tries to decrypt the given <paramref name="cipherContent"/>.
        /// </summary>
        /// <param name="authMsg">The <see cref="OmemoAuthenticatedMessage"/> that should be used for decrypting the <paramref name="cipherContent"/>.</param>
        /// <param name="session">The <see cref="OmemoSessionModel"/> that should be used for decryption.</param>
        /// <param name="cipherContent">The cipher text that should be decrypted.</param>
        /// <returns>On success the plain text for the given <paramref name="cipherContent"/>.</returns>
        public byte[] DecryptMessage(OmemoAuthenticatedMessage authMsg, OmemoSessionModel session, byte[] cipherContent)
        {
            byte[] keyHmac = DecryptKeyHmacForDevice(authMsg, session);
            byte[] key     = new byte[32];
            Buffer.BlockCopy(keyHmac, 0, key, 0, key.Length);
            byte[] hmacRef = new byte[16];
            Buffer.BlockCopy(keyHmac, key.Length, hmacRef, 0, hmacRef.Length);

            // 32 byte (256 bit) of salt. Initialized with 0s.
            byte[] hkdfOutput = CryptoUtils.HkdfSha256(key, new byte[32], "OMEMO Payload", 80);
            CryptoUtils.SplitKey(hkdfOutput, out byte[] encKey, out byte[] authKey, out byte[] iv);
            byte[] hmac = CryptoUtils.HmacSha256(authKey, cipherContent);
            hmac = CryptoUtils.Truncate(hmac, 16);
            if (!hmacRef.SequenceEqual(hmac))
            {
                throw new OmemoException("Failed to decrypt. HMAC does not match.");
            }
            return(CryptoUtils.Aes256CbcDecrypt(encKey, iv, cipherContent));
        }
Exemple #7
0
        /// <summary>
        /// Skippes receiving message keys <paramref name="until"/>.
        /// </summary>
        /// <param name="session">The <see cref="OmemoSessionModel"/> the keys should be skipped for.</param>
        /// <param name="until">Until which key should be skipped.</param>
        private void SkipMessageKeys(OmemoSessionModel session, uint until)
        {
            if (session.nR + OmemoSessionModel.MAX_SKIP < until)
            {
                throw new OmemoException("Failed to decrypt. Would skip to many message keys from " + session.nR + " to " + until + ", which is more than " + OmemoSessionModel.MAX_SKIP + '.');
            }

            if (!(session.ckR is null))
            {
                while (session.nR < until)
                {
                    byte[] mk = LibSignalUtils.KDF_CK(session.ckR, 0x01);
                    Logger.Trace($"[{nameof(SkipMessageKeys)}] {nameof(mk)} - {session.nR}: {CryptoUtils.ToHexString(mk)}");
                    session.ckR = LibSignalUtils.KDF_CK(session.ckR, 0x02);
                    session.MK_SKIPPED.SetMessageKey(session.dhR, session.nR, mk);
                    ++session.nR;
                }
            }
        }
Exemple #8
0
        /// <summary>
        /// Encrypts the given key and HMAC concatenation and returns the result.
        /// </summary>
        /// <param name="keyHmac">The key, HMAC concatenation result.</param>
        /// <param name="session">The <see cref="OmemoSessionModel"/> between the sender and receiver.</param>
        /// <param name="assData">Encode(IK_A) || Encode(IK_B) => Concatenation of Alices and Bobs public part of their identity key.</param>
        private OmemoAuthenticatedMessage EncryptKeyHmacForDevices(byte[] keyHmac, OmemoSessionModel session, byte[] assData)
        {
            byte[] mk = LibSignalUtils.KDF_CK(session.ckS, 0x01);
            Logger.Trace("[" + nameof(EncryptKeyHmacForDevices) + "] " + nameof(session.ckS) + ": " + CryptoUtils.ToHexString(session.ckS));
            session.ckS = LibSignalUtils.KDF_CK(session.ckS, 0x02);
            OmemoMessage omemoMessage = new OmemoMessage(session);

            ++session.nS;

            // 32 byte (256 bit) of salt. Initialized with 0s.
            byte[] hkdfOutput = CryptoUtils.HkdfSha256(mk, new byte[32], "OMEMO Message Key Material", 80);
            CryptoUtils.SplitKey(hkdfOutput, out byte[] encKey, out byte[] authKey, out byte[] iv);
            omemoMessage.cipherText = CryptoUtils.Aes256CbcEncrypt(encKey, iv, keyHmac);
            byte[] omemoMessageBytes = omemoMessage.ToByteArray();
            byte[] hmacInput         = CryptoUtils.Concat(assData, omemoMessageBytes);
            byte[] hmacResult        = CryptoUtils.HmacSha256(authKey, hmacInput);
            byte[] hmacTruncated     = CryptoUtils.Truncate(hmacResult, 16);

            // Update the session state:
            if (session.state == SessionState.RECEIVED)
            {
                session.state = SessionState.READY;
            }

            // Debug trace output:
            Logger.Trace("[" + nameof(EncryptKeyHmacForDevices) + "] " + nameof(mk) + ": " + CryptoUtils.ToHexString(mk));
            Logger.Trace("[" + nameof(EncryptKeyHmacForDevices) + "] " + nameof(assData) + ": " + CryptoUtils.ToHexString(assData));
            Logger.Trace("[" + nameof(EncryptKeyHmacForDevices) + "] " + nameof(hkdfOutput) + ": " + CryptoUtils.ToHexString(hkdfOutput));
            Logger.Trace("[" + nameof(EncryptKeyHmacForDevices) + "] " + nameof(encKey) + ": " + CryptoUtils.ToHexString(encKey));
            Logger.Trace("[" + nameof(EncryptKeyHmacForDevices) + "] " + nameof(authKey) + ": " + CryptoUtils.ToHexString(authKey));
            Logger.Trace("[" + nameof(EncryptKeyHmacForDevices) + "] " + nameof(iv) + ": " + CryptoUtils.ToHexString(iv));
            Logger.Trace("[" + nameof(EncryptKeyHmacForDevices) + "] " + nameof(hmacInput) + ": " + CryptoUtils.ToHexString(hmacInput));
            Logger.Trace("[" + nameof(EncryptKeyHmacForDevices) + "] " + nameof(hmacResult) + ": " + CryptoUtils.ToHexString(hmacResult));
            Logger.Trace("[" + nameof(EncryptKeyHmacForDevices) + "] " + nameof(hmacTruncated) + ": " + CryptoUtils.ToHexString(hmacTruncated));
            Logger.Trace("[" + nameof(EncryptKeyHmacForDevices) + "] " + nameof(session.state) + ": " + session.state);

            return(new OmemoAuthenticatedMessage(hmacTruncated, omemoMessageBytes));
        }
Exemple #9
0
 /// <summary>
 /// Tries a skipped message key for decrypting the key and HMAC concatenation (<see cref="OmemoMessage.cipherText"/>).
 /// </summary>
 /// <param name="msg">The <see cref="OmemoMessage"/> containing the key and HMAC concatenation (<see cref="OmemoMessage.cipherText"/>).</param>
 /// <param name="msgHmac">The HMAC of the <paramref name="msg"/>.</param>
 /// <param name="session">The <see cref="OmemoSessionModel"/> that should be used for decryption.</param>
 /// <returns>key || HMAC on success, else null.</returns>
 private byte[] TrySkippedMessageKeys(OmemoMessage msg, byte[] msgHmac, OmemoSessionModel session)
 {
     byte[] mk = session.MK_SKIPPED.GetMessagekey(msg.DH, msg.N);
     return(!(mk is null) ? DecryptKeyHmacForDevice(mk, msg, msgHmac, session.assData) : null);
 }