]> git.deb.at Git - rhonda/impostor.hazel.git/commitdiff
Add x25519 version of ECDHE_RSA_256 cipher suite
authorMatthew Endsley <mendsley@gmail.com>
Sat, 19 Dec 2020 02:32:30 +0000 (18:32 -0800)
committerMatthew Endsley <mendsley@gmail.com>
Tue, 2 Feb 2021 16:53:31 +0000 (08:53 -0800)
This the portion of the cipher suite used during connection negotiation.

Hazel.UnitTests/Dtls/X25519EcdheRsaSha256Tests.cs [new file with mode: 0644]
Hazel.UnitTests/Hazel.UnitTests.csproj
Hazel/Dtls/Handshake.cs [new file with mode: 0644]
Hazel/Dtls/IHandshakeCipherSuite.cs
Hazel/Dtls/X25519EcdheRsaSha256.cs [new file with mode: 0644]
Hazel/Hazel.csproj

diff --git a/Hazel.UnitTests/Dtls/X25519EcdheRsaSha256Tests.cs b/Hazel.UnitTests/Dtls/X25519EcdheRsaSha256Tests.cs
new file mode 100644 (file)
index 0000000..33cd6dc
--- /dev/null
@@ -0,0 +1,226 @@
+using Hazel.Dtls;
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+using System.Security.Cryptography;
+
+namespace Hazel.UnitTests.Dtls
+{
+    [TestClass]
+    public class X25519EcdheRsaSha256Tests
+    {
+        private readonly RandomNumberGenerator random = RandomNumberGenerator.Create();
+        private readonly RSA privateKey = RSA.Create();
+        private readonly RSA publicKey;
+
+        public X25519EcdheRsaSha256Tests()
+        {
+            RSAParameters keyParameters = this.privateKey.ExportParameters(false);
+            this.publicKey = RSA.Create();
+            this.publicKey.ImportParameters(keyParameters);
+        }
+
+        [TestMethod]
+        public void SmallServerDataFails()
+        {
+            byte[] data;
+
+            using (X25519EcdheRsaSha256 cipherSuite = new X25519EcdheRsaSha256(this.random))
+            {
+                int expectedSize = cipherSuite.CalculateServerMessageSize(this.privateKey);
+                Assert.IsTrue(expectedSize/2 > 1);
+
+                data = new byte[expectedSize/2];
+                random.GetBytes(data);
+            }
+
+            using (X25519EcdheRsaSha256 cipherSuite = new X25519EcdheRsaSha256(this.random))
+            {
+                byte[] sharedKey = new byte[cipherSuite.SharedKeySize()];
+                Assert.IsFalse(cipherSuite.VerifyServerMessageAndGenerateSharedKey(sharedKey, data, this.publicKey));
+            }
+        }
+
+        [TestMethod]
+        public void LargeServerDataFails()
+        {
+            byte[] data;
+
+            using (X25519EcdheRsaSha256 cipherSuite = new X25519EcdheRsaSha256(this.random))
+            {
+                int expectedSize = cipherSuite.CalculateServerMessageSize(this.privateKey);
+                Assert.IsTrue(expectedSize > 0);
+
+                data = new byte[expectedSize * 2];
+                random.GetBytes(data);
+            }
+
+            using (X25519EcdheRsaSha256 cipherSuite = new X25519EcdheRsaSha256(this.random))
+            {
+                byte[] sharedKey = new byte[cipherSuite.SharedKeySize()];
+                Assert.IsFalse(cipherSuite.VerifyServerMessageAndGenerateSharedKey(sharedKey, data, this.publicKey));
+            }
+        }
+
+        [TestMethod]
+        public void RandomServerDataFails()
+        {
+            byte[] data;
+
+            using (X25519EcdheRsaSha256 cipherSuite = new X25519EcdheRsaSha256(this.random))
+            {
+                int expectedSize = cipherSuite.CalculateServerMessageSize(this.privateKey);
+                Assert.IsTrue(expectedSize > 0);
+
+                data = new byte[expectedSize];
+                random.GetBytes(data);
+            }
+
+            using (X25519EcdheRsaSha256 cipherSuite = new X25519EcdheRsaSha256(this.random))
+            {
+                byte[] sharedKey = new byte[cipherSuite.SharedKeySize()];
+                Assert.IsFalse(cipherSuite.VerifyServerMessageAndGenerateSharedKey(sharedKey, data, this.publicKey));
+            }
+        }
+
+        [TestMethod]
+        public void SmallClientDataFails()
+        {
+            byte[] data;
+
+            using (X25519EcdheRsaSha256 cipherSuite = new X25519EcdheRsaSha256(this.random))
+            {
+                int expectedSize = cipherSuite.CalculateClientMessageSize();
+                Assert.IsTrue(expectedSize / 2 > 1);
+
+                data = new byte[expectedSize / 2];
+                random.GetBytes(data);
+            }
+
+            using (X25519EcdheRsaSha256 cipherSuite = new X25519EcdheRsaSha256(this.random))
+            {
+                byte[] sharedKey = new byte[cipherSuite.SharedKeySize()];
+                Assert.IsFalse(cipherSuite.VerifyClientMessageAndGenerateSharedKey(sharedKey, data));
+            }
+        }
+
+        [TestMethod]
+        public void LargeClientDataFails()
+        {
+            byte[] data;
+
+            using (X25519EcdheRsaSha256 cipherSuite = new X25519EcdheRsaSha256(this.random))
+            {
+                int expectedSize = cipherSuite.CalculateClientMessageSize();
+                Assert.IsTrue(expectedSize > 0);
+
+                data = new byte[expectedSize * 2];
+                random.GetBytes(data);
+            }
+
+            using (X25519EcdheRsaSha256 cipherSuite = new X25519EcdheRsaSha256(this.random))
+            {
+                byte[] sharedKey = new byte[cipherSuite.SharedKeySize()];
+                Assert.IsFalse(cipherSuite.VerifyClientMessageAndGenerateSharedKey(sharedKey, data));
+            }
+        }
+
+        [TestMethod]
+        public void RandomClientDataFails()
+        {
+            byte[] data;
+
+            using (X25519EcdheRsaSha256 cipherSuite = new X25519EcdheRsaSha256(this.random))
+            {
+                int expectedSize = cipherSuite.CalculateClientMessageSize();
+                Assert.IsTrue(expectedSize > 0);
+
+                data = new byte[expectedSize];
+                random.GetBytes(data);
+            }
+
+            using (X25519EcdheRsaSha256 cipherSuite = new X25519EcdheRsaSha256(this.random))
+            {
+                byte[] sharedKey = new byte[cipherSuite.SharedKeySize()];
+                Assert.IsFalse(cipherSuite.VerifyClientMessageAndGenerateSharedKey(sharedKey, data));
+            }
+        }
+
+        [TestMethod]
+        public void RandomSignatureFails()
+        {
+            byte[] data;
+
+            using (X25519EcdheRsaSha256 cipherSuite = new X25519EcdheRsaSha256(this.random))
+            {
+                int expectedSize = cipherSuite.CalculateServerMessageSize(this.privateKey);
+                Assert.IsTrue(expectedSize > 0);
+
+                data = new byte[expectedSize];
+                cipherSuite.EncodeServerKeyExchangeMessage(data, this.privateKey);
+            }
+
+            // overwrite signature with random data
+            byte[] randomSignature = new byte[this.privateKey.KeySize/8];
+            random.GetBytes(randomSignature);
+            new ByteSpan(randomSignature).CopyTo(new ByteSpan(data, data.Length - randomSignature.Length, randomSignature.Length));
+
+            using (X25519EcdheRsaSha256 cipherSuite = new X25519EcdheRsaSha256(this.random))
+            {
+                byte[] sharedKey = new byte[cipherSuite.SharedKeySize()];
+                Assert.IsFalse(cipherSuite.VerifyServerMessageAndGenerateSharedKey(sharedKey, data, this.publicKey));
+            }
+        }
+
+        [TestMethod]
+        public void VerifySignature()
+        {
+            byte[] data;
+
+            using (X25519EcdheRsaSha256 cipherSuite = new X25519EcdheRsaSha256(this.random))
+            {
+                int expectedSize = cipherSuite.CalculateServerMessageSize(this.privateKey);
+                Assert.IsTrue(expectedSize > 0);
+
+                data = new byte[expectedSize];
+                cipherSuite.EncodeServerKeyExchangeMessage(data, this.privateKey);
+            }
+
+            using (X25519EcdheRsaSha256 cipherSuite = new X25519EcdheRsaSha256(this.random))
+            {
+                byte[] sharedKey = new byte[cipherSuite.SharedKeySize()];
+                Assert.IsTrue(cipherSuite.VerifyServerMessageAndGenerateSharedKey(sharedKey, data, this.publicKey));
+            }
+        }
+
+        [TestMethod]
+        public void GeneratesSameSharedKey()
+        {
+            byte[] serverSharedSecret;
+            byte[] clientSharedSecret;
+
+            using (X25519EcdheRsaSha256 serverCipherSuite = new X25519EcdheRsaSha256(this.random))
+            {
+                int expectedSize = serverCipherSuite.CalculateServerMessageSize(this.privateKey);
+                Assert.IsTrue(expectedSize > 0);
+
+                byte[] serverKeyExchangeMessage = new byte[expectedSize];
+                serverCipherSuite.EncodeServerKeyExchangeMessage(serverKeyExchangeMessage, this.privateKey);
+
+                byte[] clientKeyExchange;
+
+                using (X25519EcdheRsaSha256 clientCipherSuite = new X25519EcdheRsaSha256(this.random))
+                {
+                    clientSharedSecret = new byte[clientCipherSuite.SharedKeySize()];
+                    Assert.IsTrue(clientCipherSuite.VerifyServerMessageAndGenerateSharedKey(clientSharedSecret, serverKeyExchangeMessage, this.publicKey));
+
+                    clientKeyExchange = new byte[clientCipherSuite.CalculateClientMessageSize()];
+                    clientCipherSuite.EncodeClientKeyExchangeMessage(clientKeyExchange);
+                }
+
+                serverSharedSecret = new byte[serverCipherSuite.SharedKeySize()];
+                Assert.IsTrue(serverCipherSuite.VerifyClientMessageAndGenerateSharedKey(serverSharedSecret, clientKeyExchange));
+            }
+
+            CollectionAssert.AreEqual(serverSharedSecret, clientSharedSecret);
+        }
+    }
+}
index 0a97afc8c79e976b4b77525199a26105b93ddb1b..b52e6e51c28246a2916c923b39785db0e3a3f7c7 100644 (file)
@@ -59,6 +59,7 @@
     <Compile Include="BroadcastTests.cs" />
     <Compile Include="Crypto\AesGcmTest.cs" />
     <Compile Include="Crypto\X25519Tests.cs" />
+    <Compile Include="Dtls\X25519EcdheRsaSha256Tests.cs" />
     <Compile Include="MessageReaderTests.cs" />
     <Compile Include="StatisticsTests.cs" />
     <Compile Include="TestHelper.cs" />
diff --git a/Hazel/Dtls/Handshake.cs b/Hazel/Dtls/Handshake.cs
new file mode 100644 (file)
index 0000000..d9d8d0b
--- /dev/null
@@ -0,0 +1,20 @@
+namespace Hazel.Dtls
+{
+    /// <summary>
+    /// Named curves
+    /// </summary>
+    public enum NamedCurve : ushort
+    {
+        Reserved = 0,
+        secp256r1 = 23,
+        x25519 = 29,
+    }
+
+    /// <summary>
+    /// Elliptic curve type
+    /// </summary>
+    public enum ECCurveType : byte
+    {
+        NamedCurve = 3,
+    }
+}
index 4428e8512e96ab5db61d4bcea54f28027808e1d2..eedd9778b6af13439956bf63d70adb14136ca515 100644 (file)
@@ -17,8 +17,11 @@ namespace Hazel.Dtls
         /// <summary>
         /// Calculate the size of the ServerKeyExchnage message
         /// </summary>
+        /// <param name="privateKey">
+        /// Private key that will be used to sign the message
+        /// </param>
         /// <returns>Size of the message in bytes</returns>
-        int CalculateServerMessageSize();
+        int CalculateServerMessageSize(object privateKey);
 
         /// <summary>
         /// Encodes the ServerKeyExchange message
diff --git a/Hazel/Dtls/X25519EcdheRsaSha256.cs b/Hazel/Dtls/X25519EcdheRsaSha256.cs
new file mode 100644 (file)
index 0000000..505f2d2
--- /dev/null
@@ -0,0 +1,182 @@
+using Hazel.Crypto;
+using System;
+using System.Diagnostics;
+using System.Security.Cryptography;
+
+namespace Hazel.Dtls
+{
+    /// <summary>
+    /// ECDHE_RSA_*_256 cipher suite
+    /// </summary>
+    public class X25519EcdheRsaSha256 : IHandshakeCipherSuite
+    {
+        private readonly ByteSpan privateAgreementKey;
+        private SHA256 sha256 = SHA256.Create();
+
+        /// <summary>
+        /// Create a new instance of the x25519 key exchange
+        /// </summary>
+        /// <param name="random">Random data source</param>
+        public X25519EcdheRsaSha256(RandomNumberGenerator random)
+        {
+            byte[] buffer = new byte[X25519.KeySize];
+            random.GetBytes(buffer);
+            this.privateAgreementKey = buffer;
+        }
+
+        /// <inheritdoc />
+        public void Dispose()
+        {
+            this.sha256?.Dispose();
+            this.sha256 = null;
+        }
+
+        /// <inheritdoc />
+        public int SharedKeySize()
+        {
+            return X25519.KeySize;
+        }
+
+        /// <summary>
+        /// Calculate the server message size given an RSA key size
+        /// </summary>
+        /// <param name="keySize">
+        /// Size of the private key (in bits)
+        /// </param>
+        /// <returns>
+        /// Size of the ServerKeyExchange message in bytes
+        /// </returns>
+        private static int CalculateServerMessageSize(int keySize)
+        {
+            int signatureSize = keySize / 8;
+
+            return 0
+                + 1 // ECCurveType ServerKeyExchange.params.curve_params.curve_type
+                + 2 // NamedCurve ServerKeyExchange.params.curve_params.namedcurve
+                + 1 + X25519.KeySize // ECPoint ServerKeyExchange.params.public
+                + signatureSize // ServerKeyExchange.signed_params
+                ;
+        }
+
+        /// <inheritdoc />
+        public int CalculateServerMessageSize(object privateKey)
+        {
+            RSA rsaPrivateKey = privateKey as RSA;
+            if (rsaPrivateKey == null)
+            {
+                throw new ArgumentException("Invalid private key", nameof(privateKey));
+            }
+
+            return CalculateServerMessageSize(rsaPrivateKey.KeySize);
+        }
+
+        /// <inheritdoc />
+        public void EncodeServerKeyExchangeMessage(ByteSpan output, object privateKey)
+        {
+            RSA rsaPrivateKey = privateKey as RSA;
+            if (rsaPrivateKey == null)
+            {
+                throw new ArgumentException("Invalid private key", nameof(privateKey));
+            }
+
+            output[0] = (byte)ECCurveType.NamedCurve;
+            output.WriteBigEndian16((ushort)NamedCurve.x25519, 1);
+            output[3] = (byte)X25519.KeySize;
+            X25519.Func(output.Slice(4, X25519.KeySize), this.privateAgreementKey);
+
+            // Hash the key parameters
+            byte[] paramterDigest = this.sha256.ComputeHash(output.GetUnderlyingArray(), output.Offset, 4 + X25519.KeySize);
+
+            // Sign the paramter digest
+            RSAPKCS1SignatureFormatter signer = new RSAPKCS1SignatureFormatter(rsaPrivateKey);
+            signer.SetHashAlgorithm("SHA256");
+            ByteSpan signature = signer.CreateSignature(paramterDigest);
+
+            Debug.Assert(signature.Length == rsaPrivateKey.KeySize/8);
+            signature.CopyTo(output.Slice(4+X25519.KeySize));
+        }
+
+        /// <inheritdoc />
+        public bool VerifyServerMessageAndGenerateSharedKey(ByteSpan output, ByteSpan serverKeyExchangeMessage, object publicKey)
+        {
+            RSA rsaPublicKey = publicKey as RSA;
+            if (rsaPublicKey == null)
+            {
+                return false;
+            }
+            else if (output.Length != X25519.KeySize)
+            {
+                return false;
+            }
+
+            // Verify message is compatible with this cipher suite
+            if (serverKeyExchangeMessage.Length != CalculateServerMessageSize(rsaPublicKey.KeySize))
+            {
+                return false;
+            }
+            else if (serverKeyExchangeMessage[0] != (byte)ECCurveType.NamedCurve)
+            {
+                return false;
+            }
+            else if (serverKeyExchangeMessage.ReadBigEndian16(1) != (ushort)NamedCurve.x25519)
+            {
+                return false;
+            }
+            else if (serverKeyExchangeMessage[3] != X25519.KeySize)
+            {
+                return false;
+            }
+
+            ByteSpan keyParameters = serverKeyExchangeMessage.Slice(0, 4+X25519.KeySize);
+            ByteSpan othersPublicKey = keyParameters.Slice(4);
+            ByteSpan signature = serverKeyExchangeMessage.Slice(keyParameters.Length);
+
+            // Hash the key parameters
+            byte[] parameterDigest = this.sha256.ComputeHash(keyParameters.GetUnderlyingArray(), keyParameters.Offset, keyParameters.Length);
+
+            // Verify the signature
+            RSAPKCS1SignatureDeformatter verifier = new RSAPKCS1SignatureDeformatter(rsaPublicKey);
+            verifier.SetHashAlgorithm("SHA256");
+            if (!verifier.VerifySignature(parameterDigest, signature.ToArray()))
+            {
+                return false;
+            }
+
+            // Signature has been validated, generate the shared key
+            return X25519.Func(output, this.privateAgreementKey, othersPublicKey);
+        }
+
+        private static int ClientMessageSize = 0
+                + 1 + X25519.KeySize // ECPoint ClientKeyExchange.ecdh_Yc
+                ;
+
+        /// <inheritdoc />
+        public int CalculateClientMessageSize()
+        {
+            return ClientMessageSize;
+        }
+
+        /// <inheritdoc />
+        public void EncodeClientKeyExchangeMessage(ByteSpan output)
+        {
+            output[0] = (byte)X25519.KeySize;
+            X25519.Func(output.Slice(1), this.privateAgreementKey);
+        }
+
+        /// <inheritdoc />
+        public bool VerifyClientMessageAndGenerateSharedKey(ByteSpan output, ByteSpan clientKeyExchangeMessage)
+        {
+            if (clientKeyExchangeMessage.Length != ClientMessageSize)
+            {
+                return false;
+            }
+            else if (clientKeyExchangeMessage[0] != (byte)X25519.KeySize)
+            {
+                return false;
+            }
+
+            ByteSpan othersPublicKey = clientKeyExchangeMessage.Slice(1);
+            return X25519.Func(output, this.privateAgreementKey, othersPublicKey);
+        }
+    }
+}
index ac4ed51c08288c01f356cdd4cdb2ca1cd94e66c3..1f130f7d6407e96d5d82e1af33fa7a45dd6be92a 100644 (file)
@@ -78,7 +78,9 @@
     <Compile Include="Crypto\X25519.cs" />
     <Compile Include="DataReceivedEventArgs.cs" />
     <Compile Include="DisconnectedEventArgs.cs" />
+    <Compile Include="Dtls\Handshake.cs" />
     <Compile Include="Dtls\IHandshakeCipherSuite.cs" />
+    <Compile Include="Dtls\X25519EcdheRsaSha256.cs" />
     <Compile Include="FewerThreads\HazelThreadPool.cs" />
     <Compile Include="FewerThreads\ThreadLimitedUdpConnectionListener.cs" />
     <Compile Include="FewerThreads\ThreadLimitedUdpServerConnection.cs" />