Beispiel #1
0
        /// <summary>
        /// Parse a Handshake ServerHello payload from wire format
        /// </summary>
        /// <returns>
        /// True if we successfully decode the ServerHello
        /// message. Otherwise false.
        /// </returns>
        public static bool Parse(out ServerHello result, ByteSpan span)
        {
            result = new ServerHello();
            if (span.Length < Size)
            {
                return(false);
            }

            ProtocolVersion serverVersion = (ProtocolVersion)span.ReadBigEndian16();

            span = span.Slice(2);

            result.Random = span.Slice(0, Dtls.Random.Size);
            span          = span.Slice(Dtls.Random.Size);

            byte sessionKeySize = span[0];

            span = span.Slice(1 + sessionKeySize);

            result.CipherSuite = (CipherSuite)span.ReadBigEndian16();
            span = span.Slice(2);

            CompressionMethod compressionMethod = (CompressionMethod)span[0];

            if (compressionMethod != CompressionMethod.Null)
            {
                return(false);
            }

            return(true);
        }
Beispiel #2
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);
        }