Skip to content

Commit be823a1

Browse files
authored
Support raw ECDH key agreements
1 parent de48b5a commit be823a1

20 files changed

+219
-2
lines changed

src/libraries/Common/src/Interop/Windows/BCrypt/Cng.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ internal static class KeyDerivationFunction
4545
public const string Hash = "HASH"; // BCRYPT_KDF_HASH
4646
public const string Hmac = "HMAC"; // BCRYPT_KDF_HMAC
4747
public const string Tls = "TLS_PRF"; // BCRYPT_KDF_TLS_PRF
48+
public const string Raw = "TRUNCATE"; // BCRYPT_KDF_RAW_SECRET
4849
}
4950
}
5051

src/libraries/Common/src/Interop/Windows/NCrypt/Interop.NCryptDeriveKeyMaterial.cs

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -241,5 +241,25 @@ internal static unsafe byte[] DeriveKeyMaterialTls(
241241
flags);
242242
}
243243
}
244+
245+
internal static unsafe byte[] DeriveKeyMaterialTruncate(
246+
SafeNCryptSecretHandle secretAgreement,
247+
SecretAgreementFlags flags)
248+
{
249+
if (!OperatingSystem.IsWindowsVersionAtLeast(10))
250+
{
251+
throw new PlatformNotSupportedException();
252+
}
253+
254+
byte[] result = DeriveKeyMaterial(
255+
secretAgreement,
256+
BCryptNative.KeyDerivationFunction.Raw,
257+
ReadOnlySpan<NCryptBuffer>.Empty,
258+
flags);
259+
260+
// Win32 returns the result as little endian. So we need to flip it to big endian.
261+
Array.Reverse(result);
262+
return result;
263+
}
244264
}
245265
}

src/libraries/Common/src/System/Security/Cryptography/ECDiffieHellmanAndroid.Derive.cs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,16 @@ public override byte[] DeriveKeyTls(ECDiffieHellmanPublicKey otherPartyPublicKey
7272
DeriveSecretAgreement);
7373
}
7474

75+
public override byte[] DeriveRawSecretAgreement(ECDiffieHellmanPublicKey otherPartyPublicKey)
76+
{
77+
ArgumentNullException.ThrowIfNull(otherPartyPublicKey);
78+
ThrowIfDisposed();
79+
80+
byte[]? secretAgreement = DeriveSecretAgreement(otherPartyPublicKey, hasher: null);
81+
Debug.Assert(secretAgreement is not null);
82+
return secretAgreement;
83+
}
84+
7585
/// <summary>
7686
/// Get the secret agreement generated between two parties
7787
/// </summary>

src/libraries/Common/src/System/Security/Cryptography/ECDiffieHellmanCng.cs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -130,5 +130,18 @@ public override byte[] DeriveKeyTls(ECDiffieHellmanPublicKey otherPartyPublicKey
130130
Interop.NCrypt.SecretAgreementFlags.None);
131131
}
132132
}
133+
134+
/// <inheritdoc />
135+
public override byte[] DeriveRawSecretAgreement(ECDiffieHellmanPublicKey otherPartyPublicKey)
136+
{
137+
ArgumentNullException.ThrowIfNull(otherPartyPublicKey);
138+
139+
using (SafeNCryptSecretHandle secretAgreement = DeriveSecretAgreementHandle(otherPartyPublicKey))
140+
{
141+
return Interop.NCrypt.DeriveKeyMaterialTruncate(
142+
secretAgreement,
143+
Interop.NCrypt.SecretAgreementFlags.None);
144+
}
145+
}
133146
}
134147
}

src/libraries/Common/src/System/Security/Cryptography/ECDiffieHellmanOpenSsl.Derive.cs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,17 @@ public override byte[] DeriveKeyTls(ECDiffieHellmanPublicKey otherPartyPublicKey
6969
DeriveSecretAgreement);
7070
}
7171

72+
/// <inheritdoc />
73+
public override byte[] DeriveRawSecretAgreement(ECDiffieHellmanPublicKey otherPartyPublicKey)
74+
{
75+
ArgumentNullException.ThrowIfNull(otherPartyPublicKey);
76+
ThrowIfDisposed();
77+
78+
byte[]? secretAgreement = DeriveSecretAgreement(otherPartyPublicKey, hasher: null);
79+
Debug.Assert(secretAgreement is not null);
80+
return secretAgreement;
81+
}
82+
7283
/// <summary>
7384
/// Get the secret agreement generated between two parties
7485
/// </summary>

src/libraries/Common/src/System/Security/Cryptography/ECDiffieHellmanSecurityTransforms.cs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -165,6 +165,16 @@ public override byte[] DeriveKeyTls(ECDiffieHellmanPublicKey otherPartyPublicKey
165165
DeriveSecretAgreement);
166166
}
167167

168+
public override byte[] DeriveRawSecretAgreement(ECDiffieHellmanPublicKey otherPartyPublicKey)
169+
{
170+
ArgumentNullException.ThrowIfNull(otherPartyPublicKey);
171+
ThrowIfDisposed();
172+
173+
byte[]? secretAgreement = DeriveSecretAgreement(otherPartyPublicKey, hasher: null);
174+
Debug.Assert(secretAgreement is not null);
175+
return secretAgreement;
176+
}
177+
168178
private byte[]? DeriveSecretAgreement(ECDiffieHellmanPublicKey otherPartyPublicKey, IncrementalHash? hasher)
169179
{
170180
if (!(otherPartyPublicKey is ECDiffieHellmanSecurityTransformsPublicKey secTransPubKey))

src/libraries/Common/tests/System/Security/Cryptography/AlgorithmImplementations/ECDiffieHellman/ECDiffieHellmanFactory.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ public interface IECDiffieHellmanProvider
1313
bool IsCurveValid(Oid oid);
1414
bool ExplicitCurvesSupported { get; }
1515
bool CanDeriveNewPublicKey { get; }
16+
bool SupportsRawDerivation { get; }
1617
}
1718

1819
public static partial class ECDiffieHellmanFactory
@@ -42,5 +43,7 @@ public static bool IsCurveValid(Oid oid)
4243
public static bool ExplicitCurvesSupported => s_provider.ExplicitCurvesSupported;
4344

4445
public static bool CanDeriveNewPublicKey => s_provider.CanDeriveNewPublicKey;
46+
47+
public static bool SupportsRawDerivation => s_provider.SupportsRawDerivation;
4548
}
4649
}

src/libraries/Common/tests/System/Security/Cryptography/AlgorithmImplementations/ECDiffieHellman/ECDiffieHellmanTests.NistValidation.cs

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -223,9 +223,19 @@ private static void Verify(
223223
HashAlgorithmName zHashAlgorithm,
224224
byte[] iutZ)
225225
{
226-
byte[] result = iut.DeriveKeyFromHash(cavsPublic, zHashAlgorithm);
226+
byte[] deriveHash = iut.DeriveKeyFromHash(cavsPublic, zHashAlgorithm);
227227
byte[] hashedZ = zHasher.ComputeHash(iutZ);
228-
Assert.Equal(hashedZ.ByteArrayToHex(), result.ByteArrayToHex());
228+
Assert.Equal(hashedZ.ByteArrayToHex(), deriveHash.ByteArrayToHex());
229+
230+
if (ECDiffieHellmanFactory.SupportsRawDerivation)
231+
{
232+
byte[] rawDerived = iut.DeriveRawSecretAgreement(cavsPublic);
233+
Assert.Equal(iutZ.ByteArrayToHex(), rawDerived.ByteArrayToHex());
234+
}
235+
else
236+
{
237+
Assert.Throws<PlatformNotSupportedException>(() => iut.DeriveRawSecretAgreement(cavsPublic));
238+
}
229239
}
230240
}
231241
#endif
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
using System;
5+
using System.Security.Cryptography;
6+
using Xunit;
7+
8+
namespace System.Security.Cryptography.EcDiffieHellman.Tests
9+
{
10+
public partial class ECDiffieHellmanTests
11+
{
12+
public static bool DoesNotSupportRawDerivation => !ECDiffieHellmanFactory.SupportsRawDerivation;
13+
14+
[ConditionalFact(typeof(ECDiffieHellmanFactory), nameof(ECDiffieHellmanFactory.SupportsRawDerivation))]
15+
public static void RawDerivation_OtherKeyRequired()
16+
{
17+
using (ECDiffieHellman ecdh = ECDiffieHellmanFactory.Create())
18+
{
19+
AssertExtensions.Throws<ArgumentNullException>(
20+
"otherPartyPublicKey",
21+
() => ecdh.DeriveRawSecretAgreement(null));
22+
}
23+
}
24+
25+
[ConditionalTheory(typeof(ECDiffieHellmanFactory), nameof(ECDiffieHellmanFactory.SupportsRawDerivation))]
26+
[MemberData(nameof(MismatchedKeysizes))]
27+
public static void RawDerivation_SameSizeOtherKeyRequired(int aliceSize, int bobSize)
28+
{
29+
using (ECDiffieHellman alice = ECDiffieHellmanFactory.Create(aliceSize))
30+
using (ECDiffieHellman bob = ECDiffieHellmanFactory.Create(bobSize))
31+
using (ECDiffieHellmanPublicKey bobPublic = bob.PublicKey)
32+
{
33+
AssertExtensions.Throws<ArgumentException>(
34+
"otherPartyPublicKey",
35+
() => alice.DeriveRawSecretAgreement(bobPublic));
36+
}
37+
}
38+
39+
[ConditionalTheory(typeof(ECDiffieHellmanFactory), nameof(ECDiffieHellmanFactory.SupportsRawDerivation))]
40+
[MemberData(nameof(EveryKeysize))]
41+
public static void RawDerivation_DeriveSharedSecret_Agree(int keySize)
42+
{
43+
using (ECDiffieHellman alice = ECDiffieHellmanFactory.Create(keySize))
44+
using (ECDiffieHellman bob = ECDiffieHellmanFactory.Create(keySize))
45+
using (ECDiffieHellmanPublicKey alicePublic = alice.PublicKey)
46+
using (ECDiffieHellmanPublicKey bobPublic = bob.PublicKey)
47+
{
48+
byte[] aliceDerived = alice.DeriveRawSecretAgreement(bobPublic);
49+
byte[] bobDerived = bob.DeriveRawSecretAgreement(alicePublic);
50+
Assert.Equal(aliceDerived, bobDerived);
51+
}
52+
}
53+
54+
[ConditionalFact(typeof(ECDiffieHellmanFactory), nameof(ECDiffieHellmanFactory.SupportsRawDerivation))]
55+
public static void RawDerivation_DeriveSharedSecret_Disagree()
56+
{
57+
using (ECDiffieHellman alice = ECDiffieHellmanFactory.Create(ECCurve.NamedCurves.nistP256))
58+
using (ECDiffieHellman bob = ECDiffieHellmanFactory.Create(ECCurve.NamedCurves.nistP256))
59+
using (ECDiffieHellman eve = ECDiffieHellmanFactory.Create(ECCurve.NamedCurves.nistP256))
60+
using (ECDiffieHellmanPublicKey bobPublic = bob.PublicKey)
61+
using (ECDiffieHellmanPublicKey evePublic = eve.PublicKey)
62+
{
63+
byte[] aliceDerived = alice.DeriveRawSecretAgreement(bobPublic);
64+
byte[] eveDerived = alice.DeriveRawSecretAgreement(evePublic);
65+
66+
Assert.NotEqual(aliceDerived, eveDerived);
67+
}
68+
}
69+
70+
[ConditionalFact(typeof(ECDiffieHellmanFactory), nameof(ECDiffieHellmanFactory.SupportsRawDerivation))]
71+
public static void RawDerivation_DeriveIsStable()
72+
{
73+
using (ECDiffieHellman alice = ECDiffieHellmanFactory.Create(ECCurve.NamedCurves.nistP256))
74+
using (ECDiffieHellman bob = ECDiffieHellmanFactory.Create(ECCurve.NamedCurves.nistP256))
75+
using (ECDiffieHellmanPublicKey bobPublic = bob.PublicKey)
76+
{
77+
byte[] aliceDerived1 = alice.DeriveRawSecretAgreement(bobPublic);
78+
byte[] aliceDerived2 = alice.DeriveRawSecretAgreement(bobPublic);
79+
Assert.Equal(aliceDerived1, aliceDerived2);
80+
}
81+
}
82+
83+
[ConditionalFact(nameof(DoesNotSupportRawDerivation))]
84+
public static void RawDerivation_NotSupported()
85+
{
86+
using (ECDiffieHellman alice = ECDiffieHellmanFactory.Create(ECCurve.NamedCurves.nistP256))
87+
using (ECDiffieHellman bob = ECDiffieHellmanFactory.Create(ECCurve.NamedCurves.nistP256))
88+
using (ECDiffieHellmanPublicKey bobPublic = bob.PublicKey)
89+
{
90+
Assert.Throws<PlatformNotSupportedException>(() => alice.DeriveRawSecretAgreement(bobPublic));
91+
}
92+
}
93+
}
94+
}

src/libraries/System.Security.Cryptography.Cng/tests/ECDiffieHellmanCngProvider.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ public bool ExplicitCurvesSupported
3535
}
3636

3737
public bool CanDeriveNewPublicKey => true;
38+
public bool SupportsRawDerivation => PlatformDetection.IsWindows10OrLater;
3839

3940
private static bool NativeOidFriendlyNameExists(string oidFriendlyName)
4041
{

0 commit comments

Comments
 (0)