--- /dev/null
+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);
+ }
+ }
+}
<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" />
--- /dev/null
+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);
+ }
+ }
+ }
+}
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;
public ByteSpan ClientRandom;
public ByteSpan ServerRandom;
- public MemoryStream VerificationStream;
+ public Sha256Stream VerificationStream;
public ByteSpan ClientVerification;
public ByteSpan ServerVerification;
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];
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;
// 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);
}
// 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
// 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
+ )
);
}
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);
}
}
// 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
using System;
using System.Collections.Generic;
using System.Diagnostics;
-using System.IO;
using System.Net;
using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates;
public IRecordProtection RecordProtection;
public IHandshakeCipherSuite Handshake;
public ByteSpan Cookie;
- public MemoryStream VerificationStream;
+ public Sha256Stream VerificationStream;
public RSA ServerPublicKey;
public ByteSpan ClientRandom;
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();
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();
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:
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;
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:
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;
private void SendClientHello()
{
// Reset our verification stream
- this.nextEpoch.VerificationStream.SetLength(0);
+ this.nextEpoch.VerificationStream.Reset();
// Describe our ClientHello flight
ClientHello clientHello = new ClientHello();
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
// 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(
<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" />