Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion src/libraries/Common/src/Interop/Windows/BCrypt/Cng.cs
Original file line number Diff line number Diff line change
Expand Up @@ -29,10 +29,11 @@ internal static class AlgorithmName
public const string ECDsaP256 = "ECDSA_P256"; // BCRYPT_ECDSA_P256_ALGORITHM
public const string ECDsaP384 = "ECDSA_P384"; // BCRYPT_ECDSA_P384_ALGORITHM
public const string ECDsaP521 = "ECDSA_P521"; // BCRYPT_ECDSA_P521_ALGORITHM
public const string RSA = "RSA"; // BCRYPT_RSA_ALGORITHM
public const string HKDF = "HKDF"; // BCRYPT_HKDF_ALGORITHM
public const string MD5 = "MD5"; // BCRYPT_MD5_ALGORITHM
public const string MLDsa = "ML-DSA"; // BCRYPT_MLDSA_ALGORITHM
public const string MLKem = "ML-KEM"; // BCRYPT_MLKEM_ALGORITHM
public const string RSA = "RSA"; // BCRYPT_RSA_ALGORITHM
public const string Sha1 = "SHA1"; // BCRYPT_SHA1_ALGORITHM
public const string Sha256 = "SHA256"; // BCRYPT_SHA256_ALGORITHM
public const string Sha384 = "SHA384"; // BCRYPT_SHA384_ALGORITHM
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ public enum BCryptAlgPseudoHandle : uint
BCRYPT_HMAC_SHA384_ALG_HANDLE = 0x000000c1,
BCRYPT_HMAC_SHA512_ALG_HANDLE = 0x000000d1,
BCRYPT_PBKDF2_ALG_HANDLE = 0x00000331,
BCRYPT_HKDF_ALG_HANDLE = 0x00000391,
BCRYPT_SHA3_256_ALG_HANDLE = 0x000003B1,
BCRYPT_SHA3_384_ALG_HANDLE = 0x000003C1,
BCRYPT_SHA3_512_ALG_HANDLE = 0x000003D1,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -302,6 +302,7 @@ internal enum CngBufferDescriptors : int
KDF_CONTEXT = 14,
KDF_SALT = 15,
KDF_ITERATION_COUNT = 16,
KDF_HKDF_INFO = 20,
NCRYPTBUFFER_ECC_CURVE_NAME = 60,
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1988,7 +1988,7 @@
<Compile Include="System\Security\Cryptography\ECDiffieHellmanWrapper.cs" />
<Compile Include="System\Security\Cryptography\HashProviderCng.cs" />
<Compile Include="System\Security\Cryptography\HashProviderDispenser.Windows.cs" />
<Compile Include="System\Security\Cryptography\HKDF.Managed.cs" />
<Compile Include="System\Security\Cryptography\HKDF.Windows.cs" />
<Compile Include="System\Security\Cryptography\ICngSymmetricAlgorithm.cs" />
<Compile Include="System\Security\Cryptography\LiteHash.Windows.cs" />
<Compile Include="System\Security\Cryptography\MLDsaOpenSsl.NotSupported.cs" />
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,239 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.Buffers.Binary;
using System.Diagnostics;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using Internal.Cryptography;
using Microsoft.Win32.SafeHandles;

using BCryptAlgPseudoHandle = Interop.BCrypt.BCryptAlgPseudoHandle;
using BCryptBuffer = Interop.BCrypt.BCryptBuffer;
using BCryptBufferDesc = Interop.BCrypt.BCryptBufferDesc;
using BCryptOpenAlgorithmProviderFlags = Interop.BCrypt.BCryptOpenAlgorithmProviderFlags;
using BCRYPT_KEY_DATA_BLOB_HEADER = Interop.BCrypt.BCRYPT_KEY_DATA_BLOB_HEADER;
using CngBufferDescriptors = Interop.BCrypt.CngBufferDescriptors;
using NTSTATUS = Interop.BCrypt.NTSTATUS;

namespace System.Security.Cryptography
{
public static partial class HKDF
{
private static readonly bool s_hasCngImplementation = IsCngSupported();
private const string BCRYPT_HKDF_SALT_AND_FINALIZE = "HkdfSaltAndFinalize";
private const string BCRYPT_HKDF_PRK_AND_FINALIZE = "HkdfPrkAndFinalize";
private const string BCRYPT_HKDF_HASH_ALGORITHM = "HkdfHashAlgorithm";

private static void ExtractCore(
HashAlgorithmName hashAlgorithmName,
ReadOnlySpan<byte> ikm,
ReadOnlySpan<byte> salt,
Span<byte> prk)
{
// Windows does not clearly have a way to perform just the Extract step from HKDF. We'll use managed for now.
HKDFManagedImplementation.Extract(hashAlgorithmName, ikm, salt, prk);
}

private static void Expand(
HashAlgorithmName hashAlgorithmName,
int hashLength,
ReadOnlySpan<byte> prk,
Span<byte> output,
ReadOnlySpan<byte> info)
{
if (s_hasCngImplementation && !IsAlgorithmRequiringManagedFallback(hashAlgorithmName))
{
CngDeriveKey(
hashAlgorithmName,
prk,
info,
salt: default,
output,
secretIsIkm: false);
}
else
{
HKDFManagedImplementation.Expand(hashAlgorithmName, hashLength, prk, output, info);
}
}

private static void DeriveKeyCore(
HashAlgorithmName hashAlgorithmName,
int hashLength,
ReadOnlySpan<byte> ikm,
Span<byte> output,
ReadOnlySpan<byte> salt,
ReadOnlySpan<byte> info)
{
if (s_hasCngImplementation && !IsAlgorithmRequiringManagedFallback(hashAlgorithmName))
{
CngDeriveKey(
hashAlgorithmName,
ikm,
info,
salt,
output,
secretIsIkm: true);
}
else
{
HKDFManagedImplementation.DeriveKey(hashAlgorithmName, hashLength, ikm, output, salt, info);
}
}

private static bool IsAlgorithmRequiringManagedFallback(HashAlgorithmName hashAlgorithmName)
{
return hashAlgorithmName == HashAlgorithmName.MD5;
}

private static void ThrowIfAlgorithmNotSupported(HashAlgorithmName hashAlgorithmName)
{
if ((hashAlgorithmName == HashAlgorithmName.SHA3_256 && !SHA3_256.IsSupported) ||
(hashAlgorithmName == HashAlgorithmName.SHA3_384 && !SHA3_384.IsSupported) ||
(hashAlgorithmName == HashAlgorithmName.SHA3_512 && !SHA3_512.IsSupported))
{
throw new PlatformNotSupportedException();
}
}

private static bool IsCngSupported()
{
NTSTATUS openStatus = Interop.BCrypt.BCryptOpenAlgorithmProvider(
out SafeBCryptAlgorithmHandle handle,
Internal.NativeCrypto.BCryptNative.AlgorithmName.HKDF,
null,
BCryptOpenAlgorithmProviderFlags.None);

handle.Dispose();

// HKDF was added in Windows 10 1803.
Debug.Assert(!OperatingSystem.IsWindowsVersionAtLeast(10, 0, 17134) || openStatus == NTSTATUS.STATUS_SUCCESS);
return openStatus == NTSTATUS.STATUS_SUCCESS;
}

private static unsafe void CngDeriveKey(
HashAlgorithmName hashAlgorithmName,
ReadOnlySpan<byte> secret,
ReadOnlySpan<byte> info,
ReadOnlySpan<byte> salt,
Span<byte> destination,
bool secretIsIkm)
{
Debug.Assert(Interop.BCrypt.PseudoHandlesSupported);
Debug.Assert(hashAlgorithmName.Name is not null);

ThrowIfAlgorithmNotSupported(hashAlgorithmName);

byte[]? rented;
ReadOnlySpan<byte> infoBlob;

if (destination.Overlaps(info))
{
rented = CryptoPool.Rent(info.Length);
info.CopyTo(rented);
infoBlob = rented.AsSpan(0, info.Length);
}
else
{
rented = null;
infoBlob = info;
}

SafeBCryptKeyHandle? keyHandle = null;
NTSTATUS status;

try
{
fixed (byte* pSecret = &Helpers.GetNonNullPinnableReference(secret))
{
status = Interop.BCrypt.BCryptGenerateSymmetricKey(
(nuint)BCryptAlgPseudoHandle.BCRYPT_HKDF_ALG_HANDLE,
out keyHandle,
pbKeyObject: IntPtr.Zero,
cbKeyObject: 0,
pSecret,
secret.Length,
dwFlags: 0);

if (status != NTSTATUS.STATUS_SUCCESS)
{
throw Interop.BCrypt.CreateCryptographicException(status);
}

Interop.BCrypt.BCryptSetSZProperty(keyHandle, BCRYPT_HKDF_HASH_ALGORITHM, hashAlgorithmName.Name);

if (secretIsIkm)
{
fixed (byte* pSalt = &Helpers.GetNonNullPinnableReference(salt))
{
status = Interop.BCrypt.BCryptSetProperty(
keyHandle,
BCRYPT_HKDF_SALT_AND_FINALIZE,
pSalt,
(uint)salt.Length,
dwFlags: 0);
}
}
else
{
Debug.Assert(salt.IsEmpty);
status = Interop.BCrypt.BCryptSetProperty(
keyHandle,
BCRYPT_HKDF_PRK_AND_FINALIZE,
null,
0U,
dwFlags: 0);
}

if (status != NTSTATUS.STATUS_SUCCESS)
{
throw Interop.BCrypt.CreateCryptographicException(status);
}
}

fixed (byte* pDestination = destination)
fixed (byte* pInfoBlob = &Helpers.GetNonNullPinnableReference(infoBlob))
{
BCryptBuffer infoBuffer = default;
infoBuffer.cbBuffer = infoBlob.Length;
infoBuffer.BufferType = CngBufferDescriptors.KDF_HKDF_INFO;
infoBuffer.pvBuffer = (IntPtr)pInfoBlob;

BCryptBufferDesc bufferDesc = default;
bufferDesc.ulVersion = Interop.BCrypt.BCRYPTBUFFER_VERSION;
bufferDesc.cBuffers = 1;
bufferDesc.pBuffers = (IntPtr)(&infoBuffer);

status = Interop.BCrypt.BCryptKeyDerivation(
keyHandle,
&bufferDesc,
pDestination,
destination.Length,
out uint resultLength,
dwFlags: 0);

if (status != NTSTATUS.STATUS_SUCCESS)
{
throw Interop.BCrypt.CreateCryptographicException(status);
}

if (destination.Length != resultLength)
{
Debug.Fail("HKDF resultLength != destination.Length");
throw new CryptographicException();
}
}
}
finally
{
if (rented is not null)
{
CryptoPool.Return(rented, clearSize: 0); // Info is not considered secret.
}

keyHandle?.Dispose();
}
}
}
}
Loading