/// <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); }
/// <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); }