From: Matthew Endsley Date: Wed, 3 Feb 2021 20:34:20 +0000 (-0800) Subject: ChangeChiperSpec records should have a single byte X-Git-Tag: 1.0.0~20^2~5 X-Git-Url: https://git.deb.at/?a=commitdiff_plain;h=2092d5fa4f6b4d6acfb8e3314022794349968332;p=rhonda%2Fimpostor.hazel.git ChangeChiperSpec records should have a single byte --- diff --git a/Hazel/Dtls/DtlsConnectionListener.cs b/Hazel/Dtls/DtlsConnectionListener.cs index b7a8056..55bcc77 100644 --- a/Hazel/Dtls/DtlsConnectionListener.cs +++ b/Hazel/Dtls/DtlsConnectionListener.cs @@ -42,6 +42,7 @@ namespace Hazel.Dtls public ulong PreviousSequenceWindowBitmask; public IRecordProtection RecordProtection; + public IRecordProtection PreviousRecordProtection; // Need to keep these around so we can re-transmit our // last handshake record flight @@ -112,6 +113,7 @@ namespace Hazel.Dtls this.CurrentEpoch.NextExpectedSequence = nextExpectedSequenceNumber; this.CurrentEpoch.PreviousSequenceWindowBitmask = 0; this.CurrentEpoch.RecordProtection = NullRecordProtection.Instance; + this.CurrentEpoch.PreviousRecordProtection = null; this.CurrentEpoch.ServerFinishedVerification.SecureClear(); this.CurrentEpoch.ExpectedClientFinishedVerification.SecureClear(); @@ -129,6 +131,7 @@ namespace Hazel.Dtls public void Dispose() { this.CurrentEpoch.RecordProtection?.Dispose(); + this.CurrentEpoch.PreviousRecordProtection?.Dispose(); this.NextEpoch.RecordProtection?.Dispose(); this.NextEpoch.Handshake?.Dispose(); this.NextEpoch.VerificationStream?.Dispose(); @@ -386,10 +389,18 @@ namespace Hazel.Dtls break; } + if (!ChangeCipherSpec.Parse(recordPayload)) + { + this.Logger.WriteError($"Dropping malformed ChangeCipherSpec message from `{peerAddress}`"); + break; + } + // Migrate to the next epoch peer.Epoch = peer.NextEpoch.Epoch; peer.CanHandleApplicationData = false; // Need a Finished message peer.CurrentEpoch.NextOutgoingSequenceForPreviousEpoch = peer.CurrentEpoch.NextOutgoingSequence; + peer.CurrentEpoch.PreviousRecordProtection?.Dispose(); + peer.CurrentEpoch.PreviousRecordProtection = peer.CurrentEpoch.RecordProtection; peer.CurrentEpoch.RecordProtection = peer.NextEpoch.RecordProtection; peer.CurrentEpoch.NextOutgoingSequence = 1; peer.CurrentEpoch.NextExpectedSequence = 1; @@ -594,11 +605,22 @@ namespace Hazel.Dtls { ///NOTE(mendsley): This _should_ not /// happen on a well-formed server. - Debug.Assert(false, "How do we have an established non-zero epoch without verify data"); + Debug.Assert(false, "How do we have an established non-zero epoch without verify data?"); this.Logger.WriteError($"Dropping Finished message (no verify data) from `{peerAddress}`"); return false; } + // Cannot process a Finished message without + // record protection for the previous epoch + else if (peer.CurrentEpoch.PreviousRecordProtection == null) + { + ///NOTE(mendsley): This _should_ not + /// happen on a well-formed server. + Debug.Assert(false, "How do we have an established non-zero epoch with record protection for the previous epoch?"); + + this.Logger.WriteError($"Dropping Finished message from `{peerAddress}`: No previous epoch record protection"); + return false; + } // Verify message sequence if (handshake.MessageSequence != 6) @@ -642,7 +664,7 @@ namespace Hazel.Dtls changeCipherSpecRecord.ContentType = ContentType.ChangeCipherSpec; changeCipherSpecRecord.Epoch = (ushort)(peer.Epoch - 1); changeCipherSpecRecord.SequenceNumber = peer.CurrentEpoch.NextOutgoingSequenceForPreviousEpoch; - changeCipherSpecRecord.Length = 0; + changeCipherSpecRecord.Length = (ushort)peer.CurrentEpoch.PreviousRecordProtection.GetEncryptedSize(ChangeCipherSpec.Size); ++peer.CurrentEpoch.NextOutgoingSequenceForPreviousEpoch; int plaintextFinishedPayloadSize = Handshake.Size + (int)outgoingHandshake.Length; @@ -654,20 +676,31 @@ namespace Hazel.Dtls ++peer.CurrentEpoch.NextOutgoingSequence; // Encode the flight into wire format - packet = new byte[Record.Size + Record.Size + finishedRecord.Length]; + packet = new byte[Record.Size + changeCipherSpecRecord.Length + Record.Size + finishedRecord.Length]; writer = packet; changeCipherSpecRecord.Encode(writer); writer = writer.Slice(Record.Size); + ChangeCipherSpec.Encode(writer); + + ByteSpan startOfFinishedRecord = packet.Slice(Record.Size + changeCipherSpecRecord.Length); + writer = startOfFinishedRecord; finishedRecord.Encode(writer); writer = writer.Slice(Record.Size); outgoingHandshake.Encode(writer); writer = writer.Slice(Handshake.Size); peer.CurrentEpoch.ServerFinishedVerification.CopyTo(writer); + // Protect the ChangeChipherSpec record + peer.CurrentEpoch.PreviousRecordProtection.EncryptServerPlaintext( + packet.Slice(Record.Size, changeCipherSpecRecord.Length) + , packet.Slice(Record.Size, ChangeCipherSpec.Size) + , ref changeCipherSpecRecord + ); + // Protect the Finished Handshake record peer.CurrentEpoch.RecordProtection.EncryptServerPlaintext( - packet.Slice(Record.Size + Record.Size, finishedRecord.Length) - , packet.Slice(Record.Size + Record.Size, plaintextFinishedPayloadSize) + startOfFinishedRecord.Slice(Record.Size, finishedRecord.Length) + , startOfFinishedRecord.Slice(Record.Size, plaintextFinishedPayloadSize) , ref finishedRecord ); diff --git a/Hazel/Dtls/DtlsUnityConnection.cs b/Hazel/Dtls/DtlsUnityConnection.cs index 79af8fd..6be3b19 100644 --- a/Hazel/Dtls/DtlsUnityConnection.cs +++ b/Hazel/Dtls/DtlsUnityConnection.cs @@ -429,6 +429,12 @@ namespace Hazel.Dtls break; } + if (!ChangeCipherSpec.Parse(recordPayload)) + { + this.logger.WriteError("Dropping malformed ChangeCipherSpec message"); + break; + } + // Migrate to the next epoch this.epoch = this.nextEpoch.Epoch; this.currentEpoch.RecordProtection = this.nextEpoch.RecordProtection; @@ -927,7 +933,7 @@ namespace Hazel.Dtls changeCipherSpecRecord.ContentType = ContentType.ChangeCipherSpec; changeCipherSpecRecord.Epoch = this.epoch; changeCipherSpecRecord.SequenceNumber = this.currentEpoch.NextOutgoingSequence; - changeCipherSpecRecord.Length = 0; + changeCipherSpecRecord.Length = (ushort)this.currentEpoch.RecordProtection.GetEncryptedSize(ChangeCipherSpec.Size); ++this.currentEpoch.NextOutgoingSequence; Handshake finishedHandshake = new Handshake(); @@ -947,7 +953,7 @@ namespace Hazel.Dtls // Encode flight to wire format int packetLength = 0 + Record.Size + keyExchangeRecord.Length - + Record.Size + + Record.Size + changeCipherSpecRecord.Length + Record.Size + finishedRecord.Length; ; ByteSpan packet = new byte[packetLength]; @@ -958,12 +964,16 @@ namespace Hazel.Dtls keyExchangeHandshake.Encode(writer); writer = writer.Slice(Handshake.Size); this.nextEpoch.Handshake.EncodeClientKeyExchangeMessage(writer); - writer = writer.Slice((int)keyExchangeHandshake.Length); + 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 = writer; + ByteSpan startOfFinishedRecord = startOfChangeCipherSpecRecord.Slice(Record.Size + changeCipherSpecRecord.Length); + writer = startOfFinishedRecord; finishedRecord.Encode(writer); writer = writer.Slice(Record.Size); finishedHandshake.Encode(writer); @@ -1011,6 +1021,13 @@ namespace Hazel.Dtls , 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) diff --git a/Hazel/Dtls/Record.cs b/Hazel/Dtls/Record.cs index 04369bb..27f65bb 100644 --- a/Hazel/Dtls/Record.cs +++ b/Hazel/Dtls/Record.cs @@ -73,4 +73,45 @@ namespace Hazel.Dtls span.WriteBigEndian16(this.Length, 11); } } + + public struct ChangeCipherSpec + { + public const int Size = 1; + + enum Value : byte + { + ChangeCipherSpec = 1, + } + + /// + /// Parse a ChangeCipherSpec record from wire format + /// + /// + /// True if we successfully parse the ChangeCipherSpec + /// record. Otherwise, false. + /// + public static bool Parse(ByteSpan span) + { + if (span.Length != 1) + { + return false; + } + + Value value = (Value)span[0]; + if (value != Value.ChangeCipherSpec) + { + return false; + } + + return true; + } + + /// + /// Encode a ChangeCipherSpec record to wire format + /// + public static void Encode(ByteSpan span) + { + span[0] = (byte)Value.ChangeCipherSpec; + } + } }