Support raw ECDH key agreements
authorKevin Jones <kevin@vcsjones.com>
Fri, 3 Mar 2023 14:54:13 +0000 (09:54 -0500)
committerGitHub <noreply@github.com>
Fri, 3 Mar 2023 14:54:13 +0000 (09:54 -0500)
20 files changed:
src/libraries/Common/src/Interop/Windows/BCrypt/Cng.cs
src/libraries/Common/src/Interop/Windows/NCrypt/Interop.NCryptDeriveKeyMaterial.cs
src/libraries/Common/src/System/Security/Cryptography/ECDiffieHellmanAndroid.Derive.cs
src/libraries/Common/src/System/Security/Cryptography/ECDiffieHellmanCng.cs
src/libraries/Common/src/System/Security/Cryptography/ECDiffieHellmanOpenSsl.Derive.cs
src/libraries/Common/src/System/Security/Cryptography/ECDiffieHellmanSecurityTransforms.cs
src/libraries/Common/tests/System/Security/Cryptography/AlgorithmImplementations/ECDiffieHellman/ECDiffieHellmanFactory.cs
src/libraries/Common/tests/System/Security/Cryptography/AlgorithmImplementations/ECDiffieHellman/ECDiffieHellmanTests.NistValidation.cs
src/libraries/Common/tests/System/Security/Cryptography/AlgorithmImplementations/ECDiffieHellman/ECDiffieHellmanTests.Raw.cs [new file with mode: 0644]
src/libraries/System.Security.Cryptography.Cng/tests/ECDiffieHellmanCngProvider.cs
src/libraries/System.Security.Cryptography.Cng/tests/System.Security.Cryptography.Cng.Tests.csproj
src/libraries/System.Security.Cryptography.OpenSsl/tests/EcDiffieHellmanOpenSslProvider.cs
src/libraries/System.Security.Cryptography.OpenSsl/tests/System.Security.Cryptography.OpenSsl.Tests.csproj
src/libraries/System.Security.Cryptography/ref/System.Security.Cryptography.cs
src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/ECDiffieHellman.cs
src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/ECDiffieHellmanWrapper.cs
src/libraries/System.Security.Cryptography/tests/DefaultECDiffieHellmanProvider.Android.cs
src/libraries/System.Security.Cryptography/tests/DefaultECDiffieHellmanProvider.Unix.cs
src/libraries/System.Security.Cryptography/tests/DefaultECDiffieHellmanProvider.Windows.cs
src/libraries/System.Security.Cryptography/tests/System.Security.Cryptography.Tests.csproj

index 4c0fe5e..31e8276 100644 (file)
@@ -45,6 +45,7 @@ namespace Internal.NativeCrypto
             public const string Hash = "HASH";                  // BCRYPT_KDF_HASH
             public const string Hmac = "HMAC";                  // BCRYPT_KDF_HMAC
             public const string Tls = "TLS_PRF";                // BCRYPT_KDF_TLS_PRF
+            public const string Raw = "TRUNCATE";               // BCRYPT_KDF_RAW_SECRET
         }
     }
 
index cb29083..0bb0ba1 100644 (file)
@@ -241,5 +241,25 @@ internal static partial class Interop
                     flags);
             }
         }
+
+        internal static unsafe byte[] DeriveKeyMaterialTruncate(
+            SafeNCryptSecretHandle secretAgreement,
+            SecretAgreementFlags flags)
+        {
+            if (!OperatingSystem.IsWindowsVersionAtLeast(10))
+            {
+                throw new PlatformNotSupportedException();
+            }
+
+            byte[] result = DeriveKeyMaterial(
+                secretAgreement,
+                BCryptNative.KeyDerivationFunction.Raw,
+                ReadOnlySpan<NCryptBuffer>.Empty,
+                flags);
+
+            // Win32 returns the result as little endian. So we need to flip it to big endian.
+            Array.Reverse(result);
+            return result;
+        }
     }
 }
index 7002da7..c3f9fbe 100644 (file)
@@ -72,6 +72,16 @@ namespace System.Security.Cryptography
                     DeriveSecretAgreement);
             }
 
+            public override byte[] DeriveRawSecretAgreement(ECDiffieHellmanPublicKey otherPartyPublicKey)
+            {
+                ArgumentNullException.ThrowIfNull(otherPartyPublicKey);
+                ThrowIfDisposed();
+
+                byte[]? secretAgreement = DeriveSecretAgreement(otherPartyPublicKey, hasher: null);
+                Debug.Assert(secretAgreement is not null);
+                return secretAgreement;
+            }
+
             /// <summary>
             /// Get the secret agreement generated between two parties
             /// </summary>
index a25cfcc..df4d0c6 100644 (file)
@@ -130,5 +130,18 @@ namespace System.Security.Cryptography
                     Interop.NCrypt.SecretAgreementFlags.None);
             }
         }
+
+        /// <inheritdoc />
+        public override byte[] DeriveRawSecretAgreement(ECDiffieHellmanPublicKey otherPartyPublicKey)
+        {
+            ArgumentNullException.ThrowIfNull(otherPartyPublicKey);
+
+            using (SafeNCryptSecretHandle secretAgreement = DeriveSecretAgreementHandle(otherPartyPublicKey))
+            {
+                return Interop.NCrypt.DeriveKeyMaterialTruncate(
+                    secretAgreement,
+                    Interop.NCrypt.SecretAgreementFlags.None);
+            }
+        }
     }
 }
index 6c4a212..f6fc787 100644 (file)
@@ -69,6 +69,17 @@ namespace System.Security.Cryptography
                 DeriveSecretAgreement);
         }
 
+        /// <inheritdoc />
+        public override byte[] DeriveRawSecretAgreement(ECDiffieHellmanPublicKey otherPartyPublicKey)
+        {
+            ArgumentNullException.ThrowIfNull(otherPartyPublicKey);
+            ThrowIfDisposed();
+
+            byte[]? secretAgreement = DeriveSecretAgreement(otherPartyPublicKey, hasher: null);
+            Debug.Assert(secretAgreement is not null);
+            return secretAgreement;
+        }
+
         /// <summary>
         /// Get the secret agreement generated between two parties
         /// </summary>
index c902351..cad96d1 100644 (file)
@@ -165,6 +165,16 @@ namespace System.Security.Cryptography
                     DeriveSecretAgreement);
             }
 
+            public override byte[] DeriveRawSecretAgreement(ECDiffieHellmanPublicKey otherPartyPublicKey)
+            {
+                ArgumentNullException.ThrowIfNull(otherPartyPublicKey);
+                ThrowIfDisposed();
+
+                byte[]? secretAgreement = DeriveSecretAgreement(otherPartyPublicKey, hasher: null);
+                Debug.Assert(secretAgreement is not null);
+                return secretAgreement;
+            }
+
             private byte[]? DeriveSecretAgreement(ECDiffieHellmanPublicKey otherPartyPublicKey, IncrementalHash? hasher)
             {
                 if (!(otherPartyPublicKey is ECDiffieHellmanSecurityTransformsPublicKey secTransPubKey))
index aec1b09..6497640 100644 (file)
@@ -13,6 +13,7 @@ namespace System.Security.Cryptography.EcDiffieHellman.Tests
         bool IsCurveValid(Oid oid);
         bool ExplicitCurvesSupported { get; }
         bool CanDeriveNewPublicKey { get; }
+        bool SupportsRawDerivation { get; }
     }
 
     public static partial class ECDiffieHellmanFactory
@@ -42,5 +43,7 @@ namespace System.Security.Cryptography.EcDiffieHellman.Tests
         public static bool ExplicitCurvesSupported => s_provider.ExplicitCurvesSupported;
 
         public static bool CanDeriveNewPublicKey => s_provider.CanDeriveNewPublicKey;
+
+        public static bool SupportsRawDerivation => s_provider.SupportsRawDerivation;
     }
 }
index 7e5d408..d79d8de 100644 (file)
@@ -223,9 +223,19 @@ namespace System.Security.Cryptography.EcDiffieHellman.Tests
             HashAlgorithmName zHashAlgorithm,
             byte[] iutZ)
         {
-            byte[] result = iut.DeriveKeyFromHash(cavsPublic, zHashAlgorithm);
+            byte[] deriveHash = iut.DeriveKeyFromHash(cavsPublic, zHashAlgorithm);
             byte[] hashedZ = zHasher.ComputeHash(iutZ);
-            Assert.Equal(hashedZ.ByteArrayToHex(), result.ByteArrayToHex());
+            Assert.Equal(hashedZ.ByteArrayToHex(), deriveHash.ByteArrayToHex());
+
+            if (ECDiffieHellmanFactory.SupportsRawDerivation)
+            {
+                byte[] rawDerived = iut.DeriveRawSecretAgreement(cavsPublic);
+                Assert.Equal(iutZ.ByteArrayToHex(), rawDerived.ByteArrayToHex());
+            }
+            else
+            {
+                Assert.Throws<PlatformNotSupportedException>(() => iut.DeriveRawSecretAgreement(cavsPublic));
+            }
         }
     }
 #endif
diff --git a/src/libraries/Common/tests/System/Security/Cryptography/AlgorithmImplementations/ECDiffieHellman/ECDiffieHellmanTests.Raw.cs b/src/libraries/Common/tests/System/Security/Cryptography/AlgorithmImplementations/ECDiffieHellman/ECDiffieHellmanTests.Raw.cs
new file mode 100644 (file)
index 0000000..d8d4342
--- /dev/null
@@ -0,0 +1,94 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System;
+using System.Security.Cryptography;
+using Xunit;
+
+namespace System.Security.Cryptography.EcDiffieHellman.Tests
+{
+    public partial class ECDiffieHellmanTests
+    {
+        public static bool DoesNotSupportRawDerivation => !ECDiffieHellmanFactory.SupportsRawDerivation;
+
+        [ConditionalFact(typeof(ECDiffieHellmanFactory), nameof(ECDiffieHellmanFactory.SupportsRawDerivation))]
+        public static void RawDerivation_OtherKeyRequired()
+        {
+            using (ECDiffieHellman ecdh = ECDiffieHellmanFactory.Create())
+            {
+                AssertExtensions.Throws<ArgumentNullException>(
+                    "otherPartyPublicKey",
+                    () => ecdh.DeriveRawSecretAgreement(null));
+            }
+        }
+
+        [ConditionalTheory(typeof(ECDiffieHellmanFactory), nameof(ECDiffieHellmanFactory.SupportsRawDerivation))]
+        [MemberData(nameof(MismatchedKeysizes))]
+        public static void RawDerivation_SameSizeOtherKeyRequired(int aliceSize, int bobSize)
+        {
+            using (ECDiffieHellman alice = ECDiffieHellmanFactory.Create(aliceSize))
+            using (ECDiffieHellman bob = ECDiffieHellmanFactory.Create(bobSize))
+            using (ECDiffieHellmanPublicKey bobPublic = bob.PublicKey)
+            {
+                AssertExtensions.Throws<ArgumentException>(
+                    "otherPartyPublicKey",
+                    () => alice.DeriveRawSecretAgreement(bobPublic));
+            }
+        }
+
+        [ConditionalTheory(typeof(ECDiffieHellmanFactory), nameof(ECDiffieHellmanFactory.SupportsRawDerivation))]
+        [MemberData(nameof(EveryKeysize))]
+        public static void RawDerivation_DeriveSharedSecret_Agree(int keySize)
+        {
+            using (ECDiffieHellman alice = ECDiffieHellmanFactory.Create(keySize))
+            using (ECDiffieHellman bob = ECDiffieHellmanFactory.Create(keySize))
+            using (ECDiffieHellmanPublicKey alicePublic = alice.PublicKey)
+            using (ECDiffieHellmanPublicKey bobPublic = bob.PublicKey)
+            {
+                byte[] aliceDerived = alice.DeriveRawSecretAgreement(bobPublic);
+                byte[] bobDerived = bob.DeriveRawSecretAgreement(alicePublic);
+                Assert.Equal(aliceDerived, bobDerived);
+            }
+        }
+
+        [ConditionalFact(typeof(ECDiffieHellmanFactory), nameof(ECDiffieHellmanFactory.SupportsRawDerivation))]
+        public static void RawDerivation_DeriveSharedSecret_Disagree()
+        {
+            using (ECDiffieHellman alice = ECDiffieHellmanFactory.Create(ECCurve.NamedCurves.nistP256))
+            using (ECDiffieHellman bob = ECDiffieHellmanFactory.Create(ECCurve.NamedCurves.nistP256))
+            using (ECDiffieHellman eve = ECDiffieHellmanFactory.Create(ECCurve.NamedCurves.nistP256))
+            using (ECDiffieHellmanPublicKey bobPublic = bob.PublicKey)
+            using (ECDiffieHellmanPublicKey evePublic = eve.PublicKey)
+            {
+                byte[] aliceDerived = alice.DeriveRawSecretAgreement(bobPublic);
+                byte[] eveDerived = alice.DeriveRawSecretAgreement(evePublic);
+
+                Assert.NotEqual(aliceDerived, eveDerived);
+            }
+        }
+
+        [ConditionalFact(typeof(ECDiffieHellmanFactory), nameof(ECDiffieHellmanFactory.SupportsRawDerivation))]
+        public static void RawDerivation_DeriveIsStable()
+        {
+            using (ECDiffieHellman alice = ECDiffieHellmanFactory.Create(ECCurve.NamedCurves.nistP256))
+            using (ECDiffieHellman bob = ECDiffieHellmanFactory.Create(ECCurve.NamedCurves.nistP256))
+            using (ECDiffieHellmanPublicKey bobPublic = bob.PublicKey)
+            {
+                byte[] aliceDerived1 = alice.DeriveRawSecretAgreement(bobPublic);
+                byte[] aliceDerived2 = alice.DeriveRawSecretAgreement(bobPublic);
+                Assert.Equal(aliceDerived1, aliceDerived2);
+            }
+        }
+
+        [ConditionalFact(nameof(DoesNotSupportRawDerivation))]
+        public static void RawDerivation_NotSupported()
+        {
+            using (ECDiffieHellman alice = ECDiffieHellmanFactory.Create(ECCurve.NamedCurves.nistP256))
+            using (ECDiffieHellman bob = ECDiffieHellmanFactory.Create(ECCurve.NamedCurves.nistP256))
+            using (ECDiffieHellmanPublicKey bobPublic = bob.PublicKey)
+            {
+                Assert.Throws<PlatformNotSupportedException>(() => alice.DeriveRawSecretAgreement(bobPublic));
+            }
+        }
+    }
+}
index 0470ab5..d805dbc 100644 (file)
@@ -35,6 +35,7 @@ namespace System.Security.Cryptography.EcDiffieHellman.Tests
         }
 
         public bool CanDeriveNewPublicKey => true;
+        public bool SupportsRawDerivation => PlatformDetection.IsWindows10OrLater;
 
         private static bool NativeOidFriendlyNameExists(string oidFriendlyName)
         {
index 8bd0699..e40ea90 100644 (file)
              Link="CommonTest\System\Security\Cryptography\AlgorithmImplementations\ECDiffieHellman\ECDiffieHellmanTests.ImportExport.cs" />
     <Compile Include="$(CommonTestPath)System\Security\Cryptography\AlgorithmImplementations\ECDiffieHellman\ECDiffieHellmanTests.NistValidation.cs"
              Link="CommonTest\System\Security\Cryptography\AlgorithmImplementations\ECDiffieHellman\ECDiffieHellmanTests.NistValidation.cs" />
+    <Compile Include="$(CommonTestPath)System\Security\Cryptography\AlgorithmImplementations\ECDiffieHellman\ECDiffieHellmanTests.Raw.cs"
+             Link="CommonTest\System\Security\Cryptography\AlgorithmImplementations\ECDiffieHellman\ECDiffieHellmanTests.Raw.cs" />
     <Compile Include="$(CommonTestPath)System\Security\Cryptography\AlgorithmImplementations\ECDiffieHellman\ECDiffieHellmanTests.Tls.cs"
              Link="CommonTest\System\Security\Cryptography\AlgorithmImplementations\ECDiffieHellman\ECDiffieHellmanTests.Tls.cs" />
     <Compile Include="$(CommonTestPath)System\Security\Cryptography\AlgorithmImplementations\ECDiffieHellman\ECDiffieHellmanTests.Xml.cs"
index 2890fc4..ef4aa6f 100644 (file)
@@ -27,6 +27,7 @@ namespace System.Security.Cryptography.EcDiffieHellman.Tests
         public bool ExplicitCurvesSupported => _ecdsaProvider.ExplicitCurvesSupported;
 
         public bool CanDeriveNewPublicKey => true;
+        public bool SupportsRawDerivation => true;
     }
 
     public partial class ECDiffieHellmanFactory
index 3ed8b1f..dedc6bd 100644 (file)
@@ -37,6 +37,8 @@
              Link="CommonTest\System\Security\Cryptography\AlgorithmImplementations\ECDiffieHellman\ECDiffieHellmanTests.ImportExport.cs" />
     <Compile Include="$(CommonTestPath)System\Security\Cryptography\AlgorithmImplementations\ECDiffieHellman\ECDiffieHellmanTests.NistValidation.cs"
              Link="CommonTest\System\Security\Cryptography\AlgorithmImplementations\ECDiffieHellman\ECDiffieHellmanTests.NistValidation.cs" />
+    <Compile Include="$(CommonTestPath)System\Security\Cryptography\AlgorithmImplementations\ECDiffieHellman\ECDiffieHellmanTests.Raw.cs"
+             Link="CommonTest\System\Security\Cryptography\AlgorithmImplementations\ECDiffieHellman\ECDiffieHellmanTests.Raw.cs" />
     <Compile Include="$(CommonTestPath)System\Security\Cryptography\AlgorithmImplementations\ECDiffieHellman\ECDiffieHellmanTests.Tls.cs"
              Link="CommonTest\System\Security\Cryptography\AlgorithmImplementations\ECDiffieHellman\ECDiffieHellmanTests.Tls.cs" />
     <Compile Include="$(CommonTestPath)System\Security\Cryptography\AlgorithmImplementations\ECDiffieHellman\ECDiffieHellmanTests.Xml.cs"
index 4e4e3e4..8b9691b 100644 (file)
@@ -969,6 +969,7 @@ namespace System.Security.Cryptography
         public virtual byte[] DeriveKeyFromHmac(System.Security.Cryptography.ECDiffieHellmanPublicKey otherPartyPublicKey, System.Security.Cryptography.HashAlgorithmName hashAlgorithm, byte[]? hmacKey, byte[]? secretPrepend, byte[]? secretAppend) { throw null; }
         public virtual byte[] DeriveKeyMaterial(System.Security.Cryptography.ECDiffieHellmanPublicKey otherPartyPublicKey) { throw null; }
         public virtual byte[] DeriveKeyTls(System.Security.Cryptography.ECDiffieHellmanPublicKey otherPartyPublicKey, byte[] prfLabel, byte[] prfSeed) { throw null; }
+        public virtual byte[] DeriveRawSecretAgreement(System.Security.Cryptography.ECDiffieHellmanPublicKey otherPartyPublicKey) { throw null; }
         public override void FromXmlString(string xmlString) { }
         public override string ToXmlString(bool includePrivateParameters) { throw null; }
     }
index a94fe83..d0e0047 100644 (file)
@@ -133,6 +133,35 @@ namespace System.Security.Cryptography
             throw DerivedClassMustOverride();
         }
 
+        /// <summary>
+        /// Derive raw key material.
+        /// </summary>
+        /// <param name="otherPartyPublicKey">The public key of the party with which to derive a mutual secret.</param>
+        /// <returns>The raw key agreement.</returns>
+        /// <remarks>
+        /// Care must be taking when using the raw derived secret agreement value. The raw value is expected to be used
+        /// as input in to a Key Derivation Function, and not used directly as key material.
+        /// </remarks>
+        /// <exception cref="ArgumentNullException">
+        ///   <paramref name="otherPartyPublicKey"/> is <see langword="null" />.
+        /// </exception>
+        /// <exception cref="ArgumentException">
+        ///   <paramref name="otherPartyPublicKey"/> is over a different curve than this key.
+        /// </exception>
+        /// <exception cref="NotImplementedException">
+        ///   A derived implementation has not provided an implementation of the method.
+        /// </exception>
+        /// <exception cref="PlatformNotSupportedException">
+        ///  The current platform does not support raw key agreement.
+        /// </exception>
+        /// <exception cref="ObjectDisposedException">
+        ///  The object has already been disposed.
+        /// </exception>
+        public virtual byte[] DeriveRawSecretAgreement(ECDiffieHellmanPublicKey otherPartyPublicKey)
+        {
+            throw DerivedClassMustOverride();
+        }
+
         private static NotImplementedException DerivedClassMustOverride()
         {
             return new NotImplementedException(SR.NotSupported_SubclassOverride);
index 58f1c1d..b355cca 100644 (file)
@@ -43,6 +43,9 @@ namespace System.Security.Cryptography
         public override byte[] DeriveKeyTls(ECDiffieHellmanPublicKey otherPartyPublicKey, byte[] prfLabel, byte[] prfSeed) =>
             _wrapped.DeriveKeyTls(Unwrap(otherPartyPublicKey), prfLabel, prfSeed);
 
+        public override byte[] DeriveRawSecretAgreement(ECDiffieHellmanPublicKey otherPartyPublicKey) =>
+            _wrapped.DeriveRawSecretAgreement(Unwrap(otherPartyPublicKey));
+
         public override void FromXmlString(string xmlString) => _wrapped.FromXmlString(xmlString);
 
         public override string ToXmlString(bool includePrivateParameters) =>
index 63ec79d..93c0237 100644 (file)
@@ -21,6 +21,8 @@ namespace System.Security.Cryptography.EcDiffieHellman.Tests
 
         public bool CanDeriveNewPublicKey => false;
 
+        public bool SupportsRawDerivation => true;
+
         private static bool IsValueOrFriendlyNameValid(string friendlyNameOrValue)
         {
             if (string.IsNullOrEmpty(friendlyNameOrValue))
index 58b7bcf..d00c95b 100644 (file)
@@ -35,6 +35,7 @@ namespace System.Security.Cryptography.EcDiffieHellman.Tests
         }
 
         public bool CanDeriveNewPublicKey { get; } = !PlatformDetection.IsiOS && !PlatformDetection.IstvOS && !PlatformDetection.IsMacCatalyst;
+        public bool SupportsRawDerivation => true;
 
         private static bool IsValueOrFriendlyNameValid(string friendlyNameOrValue)
         {
index c458f74..49f8ff1 100644 (file)
@@ -23,6 +23,7 @@ namespace System.Security.Cryptography.EcDiffieHellman.Tests
         }
 
         public bool CanDeriveNewPublicKey => true;
+        public bool SupportsRawDerivation => PlatformDetection.IsWindows10OrLater;
 
         private static bool NativeOidFriendlyNameExists(string oidFriendlyName)
         {
index 86e3d51..eda1865 100644 (file)
              Link="CommonTest\System\Security\Cryptography\AlgorithmImplementations\ECDiffieHellman\ECDiffieHellmanTests.ImportExport.cs" />
     <Compile Include="$(CommonTestPath)System\Security\Cryptography\AlgorithmImplementations\ECDiffieHellman\ECDiffieHellmanTests.NistValidation.cs"
              Link="CommonTest\System\Security\Cryptography\AlgorithmImplementations\ECDiffieHellman\ECDiffieHellmanTests.NistValidation.cs" />
+    <Compile Include="$(CommonTestPath)System\Security\Cryptography\AlgorithmImplementations\ECDiffieHellman\ECDiffieHellmanTests.Raw.cs"
+             Link="CommonTest\System\Security\Cryptography\AlgorithmImplementations\ECDiffieHellman\ECDiffieHellmanTests.Raw.cs" />
     <Compile Include="$(CommonTestPath)System\Security\Cryptography\AlgorithmImplementations\ECDiffieHellman\ECDiffieHellmanTests.Tls.cs"
              Link="CommonTest\System\Security\Cryptography\AlgorithmImplementations\ECDiffieHellman\ECDiffieHellmanTests.Tls.cs" />
     <Compile Include="$(CommonTestPath)System\Security\Cryptography\AlgorithmImplementations\ECDiffieHellman\ECDiffieHellmanTests.Xml.cs"