/// <summary> /// Flush any queued application data packets /// </summary> private void FlushQueuedApplicationData() { foreach (ByteSpan queuedSpan in this.queuedApplicationData) { Record outgoingRecord = new Record(); outgoingRecord.ContentType = ContentType.ApplicationData; outgoingRecord.Epoch = this.epoch; outgoingRecord.SequenceNumber = this.currentEpoch.NextOutgoingSequence; outgoingRecord.Length = (ushort)this.currentEpoch.RecordProtection.GetEncryptedSize(queuedSpan.Length); ++this.currentEpoch.NextOutgoingSequence; // Encode the record to wire format ByteSpan packet = new byte[Record.Size + outgoingRecord.Length]; ByteSpan writer = packet; outgoingRecord.Encode(writer); writer = writer.Slice(Record.Size); queuedSpan.CopyTo(writer); // Protect the record this.currentEpoch.RecordProtection.EncryptClientPlaintext( packet.Slice(Record.Size, outgoingRecord.Length) , packet.Slice(Record.Size, queuedSpan.Length) , ref outgoingRecord ); base.WriteBytesToConnection(packet.GetUnderlyingArray(), packet.Length); } this.queuedApplicationData.Clear(); }
/// <summary> /// Request from the application to write data to the DTLS /// stream. If appropriate, returns a byte span to send to /// the wire. /// </summary> /// <param name="bytes">Plaintext bytes to write</param> /// <param name="length">Length of the bytes to write</param> /// <returns> /// Encrypted data to put on the wire if appropriate, /// otherwise an empty span /// </returns> private ByteSpan WriteBytesToConnectionInternal(byte[] bytes, int length) { lock (this.syncRoot) { // If we're negotiating a new epoch, queue data if (this.nextEpoch.State != HandshakeState.Established) { ByteSpan copyOfSpan = new byte[length]; new ByteSpan(bytes, 0, length).CopyTo(copyOfSpan); this.queuedApplicationData.Add(copyOfSpan); return(ByteSpan.Empty); } // Send any queued application data now this.FlushQueuedApplicationData(); Record outgoinRecord = new Record(); outgoinRecord.ContentType = ContentType.ApplicationData; outgoinRecord.Epoch = this.epoch; outgoinRecord.SequenceNumber = this.currentEpoch.NextOutgoingSequence; outgoinRecord.Length = (ushort)this.currentEpoch.RecordProtection.GetEncryptedSize(length); ++this.currentEpoch.NextOutgoingSequence; // Encode the record to wire format ByteSpan packet = new byte[Record.Size + outgoinRecord.Length]; ByteSpan writer = packet; outgoinRecord.Encode(writer); writer = writer.Slice(Record.Size); new ByteSpan(bytes, 0, length).CopyTo(writer); // Protect the record this.currentEpoch.RecordProtection.EncryptClientPlaintext( packet.Slice(Record.Size, outgoinRecord.Length) , packet.Slice(Record.Size, length) , ref outgoinRecord ); return(packet); } }
private static void EncryptPlaintext(ByteSpan output, ByteSpan input, ref Record record, Aes128Gcm cipher, ByteSpan writeIV) { Debug.Assert(output.Length >= GetEncryptedSizeImpl(input.Length)); // Build GCM nonce (authenticated data) ByteSpan nonce = new byte[ImplicitNonceSize + ExplicitNonceSize]; writeIV.CopyTo(nonce); nonce.WriteBigEndian16(record.Epoch, ImplicitNonceSize); nonce.WriteBigEndian48(record.SequenceNumber, ImplicitNonceSize + 2); // Serialize record as additional data Record plaintextRecord = record; plaintextRecord.Length = (ushort)input.Length; ByteSpan associatedData = new byte[Record.Size]; plaintextRecord.Encode(associatedData); cipher.Seal(output, nonce, input, associatedData); }
/// <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); }
/// <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); }