Example #1
0
        /// <summary>
        /// Send (resend) a ClientHello message to the server
        /// </summary>
        private void SendClientHello()
        {
            // Reset our verification stream
            this.nextEpoch.VerificationStream.SetLength(0);
            this.nextEpoch.ClientRandom.FillWithRandom(this.random);

            // Describe our ClientHello flight
            ClientHello clientHello = new ClientHello();

            clientHello.Random       = this.nextEpoch.ClientRandom;
            clientHello.Cookie       = this.nextEpoch.Cookie;
            clientHello.CipherSuites = new byte[2];
            clientHello.CipherSuites.WriteBigEndian16((ushort)CipherSuite.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256);
            clientHello.SupportedCurves = new byte[2];
            clientHello.SupportedCurves.WriteBigEndian16((ushort)NamedCurve.x25519);

            Handshake handshake = new Handshake();

            handshake.MessageType     = HandshakeType.ClientHello;
            handshake.Length          = (uint)clientHello.CalculateSize();
            handshake.MessageSequence = 0;
            handshake.FragmentOffset  = 0;
            handshake.FragmentLength  = handshake.Length;

            // Describe the record
            int    plaintextLength = (int)(Handshake.Size + handshake.Length);
            Record outgoingRecord  = new Record();

            outgoingRecord.ContentType    = ContentType.Handshake;
            outgoingRecord.Epoch          = this.epoch;
            outgoingRecord.SequenceNumber = this.currentEpoch.NextOutgoingSequence;
            outgoingRecord.Length         = (ushort)this.currentEpoch.RecordProtection.GetEncryptedSize(plaintextLength);
            ++this.currentEpoch.NextOutgoingSequence;

            // Convert the record to wire format
            ByteSpan packet = new byte[Record.Size + outgoingRecord.Length];
            ByteSpan writer = packet;

            outgoingRecord.Encode(packet);
            writer = writer.Slice(Record.Size);
            handshake.Encode(writer);
            writer = writer.Slice(Handshake.Size);
            clientHello.Encode(writer);

            // Write ClientHello to the verification stream
            this.nextEpoch.VerificationStream.Write(
                packet.GetUnderlyingArray()
                , Record.Size
                , Handshake.Size + (int)handshake.Length
                );

            // Protect the record
            this.currentEpoch.RecordProtection.EncryptClientPlaintext(
                packet.Slice(Record.Size, outgoingRecord.Length)
                , packet.Slice(Record.Size, plaintextLength)
                , ref outgoingRecord
                );

            this.nextEpoch.State = HandshakeState.ExpectingServerHello;
            this.nextEpoch.NextPacketResendTime = DateTime.UtcNow + this.handshakeResendTimeout;
            base.WriteBytesToConnection(packet.GetUnderlyingArray(), packet.Length);
        }
Example #2
0
        /// <summary>
        /// Send (resend) the ClientKeyExchange flight
        /// </summary>
        /// <param name="isRetransmit">
        /// True if this is a retransmit of the flight. Otherwise,
        /// false
        /// </param>
        private void SendClientKeyExchangeFlight(bool isRetransmit)
        {
            // Describe our flight
            Handshake keyExchangeHandshake = new Handshake();

            keyExchangeHandshake.MessageType     = HandshakeType.ClientKeyExchange;
            keyExchangeHandshake.Length          = (ushort)this.nextEpoch.Handshake.CalculateClientMessageSize();
            keyExchangeHandshake.MessageSequence = 5;
            keyExchangeHandshake.FragmentOffset  = 0;
            keyExchangeHandshake.FragmentLength  = keyExchangeHandshake.Length;

            Record keyExchangeRecord = new Record();

            keyExchangeRecord.ContentType    = ContentType.Handshake;
            keyExchangeRecord.Epoch          = this.epoch;
            keyExchangeRecord.SequenceNumber = this.currentEpoch.NextOutgoingSequence;
            keyExchangeRecord.Length         = (ushort)this.currentEpoch.RecordProtection.GetEncryptedSize(Handshake.Size + (int)keyExchangeHandshake.Length);
            ++this.currentEpoch.NextOutgoingSequence;

            Record changeCipherSpecRecord = new Record();

            changeCipherSpecRecord.ContentType    = ContentType.ChangeCipherSpec;
            changeCipherSpecRecord.Epoch          = this.epoch;
            changeCipherSpecRecord.SequenceNumber = this.currentEpoch.NextOutgoingSequence;
            changeCipherSpecRecord.Length         = (ushort)this.currentEpoch.RecordProtection.GetEncryptedSize(ChangeCipherSpec.Size);
            ++this.currentEpoch.NextOutgoingSequence;

            Handshake finishedHandshake = new Handshake();

            finishedHandshake.MessageType     = HandshakeType.Finished;
            finishedHandshake.Length          = Finished.Size;
            finishedHandshake.MessageSequence = 6;
            finishedHandshake.FragmentOffset  = 0;
            finishedHandshake.FragmentLength  = finishedHandshake.Length;

            Record finishedRecord = new Record();

            finishedRecord.ContentType    = ContentType.Handshake;
            finishedRecord.Epoch          = this.nextEpoch.Epoch;
            finishedRecord.SequenceNumber = this.nextEpoch.NextOutgoingSequence;
            finishedRecord.Length         = (ushort)this.nextEpoch.RecordProtection.GetEncryptedSize(Handshake.Size + (int)finishedHandshake.Length);
            ++this.nextEpoch.NextOutgoingSequence;

            // Encode flight to wire format
            int packetLength = 0
                               + Record.Size + keyExchangeRecord.Length
                               + Record.Size + changeCipherSpecRecord.Length
                               + Record.Size + finishedRecord.Length;

            ;
            ByteSpan packet = new byte[packetLength];
            ByteSpan writer = packet;

            keyExchangeRecord.Encode(writer);
            writer = writer.Slice(Record.Size);
            keyExchangeHandshake.Encode(writer);
            writer = writer.Slice(Handshake.Size);
            this.nextEpoch.Handshake.EncodeClientKeyExchangeMessage(writer);

            ByteSpan startOfChangeCipherSpecRecord = packet.Slice(Record.Size + keyExchangeRecord.Length);

            writer = startOfChangeCipherSpecRecord;
            changeCipherSpecRecord.Encode(writer);
            writer = writer.Slice(Record.Size);
            ChangeCipherSpec.Encode(writer);
            writer = writer.Slice(ChangeCipherSpec.Size);

            ByteSpan startOfFinishedRecord = startOfChangeCipherSpecRecord.Slice(Record.Size + changeCipherSpecRecord.Length);

            writer = startOfFinishedRecord;
            finishedRecord.Encode(writer);
            writer = writer.Slice(Record.Size);
            finishedHandshake.Encode(writer);
            writer = writer.Slice(Handshake.Size);

            // Interject here to writer our client key exchange
            // message into the verification stream
            if (!isRetransmit)
            {
                this.nextEpoch.VerificationStream.Write(
                    packet.GetUnderlyingArray()
                    , Record.Size
                    , Handshake.Size + (int)keyExchangeHandshake.Length
                    );
            }

            // Calculate the hash of the verification stream
            ByteSpan handshakeHash;

            using (SHA256 sha256 = SHA256.Create())
            {
                this.nextEpoch.VerificationStream.Position = 0;
                handshakeHash = sha256.ComputeHash(this.nextEpoch.VerificationStream);
            }

            // Expand our master secret into Finished digests for the client and server
            PrfSha256.ExpandSecret(
                this.nextEpoch.ServerVerification
                , this.nextEpoch.MasterSecret
                , PrfLabel.SERVER_FINISHED
                , handshakeHash
                );

            PrfSha256.ExpandSecret(
                writer.Slice(0, Finished.Size)
                , this.nextEpoch.MasterSecret
                , PrfLabel.CLIENT_FINISHED
                , handshakeHash
                );
            writer = writer.Slice(Finished.Size);

            // Protect the ClientKeyExchange record
            this.currentEpoch.RecordProtection.EncryptClientPlaintext(
                packet.Slice(Record.Size, keyExchangeRecord.Length)
                , packet.Slice(Record.Size, Handshake.Size + (int)keyExchangeHandshake.Length)
                , ref keyExchangeRecord
                );

            // Protect the ChangeCipherSpec record
            this.currentEpoch.RecordProtection.EncryptClientPlaintext(
                startOfChangeCipherSpecRecord.Slice(Record.Size, changeCipherSpecRecord.Length)
                , startOfChangeCipherSpecRecord.Slice(Record.Size, ChangeCipherSpec.Size)
                , ref changeCipherSpecRecord
                );

            // Protect the Finished record
            this.nextEpoch.RecordProtection.EncryptClientPlaintext(
                startOfFinishedRecord.Slice(Record.Size, finishedRecord.Length)
                , startOfFinishedRecord.Slice(Record.Size, Handshake.Size + (int)finishedHandshake.Length)
                , ref finishedRecord
                );

            this.nextEpoch.State = HandshakeState.ExpectingChangeCipherSpec;
            this.nextEpoch.NextPacketResendTime = DateTime.UtcNow + this.handshakeResendTimeout;
            base.WriteBytesToConnection(packet.GetUnderlyingArray(), packet.Length);
        }
Example #3
0
        /// <summary>
        /// Process an incoming Handshake protocol message
        /// </summary>
        /// <param name="record">Parent record</param>
        /// <param name="message">Record payload</param>
        /// <returns>
        /// True if further processing of the underlying datagram
        /// should be continues. Otherwise, false.
        /// </returns>
        private bool ProcessHandshake(ref Record record, ByteSpan message)
        {
            // Each record may have multiple Handshake messages
            while (message.Length > 0)
            {
                ByteSpan originalPayload = message;

                Handshake handshake;
                if (!Handshake.Parse(out handshake, message))
                {
                    this.logger.WriteError("Dropping malformed handshake message");
                    return(false);
                }
                message = message.Slice(Handshake.Size);

                if (message.Length < handshake.Length)
                {
                    this.logger.WriteError($"Dropping malformed handshake message: AvailableBytes({message.Length}) Size({handshake.Length})");
                    return(false);
                }

                originalPayload = originalPayload.Slice(0, (int)(Handshake.Size + handshake.Length));
                ByteSpan payload = originalPayload.Slice(Handshake.Size);
                message = message.Slice((int)handshake.Length);

                // We only support fragmented Certificate messages
                // from the server
                if (handshake.MessageType != HandshakeType.Certificate && (handshake.FragmentOffset != 0 || handshake.FragmentLength != handshake.Length))
                {
                    this.logger.WriteError($"Dropping fragmented handshake message Type({handshake.MessageType}) Offset({handshake.FragmentOffset}) FragmentLength({handshake.FragmentLength}) Length({handshake.Length})");
                    continue;
                }

                switch (handshake.MessageType)
                {
                case HandshakeType.HelloVerifyRequest:
                    if (this.nextEpoch.State != HandshakeState.ExpectingServerHello)
                    {
                        this.logger.WriteError($"Dropping unexpected HelloVerifyRequest handshake message State({this.nextEpoch.State})");
                        continue;
                    }
                    else if (handshake.MessageSequence != 0)
                    {
                        this.logger.WriteError($"Dropping bad-sequence HelloVerifyRequest MessageSequence({handshake.MessageSequence})");
                        continue;
                    }

                    HelloVerifyRequest helloVerifyRequest;
                    if (!HelloVerifyRequest.Parse(out helloVerifyRequest, payload))
                    {
                        this.logger.WriteError("Dropping malformed HelloVerifyRequest handshake message");
                        continue;
                    }

                    // Save the cookie
                    this.nextEpoch.Cookie = new byte[helloVerifyRequest.Cookie.Length];
                    helloVerifyRequest.Cookie.CopyTo(this.nextEpoch.Cookie);

                    // Restart the handshake
                    this.SendClientHello();
                    break;

                case HandshakeType.ServerHello:
                    if (this.nextEpoch.State != HandshakeState.ExpectingServerHello)
                    {
                        this.logger.WriteError($"Dropping unexpected ServerHello handshake message State({this.nextEpoch.State})");
                        continue;
                    }
                    else if (handshake.MessageSequence != 1)
                    {
                        this.logger.WriteError($"Dropping bad-sequence ServerHello MessageSequence({handshake.MessageSequence})");
                        continue;
                    }

                    ServerHello serverHello;
                    if (!ServerHello.Parse(out serverHello, payload))
                    {
                        this.logger.WriteError("Dropping malformed ServerHello message");
                        continue;
                    }

                    switch (serverHello.CipherSuite)
                    {
                    case CipherSuite.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256:
                        this.nextEpoch.Handshake = new X25519EcdheRsaSha256(this.random);
                        break;

                    default:
                        this.logger.WriteError($"Dropping malformed ServerHello message. Unsupported CipherSuite({serverHello.CipherSuite})");
                        continue;
                    }

                    // Save server parameters
                    this.nextEpoch.SelectedCipherSuite = serverHello.CipherSuite;
                    serverHello.Random.CopyTo(this.nextEpoch.ServerRandom);
                    this.nextEpoch.State = HandshakeState.ExpectingCertificate;
                    this.nextEpoch.CertificateFragments.Clear();
                    this.nextEpoch.CertificatePayload = ByteSpan.Empty;

                    // Append ServerHelllo message to the verification stream
                    this.nextEpoch.VerificationStream.Write(
                        originalPayload.GetUnderlyingArray()
                        , originalPayload.Offset
                        , originalPayload.Length
                        );
                    break;

                case HandshakeType.Certificate:
                    if (this.nextEpoch.State != HandshakeState.ExpectingCertificate)
                    {
                        this.logger.WriteError($"Dropping unexpected Certificate handshake message State({this.nextEpoch.State})");
                        continue;
                    }
                    else if (handshake.MessageSequence != 2)
                    {
                        this.logger.WriteError($"Dropping bad-sequence Certificate MessageSequence({handshake.MessageSequence})");
                        continue;
                    }

                    // If this is a fragmented message
                    if (handshake.FragmentLength != handshake.Length)
                    {
                        if (this.nextEpoch.CertificatePayload.Length != handshake.Length)
                        {
                            this.nextEpoch.CertificatePayload = new byte[handshake.Length];
                            this.nextEpoch.CertificateFragments.Clear();
                        }

                        // Add this fragment
                        payload.CopyTo(this.nextEpoch.CertificatePayload.Slice((int)handshake.FragmentOffset, (int)handshake.FragmentLength));
                        this.nextEpoch.CertificateFragments.Add(new FragmentRange {
                            Offset = (int)handshake.FragmentOffset, Length = (int)handshake.FragmentLength
                        });
                        this.nextEpoch.CertificateFragments.Sort((FragmentRange lhs, FragmentRange rhs) => {
                            return(lhs.Offset.CompareTo(rhs.Offset));
                        });

                        // Have we completed the message?
                        int  currentOffset = 0;
                        bool valid         = true;
                        foreach (FragmentRange range in this.nextEpoch.CertificateFragments)
                        {
                            if (range.Offset != currentOffset)
                            {
                                valid = false;
                                break;
                            }

                            currentOffset += range.Length;
                        }

                        if (currentOffset != this.nextEpoch.CertificatePayload.Length)
                        {
                            valid = false;
                        }

                        // Still waiting on more fragments?
                        if (!valid)
                        {
                            continue;
                        }

                        // Replace the message payload, and continue
                        this.nextEpoch.CertificateFragments.Clear();
                        payload = this.nextEpoch.CertificatePayload;
                    }

                    X509Certificate2 certificate;
                    if (!Certificate.Parse(out certificate, payload))
                    {
                        this.logger.WriteError("Dropping malformed Certificate message");
                        continue;
                    }

                    // Verify the certificate is authenticate
                    if (!this.serverCertificates.Contains(certificate))
                    {
                        this.logger.WriteError("Dropping malformed Certificate message: Certificate not authentic");
                        continue;
                    }

                    RSA publicKey = certificate.PublicKey.Key as RSA;
                    if (publicKey == null)
                    {
                        this.logger.WriteError("Dropping malfomed Certificate message: Certificate is not RSA signed");
                        continue;
                    }

                    // Add the final Certificate message to the verification stream
                    Handshake fullCertificateHandhake = handshake;
                    fullCertificateHandhake.FragmentOffset = 0;
                    fullCertificateHandhake.FragmentLength = fullCertificateHandhake.Length;

                    byte[] serializedCertificateHandshake = new byte[Handshake.Size];
                    fullCertificateHandhake.Encode(serializedCertificateHandshake);
                    this.nextEpoch.VerificationStream.Write(serializedCertificateHandshake, 0, serializedCertificateHandshake.Length);
                    this.nextEpoch.VerificationStream.Write(payload.GetUnderlyingArray(), payload.Offset, payload.Length);

                    this.nextEpoch.ServerPublicKey = publicKey;
                    this.nextEpoch.State           = HandshakeState.ExpectingServerKeyExchange;
                    break;

                case HandshakeType.ServerKeyExchange:
                    if (this.nextEpoch.State != HandshakeState.ExpectingServerKeyExchange)
                    {
                        this.logger.WriteError($"Dropping unexpected ServerKeyExchange handshake message State({this.nextEpoch.State})");
                        continue;
                    }
                    else if (this.nextEpoch.ServerPublicKey == null)
                    {
                        ///NOTE(mendsley): This _should_ not
                        /// happen on a well-formed client
                        Debug.Assert(false, "How are we processing a ServerKeyExchange message without a server public key?");

                        this.logger.WriteError($"Dropping unexpected ServerKeyExchange handshake message: No server public key");
                        continue;
                    }
                    else if (this.nextEpoch.Handshake == null)
                    {
                        ///NOTE(mendsley): This _should_ not
                        /// happen on a well-formed client
                        Debug.Assert(false, "How did we receive a ServerKeyExchange message without a handshake instance?");

                        this.logger.WriteError($"Dropping unexpected ServerKeyExchange handshake message: No key agreement interface");
                        continue;
                    }
                    else if (handshake.MessageSequence != 3)
                    {
                        this.logger.WriteError($"Dropping bad-sequence ServerKeyExchange MessageSequence({handshake.MessageSequence})");
                        continue;
                    }

                    ByteSpan sharedSecret = new byte[this.nextEpoch.Handshake.SharedKeySize()];
                    if (!this.nextEpoch.Handshake.VerifyServerMessageAndGenerateSharedKey(sharedSecret, payload, this.nextEpoch.ServerPublicKey))
                    {
                        this.logger.WriteError("Dropping malformed ServerKeyExchangeMessage");
                        return(false);
                    }

                    // Generate the session master secret
                    ByteSpan randomSeed = new byte[2 * Random.Size];
                    this.nextEpoch.ClientRandom.CopyTo(randomSeed);
                    this.nextEpoch.ServerRandom.CopyTo(randomSeed.Slice(Random.Size));

                    const int MasterSecretSize = 48;
                    ByteSpan  masterSecret     = new byte[MasterSecretSize];
                    PrfSha256.ExpandSecret(
                        masterSecret
                        , sharedSecret
                        , PrfLabel.MASTER_SECRET
                        , randomSeed
                        );

                    // Create record protection for the upcoming epoch
                    switch (this.nextEpoch.SelectedCipherSuite)
                    {
                    case CipherSuite.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256:
                        this.nextEpoch.RecordProtection = new Aes128GcmRecordProtection(
                            masterSecret
                            , this.nextEpoch.ServerRandom
                            , this.nextEpoch.ClientRandom
                            );
                        break;

                    default:
                        ///NOTE(mendsley): this _should_ not
                        /// happen on a well-formed client.
                        Debug.Assert(false, "SeverHello processing already approved this ciphersuite");

                        this.logger.WriteError($"Dropping malformed ServerKeyExchangeMessage: Could not create record protection");
                        return(false);
                    }

                    this.nextEpoch.State        = HandshakeState.ExpectingServerHelloDone;
                    this.nextEpoch.MasterSecret = masterSecret;

                    // Append ServerKeyExchange to the verification stream
                    this.nextEpoch.VerificationStream.Write(
                        originalPayload.GetUnderlyingArray()
                        , originalPayload.Offset
                        , originalPayload.Length
                        );
                    break;

                case HandshakeType.ServerHelloDone:
                    if (this.nextEpoch.State != HandshakeState.ExpectingServerHelloDone)
                    {
                        this.logger.WriteError($"Dropping unexpected ServerHelloDone handshake message State({this.nextEpoch.State})");
                        continue;
                    }
                    else if (handshake.MessageSequence != 4)
                    {
                        this.logger.WriteError($"Dropping bad-sequence ServerHelloDone MessageSequence({handshake.MessageSequence})");
                        continue;
                    }

                    this.nextEpoch.State = HandshakeState.ExpectingChangeCipherSpec;

                    // Append ServerHelloDone to the verification stream
                    this.nextEpoch.VerificationStream.Write(
                        originalPayload.GetUnderlyingArray()
                        , originalPayload.Offset
                        , originalPayload.Length
                        );

                    this.SendClientKeyExchangeFlight(false);
                    break;

                case HandshakeType.Finished:
                    if (this.nextEpoch.State != HandshakeState.ExpectingFinished)
                    {
                        this.logger.WriteError($"Dropping unexpected Finished handshake message State({this.nextEpoch.State})");
                        continue;
                    }
                    else if (payload.Length != Finished.Size)
                    {
                        this.logger.WriteError($"Dropping malformed Finished handshake message Size({payload.Length})");
                        continue;
                    }
                    else if (handshake.MessageSequence != 7)
                    {
                        this.logger.WriteError($"Dropping bad-sequence Finished MessageSequence({handshake.MessageSequence})");
                        continue;
                    }

                    // Verify the digest from the server
                    if (1 != Crypto.Const.ConstantCompareSpans(payload, this.nextEpoch.ServerVerification))
                    {
                        this.logger.WriteError("Dropping non-verified Finished handshake message");
                        return(false);
                    }

                    ++this.nextEpoch.Epoch;
                    this.nextEpoch.State = HandshakeState.Established;
                    this.nextEpoch.NextPacketResendTime = DateTime.MinValue;
                    this.nextEpoch.ServerVerification.SecureClear();
                    this.nextEpoch.MasterSecret.SecureClear();

                    this.FlushQueuedApplicationData();
                    break;

                // Drop messages we do not support
                case HandshakeType.CertificateRequest:
                case HandshakeType.HelloRequest:
                    this.logger.WriteError($"Dropping unsupported handshake message MessageType({handshake.MessageType})");
                    break;

                // Drop messages that originate from the client
                case HandshakeType.ClientHello:
                case HandshakeType.ClientKeyExchange:
                case HandshakeType.CertificateVerify:
                    this.logger.WriteError($"Dropping client handshake message MessageType({handshake.MessageType})");
                    break;
                }
            }

            return(true);
        }