]> git.deb.at Git - rhonda/impostor.hazel.git/commitdiff
ChangeChiperSpec records should have a single byte
authorMatthew Endsley <mendsley@gmail.com>
Wed, 3 Feb 2021 20:34:20 +0000 (12:34 -0800)
committerMatthew Endsley <mendsley@gmail.com>
Wed, 3 Feb 2021 22:40:59 +0000 (14:40 -0800)
Hazel/Dtls/DtlsConnectionListener.cs
Hazel/Dtls/DtlsUnityConnection.cs
Hazel/Dtls/Record.cs

index b7a805657234dd0f80764098a6c6269bb1e53b88..55bcc775d6bafbeb7c7336ab7c9b4076c0c9ce7b 100644 (file)
@@ -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
                         );
 
index 79af8fd999520f15ef793faa03a19427877802e6..6be3b19246f1101e862ed509741e69ab57037454 100644 (file)
@@ -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)
index 04369bb56e68bf5849f25cec909d25f485d8558c..27f65bbe2d6a89159cfee9dd90b37b52b3650b88 100644 (file)
@@ -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,
+        }
+
+        /// <summary>
+        /// Parse a ChangeCipherSpec record from wire format
+        /// </summary>
+        /// <returns>
+        /// True if we successfully parse the ChangeCipherSpec
+        /// record. Otherwise, false.
+        /// </returns>
+        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;
+        }
+
+        /// <summary>
+        /// Encode a ChangeCipherSpec record to wire format
+        /// </summary>
+        public static void Encode(ByteSpan span)
+        {
+            span[0] = (byte)Value.ChangeCipherSpec;
+        }
+    }
 }