Example #1
0
        public static byte[] Respond(ECPublicKeyParameters senderPublicKey, ECPrivateKeyParameters receiverPrivateKey,
                                     ECPublicKeyParameters ephemeralSenderPublicKey)
        {
            ECPublicKeyParameters  Q_static_U    = senderPublicKey;
            ECPrivateKeyParameters d_static_V    = receiverPrivateKey;
            ECPublicKeyParameters  Q_ephemeral_U = ephemeralSenderPublicKey;

            // Calculate shared ephemeral secret 'Ze'
            BigInteger Ze = KeyAgreementFactory.CalculateEcdhcSecret(Q_ephemeral_U, d_static_V); // EC-DHC

            byte[] Ze_encoded = Ze.ToByteArrayUnsigned();

            // Calculate shared static secret 'Zs'
            BigInteger Zs = KeyAgreementFactory.CalculateEcdhcSecret(Q_static_U, d_static_V); // EC-DHC

            byte[] Zs_encoded = Zs.ToByteArrayUnsigned();

            // Concatenate Ze and Zs byte strings to form shared secret, pre-KDF : Ze||Zs
            var Z = new byte[Ze_encoded.Length + Zs_encoded.Length];

            Ze_encoded.DeepCopy_NoChecks(0, Z, 0, Ze_encoded.Length);
            Zs_encoded.DeepCopy_NoChecks(0, Z, Ze_encoded.Length, Zs_encoded.Length);

            // Zero intermediate secrets
            Ze_encoded.SecureWipe();
            Zs_encoded.SecureWipe();

            return(Z);
        }
Example #2
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);
        }
Example #3
0
        public static byte[] Initiate(ECPublicKeyParameters recipientPublicKey, ECPrivateKeyParameters senderPrivateKey,
                                      out ECPublicKeyParameters ephemeralSenderPublicKey)
        {
            ECPublicKeyParameters  Q_static_V = recipientPublicKey;
            ECPrivateKeyParameters d_static_U = senderPrivateKey;

            ECPoint    QeV;
            BigInteger deU;

            KeypairFactory.GenerateECKeypair(recipientPublicKey.Parameters, out QeV, out deU);

            var Q_ephemeral_V = new ECPublicKeyParameters("ECDHC", QeV, recipientPublicKey.Parameters);
            var d_ephemeral_U = new ECPrivateKeyParameters("ECDHC", deU, recipientPublicKey.Parameters);

            // Calculate shared ephemeral secret 'Ze'
            BigInteger Ze = KeyAgreementFactory.CalculateEcdhcSecret(Q_static_V, d_ephemeral_U); // EC-DHC

            byte[] Ze_encoded = Ze.ToByteArrayUnsigned();

            // Calculate shared static secret 'Zs'
            BigInteger Zs = KeyAgreementFactory.CalculateEcdhcSecret(Q_static_V, d_static_U); // EC-DHC

            byte[] Zs_encoded = Zs.ToByteArrayUnsigned();

            // Concatenate Ze and Zs byte strings to form shared secret, pre-KDF : Ze||Zs
            var Z = new byte[Ze_encoded.Length + Zs_encoded.Length];

            Ze_encoded.DeepCopy_NoChecks(0, Z, 0, Ze_encoded.Length);
            Zs_encoded.DeepCopy_NoChecks(0, Z, Ze_encoded.Length, Zs_encoded.Length);
            ephemeralSenderPublicKey = Q_ephemeral_V;

            // Zero intermediate secrets
            Ze_encoded.SecureWipe();
            Zs_encoded.SecureWipe();

            return(Z);
        }
Example #4
0
        /// <summary>
        ///     Calculate the shared secret in participant U's (initiator) role.
        /// </summary>
        /// <param name="recipientPublicKey">Public key of the recipient.</param>
        /// <param name="senderPrivateKey">Private key of the sender.</param>
        /// <param name="ephemeralSenderPublicKey">
        ///     Ephemeral public key to send to the responder (V, receiver). Output to this
        ///     parameter.
        /// </param>
        public static byte[] Initiate(ECKey recipientPublicKey, ECKey senderPrivateKey,
                                      out ECKey ephemeralSenderPublicKey)
        {
            if (recipientPublicKey.PublicComponent == false)
            {
                throw new ArgumentException("Recipient key is not public component.", "recipientPublicKey");
            }
            if (senderPrivateKey.PublicComponent)
            {
                throw new ArgumentException("Sender key not private component.", "senderPrivateKey");
            }

            ECKey Q_static_V = recipientPublicKey;
            ECKey d_static_U = senderPrivateKey;

            ECKeypair kp_ephemeral_U = KeypairFactory.GenerateECKeypair(senderPrivateKey.CurveName);
            ECKey     Q_ephemeral_U  = kp_ephemeral_U.ExportPublicKey();
            ECKey     d_ephemeral_U  = kp_ephemeral_U.GetPrivateKey();

            // Calculate shared ephemeral secret 'Ze'
            byte[] Ze = KeyAgreementFactory.CalculateEcdhcSecret(Q_static_V, d_ephemeral_U);
            // Calculate shared static secret 'Zs'
            byte[] Zs = KeyAgreementFactory.CalculateEcdhcSecret(Q_static_V, d_static_U);

            // Concatenate Ze and Zs byte strings to form shared secret, pre-KDF : Ze||Zs
            var Z = new byte[Ze.Length + Zs.Length];

            Ze.DeepCopy_NoChecks(0, Z, 0, Ze.Length);
            Zs.DeepCopy_NoChecks(0, Z, Ze.Length, Zs.Length);
            ephemeralSenderPublicKey = Q_ephemeral_U;

            // Zero intermediate secrets
            Ze.SecureWipe();
            Zs.SecureWipe();

            return(Z);
        }
Example #5
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);
        }
Example #6
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;
        }
Example #7
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);
        }