Пример #1
0
        private byte[] SendInitializationRequest(State state)
        {
            // message format:
            // nonce(16), pubkey(32), ecdh(32), padding(...), signature(64), mac(12)

            if (!(state is ClientState clientState))
            {
                throw new InvalidOperationException("Only the client can send init request.");
            }

            // 16 bytes nonce
            clientState.InitializationNonce = RandomNumberGenerator.Generate(InitializationNonceSize);

            // get the public key
            var pubkey = Signature.GetPublicKey();

            // generate new ECDH keypair for init message and root key
            IKeyAgreement clientEcdh = KeyAgreementFactory.GenerateNew();

            clientState.LocalEcdhForInit = clientEcdh;

            // nonce(16), <pubkey(32), ecdh(32), signature(64)>, mac(12)
            var initializationMessageSize = InitializationNonceSize + EcNumSize * 4 + MacSize;
            var messageSize = Math.Max(Configuration.MinimumMessageSize, initializationMessageSize);
            var initializationMessageSizeWithSignature    = messageSize - MacSize;
            var initializationMessageSizeWithoutSignature = messageSize - MacSize - SignatureSize;
            var signatureOffset = messageSize - MacSize - SignatureSize;
            var message         = new byte[messageSize];

            Array.Copy(clientState.InitializationNonce, 0, message, 0, InitializationNonceSize);
            Array.Copy(pubkey, 0, message, InitializationNonceSize, EcNumSize);
            Array.Copy(clientEcdh.GetPublicKey(), 0, message, InitializationNonceSize + EcNumSize, EcNumSize);

            // sign the message
            var digest = Digest.ComputeDigest(message, 0, initializationMessageSizeWithoutSignature);

            Array.Copy(Signature.Sign(digest), 0, message, signatureOffset, SignatureSize);

            // encrypt the message with the application key
            var cipher           = new AesCtrMode(AesFactory.GetAes(true, Configuration.ApplicationKey), clientState.InitializationNonce);
            var encryptedPayload = cipher.Process(message, InitializationNonceSize, initializationMessageSizeWithSignature - InitializationNonceSize);

            Array.Copy(encryptedPayload, 0, message, InitializationNonceSize, initializationMessageSizeWithSignature - InitializationNonceSize);

            // calculate mac
            var Mac = new Poly(AesFactory);

            Mac.Init(Configuration.ApplicationKey, clientState.InitializationNonce, MacSize);
            Mac.Process(message, 0, initializationMessageSizeWithSignature);
            var mac = Mac.Compute();

            Array.Copy(mac, 0, message, initializationMessageSizeWithSignature, MacSize);
            return(message);
        }
Пример #2
0
        private void ReceiveFirstMessage(State state, byte[] payload, EcdhRatchetStep ecdhRatchetStep)
        {
            if (!(state is ServerState serverState))
            {
                throw new InvalidOperationException("Only the server can receive the first client message.");
            }
            if (serverState.FirstReceiveHeaderKey == null)
            {
                throw new InvalidOperationException("Invalid message received");
            }

            // check the first receive header key mac
            var payloadExceptMac = new ArraySegment <byte>(payload, 0, payload.Length - MacSize);
            var maciv            = new ArraySegment <byte>(payload, 0, 16);
            var mac = new ArraySegment <byte>(payload, payload.Length - MacSize, MacSize);
            var Mac = new Poly(AesFactory);

            Mac.Init(serverState.FirstReceiveHeaderKey, maciv, MacSize);
            Mac.Process(payloadExceptMac);
            var compareMac = Mac.Compute();

            if (!mac.Matches(compareMac))
            {
                throw new InvalidOperationException("The first received message does not have the correct MAC");
            }

            var data = DeconstructMessage(state, payload, serverState.FirstReceiveHeaderKey, ecdhRatchetStep, false);

            if (data.Length < InitializationNonceSize || !serverState.NextInitializationNonce.Matches(data, 0, InitializationNonceSize))
            {
                throw new InvalidOperationException("The first received message did not contain the correct payload");
            }

            serverState.FirstSendHeaderKey    = null;
            serverState.FirstReceiveHeaderKey = null;
            serverState.LocalEcdhRatchetStep0 = null;
            serverState.LocalEcdhRatchetStep1 = null;
            serverState.RootKey = null;
        }
Пример #3
0
        private static int MatchMessageWithMac(ArraySegment <byte> message, IAesFactory aesFactory, params byte[][] keys)
        {
            var messageSize      = message.Count;
            var mac              = new ArraySegment <byte>(message.Array, message.Offset + messageSize - MacSize, MacSize);
            var maciv            = new ArraySegment <byte>(message.Array, message.Offset, 16);
            var payloadExceptMac = new ArraySegment <byte>(message.Array, message.Offset, messageSize - MacSize);

            if (messageSize < MinimumMessageSize)
            {
                return(-1);
            }

            for (int i = 0; i < keys.Length; i++)
            {
                var key = keys[i];
                if (key == null)
                {
                    continue;
                }
                if (key.Length != 32)
                {
                    throw new ArgumentException("Each key must be 32 bytes", nameof(keys));
                }

                var Mac = new Poly(aesFactory);
                Mac.Init(key, maciv, MacSize);
                Mac.Process(payloadExceptMac);
                var compareMac = Mac.Compute();
                if (mac.Matches(compareMac))
                {
                    return(i);
                }
            }

            return(-1);
        }
Пример #4
0
        private (byte[] headerKeyUsed, EcdhRatchetStep ratchetUsed, bool usedNextHeaderKey, bool usedApplicationKey) InterpretMessageMAC(State state, byte[] payload, byte[] overrideHeaderKey = null)
        {
            // get some basic parts
            var messageSize      = payload.Length;
            var payloadExceptMac = new ArraySegment <byte>(payload, 0, messageSize - MacSize);
            var mac = new ArraySegment <byte>(payload, messageSize - MacSize, MacSize);

            // find the header key by checking the mac
            var maciv = new byte[16];

            Array.Copy(payload, maciv, 16);
            var Mac = new Poly(AesFactory);
            var usedNextHeaderKey        = false;
            var usedApplicationHeaderKey = false;

            byte[]          headerKey   = null;
            EcdhRatchetStep ratchetUsed = null;

            if (overrideHeaderKey != null)
            {
                headerKey = overrideHeaderKey;
                Mac.Init(headerKey, maciv, MacSize);
                Mac.Process(payloadExceptMac);
                var compareMac = Mac.Compute();
                if (!mac.Matches(compareMac))
                {
                    throw new InvalidOperationException("Could not decrypt the incoming message with given header key");
                }
            }
            else
            {
                // if we are initialized check the mac using ratchet receive header keys.
                if (state.Ratchets != null && !state.Ratchets.IsEmpty)
                {
                    foreach (EcdhRatchetStep ratchet in state.Ratchets.Enumerate())
                    {
                        headerKey = ratchet.ReceiveHeaderKey;
                        Mac.Init(headerKey, maciv, MacSize);
                        Mac.Process(payloadExceptMac);
                        var compareMac = Mac.Compute();
                        if (mac.Matches(compareMac))
                        {
                            ratchetUsed = ratchet;
                            break;
                        }
                        else if (ratchet.NextReceiveHeaderKey != null)
                        {
                            headerKey = ratchet.NextReceiveHeaderKey;
                            Mac.Init(headerKey, maciv, MacSize);
                            Mac.Process(payloadExceptMac);
                            compareMac = Mac.Compute();
                            if (mac.Matches(compareMac))
                            {
                                usedNextHeaderKey = true;
                                ratchetUsed       = ratchet;
                                break;
                            }
                        }
                    }
                }

                if (ratchetUsed == null)
                {
                    // we're either not initialized or this is an initialization message.
                    // To determine that we mac using the application key
                    headerKey = Configuration.ApplicationKey;
                    Mac.Init(headerKey, maciv, MacSize);
                    Mac.Process(payloadExceptMac);
                    var compareMac = Mac.Compute();
                    if (mac.Matches(compareMac))
                    {
                        usedApplicationHeaderKey = true;
                    }
                    else
                    {
                        headerKey = null;
                    }
                }
            }

            return(headerKey, ratchetUsed, usedNextHeaderKey, usedApplicationHeaderKey);
        }
Пример #5
0
        private byte[] ConstructMessage(ArraySegment <byte> message, bool includeEcdh, EcdhRatchetStep step)
        {
            if (message == null)
            {
                throw new ArgumentNullException(nameof(message));
            }

            // message format:
            // <nonce (4)>, <payload, padding>, mac(12)
            // <nonce (4), ecdh (32)>, <payload, padding>, mac(12)

            // get the payload key and nonce
            (var payloadKey, var messageNumber) = step.SendingChain.RatchetForSending(KeyDerivation);
            var nonce = BigEndianBitConverter.GetBytes(messageNumber);

            // make sure the first bit is not set as we use that bit to indicate
            // the presence of new ECDH parameters
            if ((nonce[0] & 0b1000_0000) != 0)
            {
                throw new InvalidOperationException($"The message number is too big. Cannot encrypt more than {((uint)1 << 31) - 1} messages without exchanging keys");
            }

            // calculate some sizes
            var headerSize     = NonceSize + (includeEcdh ? EcNumSize : 0);
            var overhead       = headerSize + MacSize;
            var messageSize    = message.Count;
            var maxMessageSize = Configuration.MaximumMessageSize - overhead;
            var minPayloadSize = Configuration.MinimumMessageSize - overhead;

            // build the payload: <payload, padding>
            ArraySegment <byte> payload;

            if (messageSize < minPayloadSize)
            {
                payload = new ArraySegment <byte>(new byte[minPayloadSize]);
                Array.Copy(message.Array, message.Offset, payload.Array, 0, message.Count);
            }
            else if (messageSize > maxMessageSize)
            {
                throw new InvalidOperationException("The message doesn't fit inside the MTU");
            }
            else
            {
                payload = message;
            }

            // encrypt the payload
            var icipher          = new AesCtrMode(AesFactory.GetAes(true, payloadKey), nonce);
            var encryptedPayload = icipher.Process(payload);

            // build the header: <nonce(4), ecdh(32)?>
            var header = new byte[headerSize];

            Array.Copy(nonce, header, NonceSize);
            if (includeEcdh)
            {
                var ratchetPublicKey = step.GetPublicKey();
                Array.Copy(ratchetPublicKey, 0, header, nonce.Length, ratchetPublicKey.Length);

                // set the has ecdh bit
                header[0] |= 0b1000_0000;
            }
            else
            {
                // clear the has ecdh bit
                header[0] &= 0b0111_1111;
            }

            // encrypt the header using the header key and using the
            // last 16 bytes of the message as the nonce.
            var headerEncryptionNonce = new ArraySegment <byte>(encryptedPayload, encryptedPayload.Length - HeaderIVSize, HeaderIVSize);
            var hcipher         = new AesCtrMode(GetHeaderKeyCipher(step.SendHeaderKey), headerEncryptionNonce);
            var encryptedHeader = hcipher.Process(header);

            // mac the message: <header>, <payload>, mac(12)
            // the mac uses the header encryption derived key (all 32 bytes)
            var Mac   = new Poly(AesFactory);
            var maciv = new byte[16];
            var epl   = encryptedPayload.Length;
            var ehl   = encryptedHeader.Length;

            if (ehl < maciv.Length)
            {
                Array.Copy(encryptedHeader, maciv, ehl);
                Array.Copy(encryptedPayload, 0, maciv, ehl, 16 - ehl);
            }
            else
            {
                Array.Copy(encryptedHeader, maciv, maciv.Length);
            }
            Mac.Init(step.SendHeaderKey, maciv, MacSize);
            Mac.Process(encryptedHeader);
            Mac.Process(encryptedPayload);
            var mac = Mac.Compute();

            // construct the resulting message
            var result = new byte[ehl + epl + mac.Length];

            Array.Copy(encryptedHeader, 0, result, 0, ehl);
            Array.Copy(encryptedPayload, 0, result, ehl, epl);
            Array.Copy(mac, 0, result, ehl + epl, mac.Length);
            return(result);
        }
Пример #6
0
        private byte[] SendInitializationResponse(State state, ArraySegment <byte> initializationNonce, ArraySegment <byte> remoteEcdhForInit)
        {
            // message format:
            // new nonce(16), ecdh pubkey(32),
            // <nonce from init request(16), server pubkey(32),
            // new ecdh pubkey(32) x2, Padding(...), signature(64)>, mac(12) = 236 bytes

            if (!(state is ServerState serverState))
            {
                throw new InvalidOperationException("Only the server can send init response.");
            }

            // generate a nonce and new ecdh parms
            var serverNonce = RandomNumberGenerator.Generate(InitializationNonceSize);

            serverState.NextInitializationNonce = serverNonce;
            IKeyAgreement rootPreEcdh       = KeyAgreementFactory.GenerateNew();
            var           rootPreEcdhPubkey = rootPreEcdh.GetPublicKey();

            // generate server ECDH for root key and root key
            var rootPreKey = rootPreEcdh.DeriveKey(remoteEcdhForInit);

            rootPreKey = Digest.ComputeDigest(rootPreKey);
            var genKeys = KeyDerivation.GenerateKeys(rootPreKey, serverNonce, 3, EcNumSize);

            serverState.RootKey               = genKeys[0];
            serverState.FirstSendHeaderKey    = genKeys[1];
            serverState.FirstReceiveHeaderKey = genKeys[2];

            // generate two server ECDH. One for ratchet 0 sending key and one for the next
            // this is enough for the server to generate a receiving chain key and sending
            // chain key as soon as the client sends a sending chain key
            IKeyAgreement serverEcdhRatchet0 = KeyAgreementFactory.GenerateNew();

            serverState.LocalEcdhRatchetStep0 = serverEcdhRatchet0;
            IKeyAgreement serverEcdhRatchet1 = KeyAgreementFactory.GenerateNew();

            serverState.LocalEcdhRatchetStep1 = serverEcdhRatchet1;

            var minimumMessageSize = InitializationNonceSize * 2 + EcNumSize * 6 + MacSize;
            var entireMessageSize  = Math.Max(Configuration.MinimumMessageSize, minimumMessageSize);
            var macOffset          = entireMessageSize - MacSize;
            var entireMessageWithoutMacOrSignatureSize = macOffset - SignatureSize;
            var encryptedPayloadOffset = InitializationNonceSize + EcNumSize;
            var encryptedPayloadSize   = macOffset - encryptedPayloadOffset;

            // construct the message
            var message = new byte[entireMessageSize];

            Array.Copy(serverNonce, 0, message, 0, InitializationNonceSize);
            Array.Copy(rootPreEcdhPubkey, 0, message, InitializationNonceSize, EcNumSize);

            // construct the to-be-encrypted part
            var rre0 = serverEcdhRatchet0.GetPublicKey();
            var rre1 = serverEcdhRatchet1.GetPublicKey();

            Array.Copy(initializationNonce.Array, initializationNonce.Offset, message, encryptedPayloadOffset, InitializationNonceSize);
            Array.Copy(Signature.GetPublicKey(), 0, message, encryptedPayloadOffset + InitializationNonceSize, EcNumSize);
            Array.Copy(rre0, 0, message, encryptedPayloadOffset + InitializationNonceSize + EcNumSize, EcNumSize);
            Array.Copy(rre1, 0, message, encryptedPayloadOffset + InitializationNonceSize + EcNumSize * 2, EcNumSize);

            // sign the message
            var digest = Digest.ComputeDigest(message, 0, entireMessageWithoutMacOrSignatureSize);

            Array.Copy(Signature.Sign(digest), 0, message, entireMessageWithoutMacOrSignatureSize, SignatureSize);

            // encrypt the message
            var cipher           = new AesCtrMode(AesFactory.GetAes(true, rootPreKey), serverNonce);
            var encryptedPayload = cipher.Process(message, encryptedPayloadOffset, encryptedPayloadSize);

            Array.Copy(encryptedPayload, 0, message, encryptedPayloadOffset, encryptedPayloadSize);

            // encrypt the header
            cipher = new AesCtrMode(AesFactory.GetAes(true, Configuration.ApplicationKey), encryptedPayload, encryptedPayloadSize - HeaderIVSize, HeaderIVSize);
            var encryptedHeader = cipher.Process(message, 0, encryptedPayloadOffset);

            Array.Copy(encryptedHeader, 0, message, 0, encryptedPayloadOffset);

            // calculate mac
            var Mac = new Poly(AesFactory);

            Mac.Init(Configuration.ApplicationKey, encryptedHeader, 0, InitializationNonceSize, MacSize);
            Mac.Process(message, 0, macOffset);
            var mac = Mac.Compute();

            Array.Copy(mac, 0, message, macOffset, MacSize);

            return(message);
        }