]> git.deb.at Git - rhonda/impostor.hazel.git/commitdiff
Add Sha256Stream
authorMatthew Endsley <mendsley@gmail.com>
Mon, 12 Apr 2021 20:08:20 +0000 (13:08 -0700)
committerMatthew Endsley <mendsley@gmail.com>
Tue, 13 Apr 2021 17:42:57 +0000 (10:42 -0700)
This commit simply moves the MemoryStream/SHA256 combination into a
dedicated class and adds unit tests for SHA256 from the original FIPS
publication [1]

[1] https://csrc.nist.gov/csrc/media/publications/fips/180/2/archive/2002-08-01/documents/fips180-2withchangenotice.pdf

Hazel.UnitTests/Crypto/Sha256Tests.cs [new file with mode: 0644]
Hazel.UnitTests/Hazel.UnitTests.csproj
Hazel/Crypto/Sha256Stream.cs [new file with mode: 0644]
Hazel/Dtls/DtlsConnectionListener.cs
Hazel/Dtls/DtlsUnityConnection.cs
Hazel/Hazel.csproj

diff --git a/Hazel.UnitTests/Crypto/Sha256Tests.cs b/Hazel.UnitTests/Crypto/Sha256Tests.cs
new file mode 100644 (file)
index 0000000..f9d5840
--- /dev/null
@@ -0,0 +1,70 @@
+using Hazel.Crypto;
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+using System.Text;
+
+namespace Hazel.UnitTests.Crypto
+{
+    [TestClass]
+    public class Sha256Tests
+    {
+        [TestMethod]
+        public void TestOneBlockMessage()
+        {
+            ByteSpan message = Encoding.ASCII.GetBytes(
+                "abc"
+            );
+            byte[] expectedDigest = Utils.HexToBytes(
+                "ba7816bf 8f01cfea 414140de 5dae2223 b00361a3 96177a9c b410ff61 f20015ad"
+            );
+            byte[] actualDigest = new byte[Sha256Stream.DigestSize];
+
+            using (Sha256Stream sha256 = new Sha256Stream())
+            {
+                sha256.AddData(message);
+                sha256.CalculateHash(actualDigest);
+            }
+
+            CollectionAssert.AreEqual(expectedDigest, actualDigest);
+        }
+
+        [TestMethod]
+        public void TestMultiBlockMessage()
+        {
+            ByteSpan message = Encoding.ASCII.GetBytes(
+                "abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq"
+            );
+            byte[] expectedDigest = Utils.HexToBytes(
+                "248d6a61 d20638b8 e5c02693 0c3e6039 a33ce459 64ff2167 f6ecedd4 19db06c1"
+            );
+            byte[] actualDigest = new byte[Sha256Stream.DigestSize];
+
+            using (Sha256Stream sha256 = new Sha256Stream())
+            {
+                sha256.AddData(message);
+                sha256.CalculateHash(actualDigest);
+            }
+
+            CollectionAssert.AreEqual(expectedDigest, actualDigest);
+        }
+
+        [TestMethod]
+        public void TestLongMessage()
+        {
+            ByteSpan message = Encoding.ASCII.GetBytes(
+                new string('a', 1000000)
+            );
+            byte[] expectedDigest = Utils.HexToBytes(
+                "cdc76e5c 9914fb92 81a1c7e2 84d73e67 f1809a48 a497200e 046d39cc c7112cd0"
+            );
+            byte[] actualDigest = new byte[Sha256Stream.DigestSize];
+
+            using (Sha256Stream sha256 = new Sha256Stream())
+            {
+                sha256.AddData(message);
+                sha256.CalculateHash(actualDigest);
+            }
+
+            CollectionAssert.AreEqual(expectedDigest, actualDigest);
+        }
+    }
+}
index 19560dedc2ca4e12d75333b36333ef38ac59e15c..befd2522cd43426b74728204c6fcc654188d0ec7 100644 (file)
@@ -61,6 +61,7 @@
   <ItemGroup>
     <Compile Include="BroadcastTests.cs" />
     <Compile Include="Crypto\AesGcmTest.cs" />
+    <Compile Include="Crypto\Sha256Tests.cs" />
     <Compile Include="Crypto\X25519Tests.cs" />
     <Compile Include="Dtls\AesGcmRecordProtectedTests.cs" />
     <Compile Include="Dtls\ConnectionTests.cs" />
diff --git a/Hazel/Crypto/Sha256Stream.cs b/Hazel/Crypto/Sha256Stream.cs
new file mode 100644 (file)
index 0000000..8fb53cd
--- /dev/null
@@ -0,0 +1,81 @@
+using System;
+using System.IO;
+using System.Security.Cryptography;
+
+namespace Hazel.Crypto
+{
+    /// <summary>
+    /// Streams data into a SHA256 digest
+    /// </summary>
+    public class Sha256Stream : IDisposable
+    {
+        /// <summary>
+        /// Size of the SHA256 digest in bytes
+        /// </summary>
+        public const int DigestSize = 32;
+
+        private MemoryStream innerStream = new MemoryStream();
+
+        /// <summary>
+        /// Create a new instance of a SHA256 stream
+        /// </summary>
+        public Sha256Stream()
+        {
+        }
+
+        /// <summary>
+        /// Release resources associated with the stream
+        /// </summary>
+        public void Dispose()
+        {
+            if  (this.innerStream != null)
+            {
+                this.Reset();
+                this.innerStream.Dispose();
+                this.innerStream = null;
+            }
+
+            GC.SuppressFinalize(this);
+        }
+
+        /// <summary>
+        /// Reset the stream to its initial state
+        /// </summary>
+        public void Reset()
+        {
+            ByteSpan buffer = this.innerStream.GetBuffer();
+            buffer.SecureClear();
+
+            this.innerStream.SetLength(0);
+        }
+
+        /// <summary>
+        /// Add data to the stream
+        /// </summary>
+        public void AddData(ByteSpan data)
+        {
+            this.innerStream.Write(data.GetUnderlyingArray(), data.Offset, data.Length);
+        }
+
+        /// <summary>
+        /// Calculate the final hash of the stream data
+        /// </summary>
+        /// <param name="output">
+        /// Target span to which the hash will be written
+        /// </param>
+        public void CalculateHash(ByteSpan output)
+        {
+            if (output.Length != DigestSize)
+            {
+                throw new ArgumentException($"Expected a span of {DigestSize} bytes. Got a span of {output.Length} bytes", nameof(output));
+            }
+
+            using (SHA256 sha256 = SHA256.Create())
+            {
+                this.innerStream.Position = 0;
+                ByteSpan digest = sha256.ComputeHash(this.innerStream);
+                digest.CopyTo(output);
+            }
+        }
+    }
+}
index 330fd73438bc4b7f84746c62d01d5790515b6dc8..c6bf31dedaa34e53a8f3872bc43e19e36b113122 100644 (file)
@@ -2,7 +2,6 @@ using System;
 using System.Collections.Concurrent;
 using System.Collections.Generic;
 using System.Diagnostics;
-using System.IO;
 using System.Net;
 using System.Security.Cryptography;
 using System.Security.Cryptography.X509Certificates;
@@ -70,7 +69,7 @@ namespace Hazel.Dtls
             public ByteSpan ClientRandom;
             public ByteSpan ServerRandom;
 
-            public MemoryStream VerificationStream;
+            public Sha256Stream VerificationStream;
 
             public ByteSpan ClientVerification;
             public ByteSpan ServerVerification;
@@ -123,7 +122,7 @@ namespace Hazel.Dtls
                 this.NextEpoch.RecordProtection = null;
                 this.NextEpoch.ClientRandom = new byte[Random.Size];
                 this.NextEpoch.ServerRandom = new byte[Random.Size];
-                this.NextEpoch.VerificationStream = new MemoryStream();
+                this.NextEpoch.VerificationStream = new Sha256Stream();
                 this.NextEpoch.ClientVerification = new byte[Finished.Size];
                 this.NextEpoch.ServerVerification = new byte[Finished.Size];
 
@@ -428,7 +427,7 @@ namespace Hazel.Dtls
                             peer.NextEpoch.Handshake = null;
                             peer.NextEpoch.NextOutgoingSequence = 1;
                             peer.NextEpoch.RecordProtection = null;
-                            peer.NextEpoch.VerificationStream.SetLength(0);
+                            peer.NextEpoch.VerificationStream.Reset();
                             peer.NextEpoch.ClientVerification.SecureClear();
                             peer.NextEpoch.ServerVerification.SecureClear();
                             break;
@@ -534,11 +533,7 @@ namespace Hazel.Dtls
 
                         // Record incoming ClientKeyExchange message
                         // to verification stream
-                        peer.NextEpoch.VerificationStream.Write(
-                            originalMessage.GetUnderlyingArray()
-                            , originalMessage.Offset
-                            , originalMessage.Length
-                        );
+                        peer.NextEpoch.VerificationStream.AddData(originalMessage);
 
                         ByteSpan randomSeed = new byte[2 * Random.Size];
                         peer.NextEpoch.ClientRandom.CopyTo(randomSeed);
@@ -570,12 +565,8 @@ namespace Hazel.Dtls
                         }
 
                         // Generate verification signatures
-                        ByteSpan handshakeStreamHash;
-                        using (SHA256 sha256 = SHA256.Create())
-                        {
-                            peer.NextEpoch.VerificationStream.Position = 0;
-                            handshakeStreamHash = sha256.ComputeHash(peer.NextEpoch.VerificationStream);
-                        }
+                        ByteSpan handshakeStreamHash = new byte[Sha256Stream.DigestSize];
+                        peer.NextEpoch.VerificationStream.CalculateHash(handshakeStreamHash);
 
                         PrfSha256.ExpandSecret(
                             peer.NextEpoch.ClientVerification
@@ -860,10 +851,11 @@ namespace Hazel.Dtls
 
                 // Copy the original ClientHello
                 // handshake to our verification stream
-                peer.NextEpoch.VerificationStream.Write(
-                    originalMessage.GetUnderlyingArray()
-                    , originalMessage.Offset
-                    , Handshake.Size + (int)handshake.Length
+                peer.NextEpoch.VerificationStream.AddData(
+                    originalMessage.Slice(
+                        0
+                      , Handshake.Size + (int)handshake.Length
+                    )
                 );
             }
 
@@ -961,18 +953,10 @@ namespace Hazel.Dtls
                 certificateHandshake.Encode(writer);
                 writer = writer.Slice(Handshake.Size);
 
-                peer.NextEpoch.VerificationStream.Write(
-                    packet.GetUnderlyingArray()
-                    , packet.Offset
-                    , packet.Length
-                );
+                peer.NextEpoch.VerificationStream.AddData(packet);
                 foreach (ByteSpan span in this.encodedCertificates)
                 {
-                    peer.NextEpoch.VerificationStream.Write(
-                        span.GetUnderlyingArray()
-                        , span.Offset
-                        , span.Length
-                    );
+                    peer.NextEpoch.VerificationStream.AddData(span);
                 }
             }
 
@@ -1050,11 +1034,12 @@ namespace Hazel.Dtls
             // Record record payload for verification
             if (recordMessagesForVerifyData)
             {
-                peer.NextEpoch.VerificationStream.Write(
-                    packet.GetUnderlyingArray()
-                    , packet.Offset + Record.Size
-                    , finalRecordPayloadSize
-                 );
+                peer.NextEpoch.VerificationStream.AddData(
+                    packet.Slice(
+                          packet.Offset + Record.Size
+                        , finalRecordPayloadSize
+                    )
+                );
             }
 
             // Protect final record of the flight
index eed6ba7ec6136287575e5aca94ca937bf0ab63d3..a11bd0dc63c36abb851843ca886a38c764603b6d 100644 (file)
@@ -3,7 +3,6 @@ using Hazel.Udp;
 using System;
 using System.Collections.Generic;
 using System.Diagnostics;
-using System.IO;
 using System.Net;
 using System.Security.Cryptography;
 using System.Security.Cryptography.X509Certificates;
@@ -69,7 +68,7 @@ namespace Hazel.Dtls
             public IRecordProtection RecordProtection;
             public IHandshakeCipherSuite Handshake;
             public ByteSpan Cookie;
-            public MemoryStream VerificationStream;
+            public Sha256Stream VerificationStream;
             public RSA ServerPublicKey;
 
             public ByteSpan ClientRandom;
@@ -178,7 +177,7 @@ namespace Hazel.Dtls
             this.nextEpoch.Handshake = null;
             this.nextEpoch.Cookie = ByteSpan.Empty;
             this.nextEpoch.VerificationStream?.Dispose();
-            this.nextEpoch.VerificationStream = new MemoryStream();
+            this.nextEpoch.VerificationStream = new Sha256Stream();
             this.nextEpoch.ServerPublicKey = null;
             this.nextEpoch.ServerRandom.SecureClear();
             this.nextEpoch.ClientRandom.SecureClear();
@@ -480,7 +479,7 @@ namespace Hazel.Dtls
                         this.nextEpoch.RecordProtection = null;
                         this.nextEpoch.Handshake?.Dispose();
                         this.nextEpoch.Cookie = ByteSpan.Empty;
-                        this.nextEpoch.VerificationStream.SetLength(0);
+                        this.nextEpoch.VerificationStream.Reset();
                         this.nextEpoch.ServerPublicKey = null;
                         this.nextEpoch.ServerRandom.SecureClear();
                         this.nextEpoch.ClientRandom.SecureClear();
@@ -620,11 +619,7 @@ namespace Hazel.Dtls
                         this.nextEpoch.CertificatePayload = ByteSpan.Empty;
 
                         // Append ServerHelllo message to the verification stream
-                        this.nextEpoch.VerificationStream.Write(
-                              originalPayload.GetUnderlyingArray()
-                            , originalPayload.Offset
-                            , originalPayload.Length
-                        );
+                        this.nextEpoch.VerificationStream.AddData(originalPayload);
                         break;
 
                     case HandshakeType.Certificate:
@@ -713,8 +708,8 @@ namespace Hazel.Dtls
 
                         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.VerificationStream.AddData(serializedCertificateHandshake);
+                        this.nextEpoch.VerificationStream.AddData(payload);
 
                         this.nextEpoch.ServerPublicKey = publicKey;
                         this.nextEpoch.State = HandshakeState.ExpectingServerKeyExchange;
@@ -795,11 +790,7 @@ namespace Hazel.Dtls
                         this.nextEpoch.MasterSecret = masterSecret;
 
                         // Append ServerKeyExchange to the verification stream
-                        this.nextEpoch.VerificationStream.Write(
-                              originalPayload.GetUnderlyingArray()
-                            , originalPayload.Offset
-                            , originalPayload.Length
-                        );
+                        this.nextEpoch.VerificationStream.AddData(originalPayload);
                         break;
 
                     case HandshakeType.ServerHelloDone:
@@ -817,11 +808,7 @@ namespace Hazel.Dtls
                         this.nextEpoch.State = HandshakeState.ExpectingChangeCipherSpec;
 
                         // Append ServerHelloDone to the verification stream
-                        this.nextEpoch.VerificationStream.Write(
-                              originalPayload.GetUnderlyingArray()
-                            , originalPayload.Offset
-                            , originalPayload.Length
-                        );
+                        this.nextEpoch.VerificationStream.AddData(originalPayload);
 
                         this.SendClientKeyExchangeFlight(false);
                         break;
@@ -883,7 +870,7 @@ namespace Hazel.Dtls
         private void SendClientHello()
         {
             // Reset our verification stream
-            this.nextEpoch.VerificationStream.SetLength(0);
+            this.nextEpoch.VerificationStream.Reset();
 
             // Describe our ClientHello flight
             ClientHello clientHello = new ClientHello();
@@ -920,10 +907,11 @@ namespace Hazel.Dtls
             clientHello.Encode(writer);
 
             // Write ClientHello to the verification stream
-            this.nextEpoch.VerificationStream.Write(
-                  packet.GetUnderlyingArray()
-                , Record.Size
-                , Handshake.Size + (int)handshake.Length
+            this.nextEpoch.VerificationStream.AddData(
+                packet.Slice(
+                      Record.Size
+                    , Handshake.Size + (int)handshake.Length
+                )
             );
 
             // Protect the record
@@ -1016,20 +1004,17 @@ namespace Hazel.Dtls
             // message into the verification stream
             if (!isRetransmit)
             {
-                this.nextEpoch.VerificationStream.Write(
-                      packet.GetUnderlyingArray()
-                    , Record.Size
-                    , Handshake.Size + (int)keyExchangeHandshake.Length
+                this.nextEpoch.VerificationStream.AddData(
+                    packet.Slice(
+                          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);
-            }
+            ByteSpan handshakeHash = new byte[Sha256Stream.DigestSize];
+            this.nextEpoch.VerificationStream.CalculateHash(handshakeHash);
 
             // Expand our master secret into Finished digests for the client and server
             PrfSha256.ExpandSecret(
index 9b15a5a4795f3eecf3e031561b7242d245184248..c94b972e332b9e476cf5cd7a8b3fd52ec2f3014b 100644 (file)
@@ -75,6 +75,7 @@
     <Compile Include="ConnectionState.cs" />
     <Compile Include="Crypto\AesGcm.cs" />
     <Compile Include="Crypto\Const.cs" />
+    <Compile Include="Crypto\Sha256Stream.cs" />
     <Compile Include="Crypto\SpanCryptoExtensions.cs" />
     <Compile Include="Crypto\X25519.cs" />
     <Compile Include="DataReceivedEventArgs.cs" />