From 23e6503fcccf259bf438a5e59e4a95a82f578e01 Mon Sep 17 00:00:00 2001 From: Matthew Endsley Date: Mon, 12 Apr 2021 13:08:20 -0700 Subject: [PATCH] Add Sha256Stream 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 | 70 ++++++++++++++++++++++ Hazel.UnitTests/Hazel.UnitTests.csproj | 1 + Hazel/Crypto/Sha256Stream.cs | 81 ++++++++++++++++++++++++++ Hazel/Dtls/DtlsConnectionListener.cs | 53 ++++++----------- Hazel/Dtls/DtlsUnityConnection.cs | 57 +++++++----------- Hazel/Hazel.csproj | 1 + 6 files changed, 193 insertions(+), 70 deletions(-) create mode 100644 Hazel.UnitTests/Crypto/Sha256Tests.cs create mode 100644 Hazel/Crypto/Sha256Stream.cs diff --git a/Hazel.UnitTests/Crypto/Sha256Tests.cs b/Hazel.UnitTests/Crypto/Sha256Tests.cs new file mode 100644 index 0000000..f9d5840 --- /dev/null +++ b/Hazel.UnitTests/Crypto/Sha256Tests.cs @@ -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); + } + } +} diff --git a/Hazel.UnitTests/Hazel.UnitTests.csproj b/Hazel.UnitTests/Hazel.UnitTests.csproj index 19560de..befd252 100644 --- a/Hazel.UnitTests/Hazel.UnitTests.csproj +++ b/Hazel.UnitTests/Hazel.UnitTests.csproj @@ -61,6 +61,7 @@ + diff --git a/Hazel/Crypto/Sha256Stream.cs b/Hazel/Crypto/Sha256Stream.cs new file mode 100644 index 0000000..8fb53cd --- /dev/null +++ b/Hazel/Crypto/Sha256Stream.cs @@ -0,0 +1,81 @@ +using System; +using System.IO; +using System.Security.Cryptography; + +namespace Hazel.Crypto +{ + /// + /// Streams data into a SHA256 digest + /// + public class Sha256Stream : IDisposable + { + /// + /// Size of the SHA256 digest in bytes + /// + public const int DigestSize = 32; + + private MemoryStream innerStream = new MemoryStream(); + + /// + /// Create a new instance of a SHA256 stream + /// + public Sha256Stream() + { + } + + /// + /// Release resources associated with the stream + /// + public void Dispose() + { + if (this.innerStream != null) + { + this.Reset(); + this.innerStream.Dispose(); + this.innerStream = null; + } + + GC.SuppressFinalize(this); + } + + /// + /// Reset the stream to its initial state + /// + public void Reset() + { + ByteSpan buffer = this.innerStream.GetBuffer(); + buffer.SecureClear(); + + this.innerStream.SetLength(0); + } + + /// + /// Add data to the stream + /// + public void AddData(ByteSpan data) + { + this.innerStream.Write(data.GetUnderlyingArray(), data.Offset, data.Length); + } + + /// + /// Calculate the final hash of the stream data + /// + /// + /// Target span to which the hash will be written + /// + 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); + } + } + } +} diff --git a/Hazel/Dtls/DtlsConnectionListener.cs b/Hazel/Dtls/DtlsConnectionListener.cs index 330fd73..c6bf31d 100644 --- a/Hazel/Dtls/DtlsConnectionListener.cs +++ b/Hazel/Dtls/DtlsConnectionListener.cs @@ -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 diff --git a/Hazel/Dtls/DtlsUnityConnection.cs b/Hazel/Dtls/DtlsUnityConnection.cs index eed6ba7..a11bd0d 100644 --- a/Hazel/Dtls/DtlsUnityConnection.cs +++ b/Hazel/Dtls/DtlsUnityConnection.cs @@ -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( diff --git a/Hazel/Hazel.csproj b/Hazel/Hazel.csproj index 9b15a5a..c94b972 100644 --- a/Hazel/Hazel.csproj +++ b/Hazel/Hazel.csproj @@ -75,6 +75,7 @@ + -- 2.39.5