Пример #1
0
        public static EcdhRatchetStep InitializeServer(IKeyDerivation kdf, IDigest digest,
                                                       IKeyAgreement previousKeyPair,
                                                       byte[] rootKey, ArraySegment <byte> remotePublicKey, IKeyAgreement keyPair,
                                                       byte[] receiveHeaderKey, byte[] sendHeaderKey)
        {
            if (receiveHeaderKey.Length != 32 || sendHeaderKey.Length != 32)
            {
                throw new InvalidOperationException("Keys need to be 32 bytes.");
            }
            Log.Verbose($"--Initialize ECDH Ratchet");
            Log.Verbose($"Root Key:           {Log.ShowBytes(rootKey)}");
            Log.Verbose($"Prev ECDH Private: ({Log.ShowBytes(previousKeyPair.GetPublicKey())})");
            Log.Verbose($"ECDH Public:        {Log.ShowBytes(remotePublicKey)}");
            Log.Verbose($"Curr ECDH Private: ({Log.ShowBytes(keyPair.GetPublicKey())})");

            var e = new EcdhRatchetStep
            {
                EcdhKey          = keyPair,
                ReceiveHeaderKey = receiveHeaderKey,
                SendHeaderKey    = sendHeaderKey
            };

            // receive chain
            Log.Verbose("  --Receiving Chain");
            var rcderived = previousKeyPair.DeriveKey(remotePublicKey);

            rcderived = digest.ComputeDigest(rcderived);
            Log.Verbose($"  C Input Key:    {Log.ShowBytes(rootKey)}");
            Log.Verbose($"  C Key Info:     {Log.ShowBytes(rcderived)}");
            var rckeys = kdf.GenerateKeys(rcderived, rootKey, 3, 32);

            Log.Verbose($"  C Key Out 0:    {Log.ShowBytes(rckeys[0])}");
            Log.Verbose($"  C Key Out 1:    {Log.ShowBytes(rckeys[1])}");
            Log.Verbose($"  C Key Out 2:    {Log.ShowBytes(rckeys[2])}");
            rootKey = rckeys[0];
            e.ReceivingChain.Initialize(rckeys[1]);
            e.NextReceiveHeaderKey = rckeys[2];

            // send chain
            Log.Verbose("  --Sending Chain");
            var scderived = keyPair.DeriveKey(remotePublicKey);

            scderived = digest.ComputeDigest(scderived);
            Log.Verbose($"  C Input Key:    {Log.ShowBytes(rootKey)}");
            Log.Verbose($"  C Key Info:     {Log.ShowBytes(scderived)}");
            var sckeys = kdf.GenerateKeys(scderived, rootKey, 3, 32);

            Log.Verbose($"  C Key Out 0:    {Log.ShowBytes(sckeys[0])}");
            Log.Verbose($"  C Key Out 1:    {Log.ShowBytes(sckeys[1])}");
            Log.Verbose($"  C Key Out 2:    {Log.ShowBytes(sckeys[2])}");
            rootKey = sckeys[0];
            e.SendingChain.Initialize(sckeys[1]);
            e.NextSendHeaderKey = sckeys[2];

            // next root key
            Log.Verbose($"Next Root Key:     ({Log.ShowBytes(rootKey)})");
            e.NextRootKey = rootKey;
            return(e);
        }
Пример #2
0
        public static EcdhRatchetStep[] InitializeClient(IKeyDerivation kdf, IDigest digest,
                                                         byte[] rootKey, ArraySegment <byte> remotePublicKey0, ArraySegment <byte> remotePublicKey1, IKeyAgreement keyPair,
                                                         byte[] receiveHeaderKey, byte[] sendHeaderKey,
                                                         IKeyAgreement nextKeyPair)
        {
            if (receiveHeaderKey.Length != 32 || sendHeaderKey.Length != 32)
            {
                throw new InvalidOperationException("Keys need to be 32 bytes.");
            }
            Log.Verbose($"--Initialize ECDH Ratchet CLIENT");
            Log.Verbose($"Root Key:           {Log.ShowBytes(rootKey)}");
            Log.Verbose($"ECDH Public 0:      {Log.ShowBytes(remotePublicKey0)}");
            Log.Verbose($"ECDH Public 1:      {Log.ShowBytes(remotePublicKey1)}");
            Log.Verbose($"ECDH Private:      ({Log.ShowBytes(keyPair.GetPublicKey())})");

            var e0 = new EcdhRatchetStep
            {
                EcdhKey       = keyPair,
                SendHeaderKey = sendHeaderKey
            };

            // receive chain doesn't exist
            Log.Verbose("  --Receiving Chain");

            // send chain
            Log.Verbose("  --Sending Chain");
            var scderived = keyPair.DeriveKey(remotePublicKey0);

            scderived = digest.ComputeDigest(scderived);
            Log.Verbose($"  C Input Key:    {Log.ShowBytes(rootKey)}");
            Log.Verbose($"  C Key Info:     {Log.ShowBytes(scderived)}");
            var sckeys = kdf.GenerateKeys(scderived, rootKey, 3, 32);

            Log.Verbose($"  C Key Out 0:    {Log.ShowBytes(sckeys[0])}");
            Log.Verbose($"  C Key Out 1:    {Log.ShowBytes(sckeys[1])}");
            Log.Verbose($"  C Key Out 2:    {Log.ShowBytes(sckeys[2])}");
            rootKey = sckeys[0];
            e0.SendingChain.Initialize(sckeys[1]);
            var nextSendHeaderKey = sckeys[2];

            var e1 = InitializeServer(kdf, digest,
                                      keyPair,
                                      rootKey,
                                      remotePublicKey1,
                                      nextKeyPair,
                                      receiveHeaderKey,
                                      nextSendHeaderKey);

            return(new[] { e0, e1 });
        }
Пример #3
0
        public static EcdhRatchetStep Create(IKeyAgreement EcdhKey, byte[] NextRootKey,
                                             int receivingGeneration, byte[] receivingHeaderKey, byte[] receivingNextHeaderKey, byte[] receivingChainKey,
                                             int sendingGeneration, byte[] sendingHeaderKey, byte[] sendingNextHeaderKey, byte[] sendingChainKey)
        {
            if (NextRootKey != null && NextRootKey.Length != 32)
            {
                throw new InvalidOperationException("The next root key size needs to be 32 bytes");
            }
            if (receivingHeaderKey != null && receivingHeaderKey.Length != 32)
            {
                throw new InvalidOperationException("The receiving header key size needs to be 32 bytes");
            }
            if (receivingNextHeaderKey != null && receivingNextHeaderKey.Length != 32)
            {
                throw new InvalidOperationException("The next receiving header key size needs to be 32 bytes");
            }
            if (receivingChainKey != null && receivingChainKey.Length != 32)
            {
                throw new InvalidOperationException("The receiving chain key size needs to be 32 bytes");
            }
            if (sendingHeaderKey != null && sendingHeaderKey.Length != 32)
            {
                throw new InvalidOperationException("The sending header key size needs to be 32 bytes");
            }
            if (sendingNextHeaderKey != null && sendingNextHeaderKey.Length != 32)
            {
                throw new InvalidOperationException("The next sending header key size needs to be 32 bytes");
            }
            if (sendingChainKey != null && sendingChainKey.Length != 32)
            {
                throw new InvalidOperationException("The sending chain key size needs to be 32 bytes");
            }

            var step = new EcdhRatchetStep()
            {
                EcdhKey              = EcdhKey,
                NextRootKey          = NextRootKey,
                ReceiveHeaderKey     = receivingHeaderKey,
                NextReceiveHeaderKey = receivingNextHeaderKey,
                SendHeaderKey        = sendingHeaderKey,
                NextSendHeaderKey    = sendingNextHeaderKey
            };

            step.ReceivingChain.Initialize(receivingChainKey);
            step.ReceivingChain.Generation = receivingGeneration;
            step.SendingChain.Initialize(sendingChainKey);
            step.SendingChain.Generation = sendingGeneration;

            return(step);
        }
Пример #4
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;
        }
Пример #5
0
 private static void ShredRatchet(EcdhRatchetStep r)
 {
     (r.EcdhKey as IDisposable)?.Dispose();
     r.EcdhKey = null;
     r.NextReceiveHeaderKey?.Shred();
     r.NextReceiveHeaderKey = null;
     r.NextRootKey?.Shred();
     r.NextRootKey = null;
     r.NextSendHeaderKey?.Shred();
     r.NextSendHeaderKey = null;
     r.ReceiveHeaderKey?.Shred();
     r.ReceiveHeaderKey = null;
     r.SendHeaderKey?.Shred();
     r.SendHeaderKey = null;
     r.ReceivingChain.ChainKey?.Shred();
     r.ReceivingChain.ChainKey = null;
     r.ReceivingChain.OldChainKey?.Shred();
     r.ReceivingChain.OldChainKey = null;
     r.SendingChain.ChainKey?.Shred();
     r.SendingChain.ChainKey = null;
     r.SendingChain.OldChainKey?.Shred();
     r.SendingChain.OldChainKey = null;
 }
Пример #6
0
        private byte[] ProcessInitialization(State state, byte[] dataReceived, byte[] headerKeyUsed, EcdhRatchetStep ecdhRatchetStep, bool usedApplicationHeaderKey)
        {
            byte[] sendback;
            if (Configuration.IsClient)
            {
                Log.Verbose("\n\n###CLIENT");
                var clientState = (ClientState)state;

                if (dataReceived == null)
                {
                    // step 1: send first init request from client
                    sendback = SendInitializationRequest(state);
                }
                else
                {
                    if (usedApplicationHeaderKey)
                    {
                        if (clientState.Ratchets.Count == 0 && clientState.LocalEcdhForInit != null)
                        {
                            // step 2: init response from server
                            ReceiveInitializationResponse(clientState, dataReceived);
                            sendback = SendFirstClientMessage(clientState);
                        }
                        else
                        {
                            throw new InvalidOperationException("Unexpected initialization message");
                        }
                    }
                    else
                    {
                        // step 3: receive first message from server
                        ReceiveFirstResponse(clientState, dataReceived, headerKeyUsed, ecdhRatchetStep);
                        // initialization completed successfully.
                        sendback = null;
                    }
                }
            }
            else
            {
                Log.Verbose("\n\n###SERVER");
                var serverState = (ServerState)state;

                if (dataReceived == null)
                {
                    throw new InvalidOperationException("Only the client can send initialization without having received a response first");
                }

                if (usedApplicationHeaderKey)
                {
                    // step 1: client init request
                    (ArraySegment <byte> initializationNonce, ArraySegment <byte> remoteEcdhForInit) = ReceiveInitializationRequest(serverState, dataReceived);
                    sendback = SendInitializationResponse(serverState, initializationNonce, remoteEcdhForInit);
                }
                else
                {
                    // step 2: first message from client
                    ReceiveFirstMessage(serverState, dataReceived, ecdhRatchetStep);
                    sendback = SendFirstResponse(serverState);
                }
            }

            if (sendback != null && sendback.Length > Configuration.MaximumMessageSize)
            {
                throw new InvalidOperationException("The MTU is too small");
            }

            return(sendback);
        }
Пример #7
0
        private byte[] DeconstructMessage(State state, byte[] payload, byte[] headerKey, EcdhRatchetStep ratchetUsed, bool usedNextHeaderKey)
        {
            if (state == null)
            {
                throw new ArgumentNullException(nameof(state));
            }
            if (payload == null)
            {
                throw new ArgumentNullException(nameof(payload));
            }
            if (headerKey == null)
            {
                throw new ArgumentNullException(nameof(headerKey));
            }

            var messageSize    = payload.Length;
            var encryptedNonce = new ArraySegment <byte>(payload, 0, NonceSize);

            // decrypt the nonce
            var headerEncryptionNonce = new ArraySegment <byte>(payload, payload.Length - MacSize - HeaderIVSize, HeaderIVSize);
            var hcipher        = new AesCtrMode(GetHeaderKeyCipher(headerKey), headerEncryptionNonce);
            var decryptedNonce = hcipher.Process(encryptedNonce);

            CheckNonce(headerEncryptionNonce);

            // get the ecdh bit
            var hasEcdh = (decryptedNonce[0] & 0b1000_0000) != 0;

            decryptedNonce[0] &= 0b0111_1111;

            // extract ecdh if needed
            var step = BigEndianBitConverter.ToInt32(decryptedNonce);

            if (hasEcdh)
            {
                var clientEcdhPublic = new ArraySegment <byte>(hcipher.Process(new ArraySegment <byte>(payload, NonceSize, EcNumSize)));

                if (ratchetUsed == null)
                {
                    // an override header key was used.
                    // this means we have to initialize the ratchet
                    if (!(state is ServerState serverState))
                    {
                        throw new InvalidOperationException("Only the server can initialize a ratchet.");
                    }
                    ratchetUsed = EcdhRatchetStep.InitializeServer(KeyDerivation, Digest,
                                                                   serverState.LocalEcdhRatchetStep0,
                                                                   serverState.RootKey, clientEcdhPublic,
                                                                   serverState.LocalEcdhRatchetStep1,
                                                                   serverState.FirstReceiveHeaderKey,
                                                                   serverState.FirstSendHeaderKey);
                    serverState.Ratchets.Add(ratchetUsed);
                }
                else
                {
                    if (usedNextHeaderKey)
                    {
                        // perform ecdh ratchet
                        IKeyAgreement newEcdh = KeyAgreementFactory.GenerateNew();

                        // this is the hottest line in the deconstruct process:
                        EcdhRatchetStep newRatchet = ratchetUsed.Ratchet(KeyDerivation, Digest, clientEcdhPublic, newEcdh);
                        state.Ratchets.Add(newRatchet);
                        ratchetUsed = newRatchet;
                    }
                }
            }


            // get the inner payload key from the receive chain
            if (ratchetUsed == null)
            {
                throw new InvalidOperationException("An override header key was used but the message did not contain ECDH parameters");
            }
            (var key, var _) = ratchetUsed.ReceivingChain.RatchetForReceiving(KeyDerivation, step);
            CheckNonce(key);

            // get the encrypted payload
            var payloadOffset    = hasEcdh ? NonceSize + EcNumSize : NonceSize;
            var encryptedPayload = new ArraySegment <byte>(payload, payloadOffset, messageSize - payloadOffset - MacSize);

            // decrypt the inner payload
            var icipher = new AesCtrMode(AesFactory.GetAes(true, key), decryptedNonce);
            var decryptedInnerPayload = icipher.Process(encryptedPayload);

            return(decryptedInnerPayload);
        }
Пример #8
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);
        }
Пример #9
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);
        }
Пример #10
0
        private void ReceiveFirstResponse(State state, byte[] data, byte[] headerKey, EcdhRatchetStep ecdhRatchetStep)
        {
            if (!(state is ClientState clientState))
            {
                throw new InvalidOperationException("Only the client can receive the first response.");
            }

            var contents = DeconstructMessage(state, data, headerKey, ecdhRatchetStep, false);

            if (contents == null || contents.Length < InitializationNonceSize)
            {
                throw new InvalidOperationException("The first response from the server was not valid");
            }

            var nonce = new ArraySegment <byte>(contents, 0, InitializationNonceSize);

            if (!nonce.Matches(clientState.InitializationNonce))
            {
                throw new InvalidOperationException("The first response from the server did not contain the correct nonce");
            }

            clientState.InitializationNonce = null;
        }
Пример #11
0
        private void ReceiveInitializationResponse(State state, byte[] data)
        {
            if (!(state is ClientState clientState))
            {
                throw new InvalidOperationException("Only the client can receive an init response.");
            }

            var messageSize    = data.Length;
            var macOffset      = messageSize - MacSize;
            var headerIvOffset = macOffset - HeaderIVSize;
            var headerSize     = InitializationNonceSize + EcNumSize;
            var payloadSize    = messageSize - headerSize - MacSize;

            // decrypt header
            var cipher          = new AesCtrMode(AesFactory.GetAes(true, Configuration.ApplicationKey), data, headerIvOffset, HeaderIVSize);
            var decryptedHeader = cipher.Process(data, 0, headerSize);

            Array.Copy(decryptedHeader, 0, data, 0, headerSize);

            // new nonce(16), ecdh pubkey(32), <nonce(16), server pubkey(32),
            // new ecdh pubkey(32) x2, signature(64)>, mac(12)
            var nonce            = new ArraySegment <byte>(data, 0, InitializationNonceSize);
            var rootEcdhKey      = new ArraySegment <byte>(data, InitializationNonceSize, EcNumSize);
            var encryptedPayload = new ArraySegment <byte>(data, headerSize, payloadSize);

            CheckNonce(nonce);

            // decrypt payload
            IKeyAgreement rootEcdh   = clientState.LocalEcdhForInit;
            var           rootPreKey = rootEcdh.DeriveKey(rootEcdhKey);

            rootPreKey = Digest.ComputeDigest(rootPreKey);
            cipher     = new AesCtrMode(AesFactory.GetAes(true, rootPreKey), nonce);
            var decryptedPayload = cipher.Process(encryptedPayload);

            Array.Copy(decryptedPayload, 0, data, headerSize, payloadSize);

            // extract some goodies
            var oldNonce           = new ArraySegment <byte>(data, headerSize, InitializationNonceSize);
            var serverPubKey       = new ArraySegment <byte>(data, headerSize + InitializationNonceSize, EcNumSize);
            var remoteRatchetEcdh0 = new ArraySegment <byte>(data, headerSize + InitializationNonceSize + EcNumSize, EcNumSize);
            var remoteRatchetEcdh1 = new ArraySegment <byte>(data, headerSize + InitializationNonceSize + EcNumSize * 2, EcNumSize);

            // make sure the nonce sent back by the server (which is encrypted and signed)
            // matches the nonce we sent previously
            if (!oldNonce.Matches(clientState.InitializationNonce))
            {
                throw new InvalidOperationException("Nonce did not match");
            }

            // verify that the signature matches
            IVerifier verifier = VerifierFactory.Create(serverPubKey);

            if (!verifier.VerifySignedMessage(Digest, new ArraySegment <byte>(data, 0, payloadSize + headerSize)))
            {
                throw new InvalidOperationException("The signature was invalid");
            }

            // keep the server public key around
            clientState.ServerPublicKey = serverPubKey.ToArray();

            // store the new nonce we got from the server
            clientState.InitializationNonce = nonce.ToArray();
            Log.Verbose($"storing iniitlizaionta nonce: {Log.ShowBytes(nonce)}");

            // we now have enough information to construct our double ratchet
            IKeyAgreement localStep0EcdhRatchet = KeyAgreementFactory.GenerateNew();
            IKeyAgreement localStep1EcdhRatchet = KeyAgreementFactory.GenerateNew();

            // initialize client root key and ecdh ratchet
            var genKeys          = KeyDerivation.GenerateKeys(rootPreKey, clientState.InitializationNonce, 3, 32);
            var rootKey          = genKeys[0];
            var receiveHeaderKey = genKeys[1];
            var sendHeaderKey    = genKeys[2];

            clientState.Ratchets.Add(EcdhRatchetStep.InitializeClient(KeyDerivation, Digest, rootKey,
                                                                      remoteRatchetEcdh0, remoteRatchetEcdh1, localStep0EcdhRatchet,
                                                                      receiveHeaderKey, sendHeaderKey,
                                                                      localStep1EcdhRatchet));

            clientState.LocalEcdhForInit = null;
        }
Пример #12
0
        protected void ReadRatchet(Stream stream, IKeyAgreementFactory kexFac)
        {
            List <EcdhRatchetStep> steps = new List <EcdhRatchetStep>();
            bool last         = true;
            bool secondToLast = false;

            for (; ;)
            {
                // check if this is still a ratchet record
                var b = stream.ReadByte();
                stream.Seek(-1, SeekOrigin.Current);
                if (b < 0 || (b & 0b1110_0000) == 0)
                {
                    break;
                }

                if (last)
                {
                    var genbytes = new byte[8];
                    stream.Read(genbytes, 0, 8);
                    genbytes[0] &= 0b0001_1111;
                    var rgeneration = BigEndianBitConverter.ToInt32(genbytes, 0); // hot
                    var sgeneration = BigEndianBitConverter.ToInt32(genbytes, 4); // hot and important

                    var ecdh           = kexFac.Deserialize(stream);
                    var nextRootKey    = new byte[KeySizeInBytes];
                    var sHeaderKey     = new byte[KeySizeInBytes];
                    var sNextHeaderKey = new byte[KeySizeInBytes];
                    var sChainKey      = new byte[KeySizeInBytes];
                    var rHeaderKey     = new byte[KeySizeInBytes];
                    var rNextHeaderKey = new byte[KeySizeInBytes];
                    var rChainKey      = new byte[KeySizeInBytes];
                    stream.Read(nextRootKey, 0, KeySizeInBytes);    // cold
                    stream.Read(sHeaderKey, 0, KeySizeInBytes);     // cold
                    stream.Read(sNextHeaderKey, 0, KeySizeInBytes); // cold
                    stream.Read(sChainKey, 0, KeySizeInBytes);      // hot and important
                    stream.Read(rHeaderKey, 0, KeySizeInBytes);     // cold
                    stream.Read(rNextHeaderKey, 0, KeySizeInBytes); // cold
                    stream.Read(rChainKey, 0, KeySizeInBytes);      // hot
                    steps.Add(EcdhRatchetStep.Create(ecdh, nextRootKey, rgeneration, rHeaderKey, rNextHeaderKey, rChainKey,
                                                     sgeneration, sHeaderKey, sNextHeaderKey, sChainKey));
                }
                else if (secondToLast)
                {
                    if ((b & 0b1000_0000) != 0)
                    {
                        // send and receive chain
                        var genbytes = new byte[8];
                        stream.Read(genbytes, 0, 8);
                        genbytes[0] &= 0b0001_1111;
                        var rgeneration = BigEndianBitConverter.ToInt32(genbytes, 0); // hot
                        var sgeneration = BigEndianBitConverter.ToInt32(genbytes, 4); // hot and important

                        var sHeaderKey = new byte[KeySizeInBytes];
                        var sChainKey  = new byte[KeySizeInBytes];
                        var rHeaderKey = new byte[KeySizeInBytes];
                        var rChainKey  = new byte[KeySizeInBytes];
                        stream.Read(sHeaderKey, 0, KeySizeInBytes); // cold
                        stream.Read(sChainKey, 0, KeySizeInBytes);  // hot and important
                        stream.Read(rHeaderKey, 0, KeySizeInBytes); // cold
                        stream.Read(rChainKey, 0, KeySizeInBytes);  // hot
                        steps.Add(EcdhRatchetStep.Create(null, null, rgeneration, rHeaderKey, null, rChainKey,
                                                         sgeneration, sHeaderKey, null, sChainKey));
                    }
                    else
                    {
                        // only sending chain - the client starts with only a sending chain as the first generation
                        var genbytes = new byte[4];
                        stream.Read(genbytes, 0, 4);
                        genbytes[0] &= 0b0001_1111;
                        var sgeneration = BigEndianBitConverter.ToInt32(genbytes, 0); // hot and important

                        var sHeaderKey = new byte[KeySizeInBytes];
                        var sChainKey  = new byte[KeySizeInBytes];
                        stream.Read(sHeaderKey, 0, KeySizeInBytes); // cold
                        stream.Read(sChainKey, 0, KeySizeInBytes);  // hot and important
                        steps.Add(EcdhRatchetStep.Create(null, null, 0, null, null, null,
                                                         sgeneration, sHeaderKey, null, sChainKey));
                    }
                }