Skip to content

Commit

Permalink
Support raw ECDH key agreements
Browse files Browse the repository at this point in the history
  • Loading branch information
vcsjones committed Mar 3, 2023
1 parent de48b5a commit be823a1
Show file tree
Hide file tree
Showing 20 changed files with 219 additions and 2 deletions.
1 change: 1 addition & 0 deletions src/libraries/Common/src/Interop/Windows/BCrypt/Cng.cs
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ internal static class KeyDerivationFunction
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
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -241,5 +241,25 @@ internal static unsafe byte[] DeriveKeyMaterialTls(
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;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,16 @@ public override byte[] DeriveKeyTls(ECDiffieHellmanPublicKey otherPartyPublicKey
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>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -130,5 +130,18 @@ public override byte[] DeriveKeyTls(ECDiffieHellmanPublicKey otherPartyPublicKey
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);
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,17 @@ public override byte[] DeriveKeyTls(ECDiffieHellmanPublicKey otherPartyPublicKey
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>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,16 @@ public override byte[] DeriveKeyTls(ECDiffieHellmanPublicKey otherPartyPublicKey
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))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ public interface IECDiffieHellmanProvider
bool IsCurveValid(Oid oid);
bool ExplicitCurvesSupported { get; }
bool CanDeriveNewPublicKey { get; }
bool SupportsRawDerivation { get; }
}

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

public static bool CanDeriveNewPublicKey => s_provider.CanDeriveNewPublicKey;

public static bool SupportsRawDerivation => s_provider.SupportsRawDerivation;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -223,9 +223,19 @@ private static void Verify(
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
Expand Down
Original file line number Diff line number Diff line change
@@ -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));
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ public bool ExplicitCurvesSupported
}

public bool CanDeriveNewPublicKey => true;
public bool SupportsRawDerivation => PlatformDetection.IsWindows10OrLater;

private static bool NativeOidFriendlyNameExists(string oidFriendlyName)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,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"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ public ECDiffieHellman Create(ECCurve curve)
public bool ExplicitCurvesSupported => _ecdsaProvider.ExplicitCurvesSupported;

public bool CanDeriveNewPublicKey => true;
public bool SupportsRawDerivation => true;
}

public partial class ECDiffieHellmanFactory
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -969,6 +969,7 @@ protected ECDiffieHellman() { }
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; }
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,35 @@ public virtual byte[] DeriveKeyTls(ECDiffieHellmanPublicKey otherPartyPublicKey,
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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,9 @@ public override byte[] DeriveKeyFromHmac(
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) =>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ public bool IsCurveValid(Oid oid)

public bool CanDeriveNewPublicKey => false;

public bool SupportsRawDerivation => true;

private static bool IsValueOrFriendlyNameValid(string friendlyNameOrValue)
{
if (string.IsNullOrEmpty(friendlyNameOrValue))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ public bool ExplicitCurvesSupported
}

public bool CanDeriveNewPublicKey { get; } = !PlatformDetection.IsiOS && !PlatformDetection.IstvOS && !PlatformDetection.IsMacCatalyst;
public bool SupportsRawDerivation => true;

private static bool IsValueOrFriendlyNameValid(string friendlyNameOrValue)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ public bool ExplicitCurvesSupported
}

public bool CanDeriveNewPublicKey => true;
public bool SupportsRawDerivation => PlatformDetection.IsWindows10OrLater;

private static bool NativeOidFriendlyNameExists(string oidFriendlyName)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,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"
Expand Down

0 comments on commit be823a1

Please sign in to comment.