diff --git a/src/libraries/Common/src/Interop/Windows/BCrypt/Interop.BCryptAlgPseudoHandle.cs b/src/libraries/Common/src/Interop/Windows/BCrypt/Interop.BCryptAlgPseudoHandle.cs
index 433e6f515dca9..57c508bf70947 100644
--- a/src/libraries/Common/src/Interop/Windows/BCrypt/Interop.BCryptAlgPseudoHandle.cs
+++ b/src/libraries/Common/src/Interop/Windows/BCrypt/Interop.BCryptAlgPseudoHandle.cs
@@ -25,6 +25,15 @@ public enum BCryptAlgPseudoHandle : uint
BCRYPT_PBKDF2_ALG_HANDLE = 0x00000331,
}
- internal static bool PseudoHandlesSupported { get; } = OperatingSystem.IsWindowsVersionAtLeast(10, 0, 0);
+ internal static bool PseudoHandlesSupported { get; } =
+#if NET
+ OperatingSystem.IsWindowsVersionAtLeast(10, 0, 0);
+#elif NETSTANDARD2_0_OR_GREATER
+ RuntimeInformation.IsOSPlatform(OSPlatform.Windows) && Environment.OSVersion.Version.Major >= 10;
+#elif NETFRAMEWORK
+ Environment.OSVersion.Version.Major >= 10;
+#else
+#error Unhandled platform targets
+#endif
}
}
diff --git a/src/libraries/Common/src/System/Security/Cryptography/SP800108HmacCounterKdf.cs b/src/libraries/Common/src/System/Security/Cryptography/SP800108HmacCounterKdf.cs
new file mode 100644
index 0000000000000..6598136dfe7a7
--- /dev/null
+++ b/src/libraries/Common/src/System/Security/Cryptography/SP800108HmacCounterKdf.cs
@@ -0,0 +1,611 @@
+// 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.Runtime.InteropServices;
+using System.Runtime.Versioning;
+using System.Text;
+
+#pragma warning disable CA1510
+
+namespace System.Security.Cryptography
+{
+ ///
+ /// NIST SP 800-108 HMAC CTR Key-Based Key Derivation (KBKDF)
+ ///
+ ///
+ ///
+ /// This implements NIST SP 800-108 HMAC in counter mode. The implemented KDF assumes the form of
+ /// PRF (KI, [i]2 || Label || 0x00 || Context || [L]2) where [i]2 and [L]2 are encoded as
+ /// unsigned 32-bit integers, big endian.
+ ///
+ ///
+ /// All members of this class are thread safe. If the instance is disposed of while other threads are using
+ /// the instance, those threads will either receive an or produce a valid
+ /// derived key.
+ ///
+ ///
+ public sealed partial class SP800108HmacCounterKdf : IDisposable
+ {
+ // The maximum amount of data that we can produce with the PRF is 0x1FFFFFFF.
+ // This is because of L[2]. From SP 800-108 r1:
+ // L – An integer specifying the requested length (in bits) of the derived keying material KOUT.
+ // As an unsigned 32-bit interger (see r), L needs to become L[2] by multiplying by 8 (bytes to bits).
+ // We can't encode more than 0x1FFFFFFF as bits without overflowing.
+ // Windows' BCryptKeyDerivation cannot fullfill a request larger than 0x1FFFFFFF, either.
+ private const int MaxPrfOutputSize = (int)(uint.MaxValue / 8);
+
+ private readonly SP800108HmacCounterKdfImplementationBase _implementation;
+
+ private static partial SP800108HmacCounterKdfImplementationBase CreateImplementation(
+ ReadOnlySpan key,
+ HashAlgorithmName hashAlgorithm);
+
+ ///
+ /// Initializes a new instance of using a specified key and HMAC algorithm.
+ ///
+ /// The key-derivation key.
+ /// The HMAC algorithm.
+ ///
+ /// has a which is .
+ ///
+ ///
+ /// has a which is empty.
+ ///
+ ///
+ /// is not a known or supported hash algorithm.
+ ///
+ ///
+ /// The current platform does not have a supported implementation of HMAC.
+ ///
+ public SP800108HmacCounterKdf(ReadOnlySpan key, HashAlgorithmName hashAlgorithm)
+ {
+ CheckHashAlgorithm(hashAlgorithm);
+ _implementation = CreateImplementation(key, hashAlgorithm);
+ }
+
+ ///
+ /// Initializes a new instance of using a specified key and HMAC algorithm.
+ ///
+ /// The key-derivation key.
+ /// The HMAC algorithm.
+ ///
+ ///
+ /// has a which is .
+ ///
+ /// -or-
+ ///
+ /// is .
+ ///
+ ///
+ ///
+ /// has a which is empty.
+ ///
+ ///
+ /// is not a known or supported hash algorithm.
+ ///
+ ///
+ /// The current platform does not have a supported implementation of HMAC.
+ ///
+ public SP800108HmacCounterKdf(byte[] key, HashAlgorithmName hashAlgorithm)
+ {
+ // This constructor doesn't defer to the span constructor because SP800108HmacCounterKdfImplementationCng
+ // has a constructor for byte[] key to avoid a byte[]->span->byte[] conversion.
+
+ if (key is null)
+ throw new ArgumentNullException(nameof(key));
+
+ CheckHashAlgorithm(hashAlgorithm);
+ _implementation = CreateImplementation(key, hashAlgorithm);
+ }
+
+ ///
+ /// Derives a key of a specified length.
+ ///
+ /// The key-derivation key.
+ /// The HMAC algorithm.
+ /// The label that identifies the purpose for the derived key.
+ /// The context containing information related to the derived key.
+ /// The length of the derived key, in bytes.
+ /// An array containing the derived key.
+ ///
+ ///
+ /// is .
+ ///
+ /// -or-
+ ///
+ /// is .
+ ///
+ /// -or-
+ ///
+ /// is .
+ ///
+ /// -or-
+ ///
+ /// has a which is .
+ ///
+ ///
+ ///
+ /// has a which is empty.
+ ///
+ ///
+ /// is negative or larger than the maximum number of bytes
+ /// that can be derived.
+ ///
+ ///
+ /// is not a known or supported hash algorithm.
+ ///
+ ///
+ /// The current platform does not have a supported implementation of HMAC.
+ ///
+ public static byte[] DeriveBytes(byte[] key, HashAlgorithmName hashAlgorithm, byte[] label, byte[] context, int derivedKeyLengthInBytes)
+ {
+ if (key is null)
+ throw new ArgumentNullException(nameof(key));
+
+ if (label is null)
+ throw new ArgumentNullException(nameof(label));
+
+ if (context is null)
+ throw new ArgumentNullException(nameof(context));
+
+ CheckPrfOutputLength(derivedKeyLengthInBytes, nameof(derivedKeyLengthInBytes));
+ CheckHashAlgorithm(hashAlgorithm);
+
+ // Don't call to the Span overload so that we don't go from array->span->array for the key in the .NET Standard
+ // build, which prefers to use arrays for the key.
+ return DeriveBytesCore(key, hashAlgorithm, label, context, derivedKeyLengthInBytes);
+ }
+
+ ///
+ /// Derives a key of a specified length.
+ ///
+ /// The key-derivation key.
+ /// The HMAC algorithm.
+ /// The label that identifies the purpose for the derived key.
+ /// The context containing information related to the derived key.
+ /// The length of the derived key, in bytes.
+ /// An array containing the derived key.
+ ///
+ ///
+ /// is .
+ ///
+ /// -or-
+ ///
+ /// is .
+ ///
+ /// -or-
+ ///
+ /// is .
+ ///
+ /// -or-
+ ///
+ /// has a which is .
+ ///
+ ///
+ ///
+ /// has a which is empty.
+ ///
+ ///
+ /// is negative or larger than the maximum number of bytes
+ /// that can be derived.
+ ///
+ ///
+ /// is not a known or supported hash algorithm.
+ ///
+ ///
+ /// or contains text that cannot be converted to UTF8.
+ ///
+ ///
+ /// and will be converted to bytes using the UTF8 encoding.
+ /// for other encodings, perform the conversion using the desired encoding and use an overload which accepts the
+ /// label and context as a sequence of bytes.
+ ///
+ ///
+ /// The current platform does not have a supported implementation of HMAC.
+ ///
+ public static byte[] DeriveBytes(byte[] key, HashAlgorithmName hashAlgorithm, string label, string context, int derivedKeyLengthInBytes)
+ {
+ if (key is null)
+ throw new ArgumentNullException(nameof(key));
+
+ if (label is null)
+ throw new ArgumentNullException(nameof(label));
+
+ if (context is null)
+ throw new ArgumentNullException(nameof(context));
+
+ CheckPrfOutputLength(derivedKeyLengthInBytes, nameof(derivedKeyLengthInBytes));
+ CheckHashAlgorithm(hashAlgorithm);
+
+ byte[] result = new byte[derivedKeyLengthInBytes];
+ DeriveBytesCore(key, hashAlgorithm, label.AsSpan(), context.AsSpan(), result);
+ return result;
+ }
+
+ ///
+ /// Derives a key of a specified length.
+ ///
+ /// The key-derivation key.
+ /// The HMAC algorithm.
+ /// The label that identifies the purpose for the derived key.
+ /// The context containing information related to the derived key.
+ /// The length of the derived key, in bytes.
+ /// An array containing the derived key.
+ ///
+ /// has a which is .
+ ///
+ ///
+ /// has a which is empty.
+ ///
+ ///
+ /// is negative or larger than the maximum number of bytes
+ /// that can be derived.
+ ///
+ ///
+ /// is not a known or supported hash algorithm.
+ ///
+ ///
+ /// The current platform does not have a supported implementation of HMAC.
+ ///
+ public static byte[] DeriveBytes(ReadOnlySpan key, HashAlgorithmName hashAlgorithm, ReadOnlySpan label, ReadOnlySpan context, int derivedKeyLengthInBytes)
+ {
+ CheckPrfOutputLength(derivedKeyLengthInBytes, nameof(derivedKeyLengthInBytes));
+
+ byte[] result = new byte[derivedKeyLengthInBytes];
+ DeriveBytes(key, hashAlgorithm, label, context, result);
+ return result;
+ }
+
+ ///
+ /// Fills a buffer with a derived key.
+ ///
+ /// The key-derivation key.
+ /// The HMAC algorithm.
+ /// The label that identifies the purpose for the derived key.
+ /// The context containing information related to the derived key.
+ /// The buffer which will receive the derived key.
+ ///
+ /// has a which is .
+ ///
+ ///
+ /// has a which is empty.
+ ///
+ ///
+ /// is larger than the maximum number of bytes that can be derived.
+ ///
+ ///
+ /// is not a known or supported hash algorithm.
+ ///
+ ///
+ /// The current platform does not have a supported implementation of HMAC.
+ ///
+ public static void DeriveBytes(ReadOnlySpan key, HashAlgorithmName hashAlgorithm, ReadOnlySpan label, ReadOnlySpan context, Span destination)
+ {
+ CheckHashAlgorithm(hashAlgorithm);
+ CheckPrfOutputLength(destination.Length, nameof(destination));
+ DeriveBytesCore(key, hashAlgorithm, label, context, destination);
+ }
+
+ ///
+ /// Derives a key of a specified length.
+ ///
+ /// The key-derivation key.
+ /// The HMAC algorithm.
+ /// The label that identifies the purpose for the derived key.
+ /// The context containing information related to the derived key.
+ /// The length of the derived key, in bytes.
+ /// An array containing the derived key.
+ ///
+ /// has a which is .
+ ///
+ ///
+ /// has a which is empty.
+ ///
+ ///
+ /// is negative or larger than the maximum number of bytes
+ /// that can be derived.
+ ///
+ ///
+ /// is not a known or supported hash algorithm.
+ ///
+ ///
+ /// or contains text that cannot be converted to UTF8.
+ ///
+ ///
+ /// The current platform does not have a supported implementation of HMAC.
+ ///
+ ///
+ /// and will be converted to bytes using the UTF8 encoding.
+ /// for other encodings, perform the conversion using the desired encoding and use an overload which accepts the
+ /// label and context as a sequence of bytes.
+ ///
+ public static byte[] DeriveBytes(ReadOnlySpan key, HashAlgorithmName hashAlgorithm, ReadOnlySpan label, ReadOnlySpan context, int derivedKeyLengthInBytes)
+ {
+ CheckPrfOutputLength(derivedKeyLengthInBytes, nameof(derivedKeyLengthInBytes));
+
+ byte[] result = new byte[derivedKeyLengthInBytes];
+ DeriveBytes(key, hashAlgorithm, label, context, result);
+ return result;
+ }
+
+ ///
+ /// Fills a buffer with a derived key.
+ ///
+ /// The key-derivation key.
+ /// The HMAC algorithm.
+ /// The label that identifies the purpose for the derived key.
+ /// The context containing information related to the derived key.
+ /// The buffer which will receive the derived key.
+ ///
+ /// has a which is .
+ ///
+ ///
+ /// has a which is empty.
+ ///
+ ///
+ /// is larger than the maximum number of bytes that can be derived.
+ ///
+ ///
+ /// is not a known or supported hash algorithm.
+ ///
+ ///
+ /// or contains text that cannot be converted to UTF8.
+ ///
+ ///
+ /// The current platform does not have a supported implementation of HMAC.
+ ///
+ ///
+ /// and will be converted to bytes using the UTF8 encoding.
+ /// for other encodings, perform the conversion using the desired encoding and use an overload which accepts the
+ /// label and context as a sequence of bytes.
+ ///
+ public static void DeriveBytes(ReadOnlySpan key, HashAlgorithmName hashAlgorithm, ReadOnlySpan label, ReadOnlySpan context, Span destination)
+ {
+ CheckHashAlgorithm(hashAlgorithm);
+ CheckPrfOutputLength(destination.Length, nameof(destination));
+ DeriveBytesCore(key, hashAlgorithm, label, context, destination);
+ }
+
+ ///
+ /// Derives a key of a specified length.
+ ///
+ /// The label that identifies the purpose for the derived key.
+ /// The context containing information related to the derived key.
+ /// The length of the derived key, in bytes.
+ /// An array containing the derived key.
+ ///
+ ///
+ /// is .
+ ///
+ /// -or-
+ ///
+ /// is .
+ ///
+ ///
+ ///
+ /// is negative or larger than the maximum number of bytes
+ /// that can be derived.
+ ///
+ public byte[] DeriveKey(byte[] label, byte[] context, int derivedKeyLengthInBytes)
+ {
+ if (label is null)
+ throw new ArgumentNullException(nameof(label));
+
+ if (context is null)
+ throw new ArgumentNullException(nameof(context));
+
+ CheckPrfOutputLength(derivedKeyLengthInBytes, nameof(derivedKeyLengthInBytes));
+
+ byte[] result = new byte[derivedKeyLengthInBytes];
+ DeriveKeyCore(label, context, result);
+ return result;
+ }
+
+ ///
+ /// Derives a key of a specified length.
+ ///
+ /// The label that identifies the purpose for the derived key.
+ /// The context containing information related to the derived key.
+ /// The length of the derived key, in bytes.
+ /// An array containing the derived key.
+ ///
+ /// is negative or larger than the maximum number of bytes
+ /// that can be derived.
+ ///
+ public byte[] DeriveKey(ReadOnlySpan label, ReadOnlySpan context, int derivedKeyLengthInBytes)
+ {
+ CheckPrfOutputLength(derivedKeyLengthInBytes, nameof(derivedKeyLengthInBytes));
+
+ byte[] result = new byte[derivedKeyLengthInBytes];
+ DeriveKey(label, context, result);
+ return result;
+ }
+
+ ///
+ /// Fills a buffer with a derived key.
+ ///
+ /// The label that identifies the purpose for the derived key.
+ /// The context containing information related to the derived key.
+ /// The buffer which will receive the derived key.
+ ///
+ ///
+ /// is .
+ ///
+ /// -or-
+ ///
+ /// is .
+ ///
+ ///
+ ///
+ /// is larger than the maximum number of bytes that can be derived.
+ ///
+ public void DeriveKey(ReadOnlySpan label, ReadOnlySpan context, Span destination)
+ {
+ CheckPrfOutputLength(destination.Length, nameof(destination));
+ DeriveKeyCore(label, context, destination);
+ }
+
+ ///
+ /// Derives a key of a specified length.
+ ///
+ /// The label that identifies the purpose for the derived key.
+ /// The context containing information related to the derived key.
+ /// The length of the derived key, in bytes.
+ /// An array containing the derived key.
+ ///
+ /// is negative or larger than the maximum number of bytes
+ /// that can be derived.
+ ///
+ ///
+ /// or contains text that cannot be converted to UTF8.
+ ///
+ ///
+ /// and will be converted to bytes using the UTF8 encoding.
+ /// for other encodings, perform the conversion using the desired encoding and use an overload which accepts the
+ /// label and context as a sequence of bytes.
+ ///
+ public byte[] DeriveKey(ReadOnlySpan label, ReadOnlySpan context, int derivedKeyLengthInBytes)
+ {
+ CheckPrfOutputLength(derivedKeyLengthInBytes, nameof(derivedKeyLengthInBytes));
+
+ byte[] result = new byte[derivedKeyLengthInBytes];
+ DeriveKeyCore(label, context, result);
+ return result;
+ }
+
+ ///
+ /// Fills a buffer with a derived key.
+ ///
+ /// The label that identifies the purpose for the derived key.
+ /// The context containing information related to the derived key.
+ /// The buffer which will receive the derived key.
+ ///
+ /// is larger than the maximum number of bytes that can be derived.
+ ///
+ ///
+ /// or contains text that cannot be converted to UTF8.
+ ///
+ ///
+ /// and will be converted to bytes using the UTF8 encoding.
+ /// for other encodings, perform the conversion using the desired encoding and use an overload which accepts the
+ /// label and context as a sequence of bytes.
+ ///
+ public void DeriveKey(ReadOnlySpan label, ReadOnlySpan context, Span destination)
+ {
+ CheckPrfOutputLength(destination.Length, nameof(destination));
+ DeriveKeyCore(label, context, destination);
+ }
+
+ ///
+ /// Derives a key of a specified length.
+ ///
+ /// The label that identifies the purpose for the derived key.
+ /// The context containing information related to the derived key.
+ /// The length of the derived key, in bytes.
+ /// An array containing the derived key.
+ ///
+ ///
+ /// is .
+ ///
+ /// -or-
+ ///
+ /// is .
+ ///
+ ///
+ ///
+ /// is negative or larger than the maximum number of bytes
+ /// that can be derived.
+ ///
+ ///
+ /// or contains text that cannot be converted to UTF8.
+ ///
+ ///
+ /// and will be converted to bytes using the UTF8 encoding.
+ /// for other encodings, perform the conversion using the desired encoding and use an overload which accepts the
+ /// label and context as a sequence of bytes.
+ ///
+ public byte[] DeriveKey(string label, string context, int derivedKeyLengthInBytes)
+ {
+ if (label is null)
+ throw new ArgumentNullException(nameof(label));
+
+ if (context is null)
+ throw new ArgumentNullException(nameof(context));
+
+ return DeriveKey(label.AsSpan(), context.AsSpan(), derivedKeyLengthInBytes);
+ }
+
+ ///
+ /// Releases all resources used by the current instance of .
+ ///
+ public void Dispose()
+ {
+ _implementation.Dispose();
+ }
+
+ private static void CheckHashAlgorithm(HashAlgorithmName hashAlgorithm)
+ {
+ string? hashAlgorithmName = hashAlgorithm.Name;
+
+ switch (hashAlgorithmName)
+ {
+ case null:
+ throw new ArgumentNullException(nameof(hashAlgorithm));
+ case "":
+ throw new ArgumentException(SR.Argument_EmptyString, nameof(hashAlgorithm));
+ case HashAlgorithmNames.SHA1:
+ case HashAlgorithmNames.SHA256:
+ case HashAlgorithmNames.SHA384:
+ case HashAlgorithmNames.SHA512:
+ break;
+ default:
+ throw new CryptographicException(SR.Format(SR.Cryptography_UnknownHashAlgorithm, hashAlgorithmName));
+ }
+ }
+
+ private static partial byte[] DeriveBytesCore(
+ byte[] key,
+ HashAlgorithmName hashAlgorithm,
+ byte[] label,
+ byte[] context,
+ int derivedKeyLengthInBytes);
+
+ private static partial void DeriveBytesCore(
+ ReadOnlySpan key,
+ HashAlgorithmName hashAlgorithm,
+ ReadOnlySpan label,
+ ReadOnlySpan context,
+ Span destination);
+
+ private static partial void DeriveBytesCore(
+ ReadOnlySpan key,
+ HashAlgorithmName hashAlgorithm,
+ ReadOnlySpan label,
+ ReadOnlySpan context,
+ Span destination);
+
+ private void DeriveKeyCore(ReadOnlySpan label, ReadOnlySpan context, Span destination)
+ {
+ _implementation.DeriveBytes(label, context, destination);
+ }
+
+ private void DeriveKeyCore(ReadOnlySpan label, ReadOnlySpan context, Span destination)
+ {
+ _implementation.DeriveBytes(label, context, destination);
+ }
+
+ private static void CheckPrfOutputLength(int length, string paramName)
+ {
+ if (length > MaxPrfOutputSize)
+ {
+ throw new ArgumentOutOfRangeException(paramName, SR.ArgumentOutOfRange_KOut_Too_Large);
+ }
+
+ if (length < 0)
+ {
+ throw new ArgumentOutOfRangeException(paramName, SR.ArgumentOutOfRange_NeedNonNegNum);
+ }
+ }
+ }
+}
diff --git a/src/libraries/Common/src/System/Security/Cryptography/SP800108HmacCounterKdfImplementationBase.cs b/src/libraries/Common/src/System/Security/Cryptography/SP800108HmacCounterKdfImplementationBase.cs
new file mode 100644
index 0000000000000..698c59d54c323
--- /dev/null
+++ b/src/libraries/Common/src/System/Security/Cryptography/SP800108HmacCounterKdfImplementationBase.cs
@@ -0,0 +1,14 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+namespace System.Security.Cryptography
+{
+ internal abstract class SP800108HmacCounterKdfImplementationBase : IDisposable
+ {
+ internal abstract void DeriveBytes(ReadOnlySpan label, ReadOnlySpan context, Span destination);
+ internal abstract void DeriveBytes(byte[] label, byte[] context, Span destination);
+ internal abstract void DeriveBytes(ReadOnlySpan label, ReadOnlySpan context, Span destination);
+
+ public abstract void Dispose();
+ }
+}
diff --git a/src/libraries/Common/src/System/Security/Cryptography/SP800108HmacCounterKdfImplementationCng.cs b/src/libraries/Common/src/System/Security/Cryptography/SP800108HmacCounterKdfImplementationCng.cs
new file mode 100644
index 0000000000000..ca8f72c61428a
--- /dev/null
+++ b/src/libraries/Common/src/System/Security/Cryptography/SP800108HmacCounterKdfImplementationCng.cs
@@ -0,0 +1,211 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using Microsoft.Win32.SafeHandles;
+using System.Diagnostics;
+
+using BCryptBuffer = Interop.BCrypt.BCryptBuffer;
+using CngBufferDescriptors = Interop.BCrypt.CngBufferDescriptors;
+using NTSTATUS = Interop.BCrypt.NTSTATUS;
+
+namespace System.Security.Cryptography
+{
+ internal sealed partial class SP800108HmacCounterKdfImplementationCng : SP800108HmacCounterKdfImplementationBase
+ {
+ private const string BCRYPT_SP800108_CTR_HMAC_ALGORITHM = "SP800_108_CTR_HMAC";
+ private const nuint BCRYPT_SP800108_CTR_HMAC_ALG_HANDLE = 0x00000341;
+ private const int CharToBytesStackBufferSize = 256;
+
+ // A cached algorithm handle. On Windows 10 this is null if we are using a psuedo handle.
+ private static readonly SafeBCryptAlgorithmHandle? s_sp800108CtrHmacAlgorithmHandle = OpenAlgorithmHandle();
+
+ private readonly SafeBCryptKeyHandle _keyHandle;
+ private readonly HashAlgorithmName _hashAlgorithm;
+
+ public override void Dispose()
+ {
+ _keyHandle.Dispose();
+ }
+
+ internal override void DeriveBytes(byte[] label, byte[] context, Span destination)
+ {
+ DeriveBytes(new ReadOnlySpan(label), new ReadOnlySpan(context), destination);
+ }
+
+ internal override unsafe void DeriveBytes(ReadOnlySpan label, ReadOnlySpan context, Span destination)
+ {
+ if (destination.Length == 0)
+ {
+ return;
+ }
+
+ Debug.Assert(destination.Length <= 0x1FFFFFFF);
+ Debug.Assert(_hashAlgorithm.Name is not null);
+
+ fixed (byte* pLabel = label)
+ fixed (byte* pContext = context)
+ fixed (byte* pDestination = destination)
+ fixed (char* pHashAlgorithm = _hashAlgorithm.Name)
+ {
+ const int BCryptBufferLength = 3;
+ BCryptBuffer* buffers = stackalloc BCryptBuffer[BCryptBufferLength];
+
+ buffers[0].BufferType = CngBufferDescriptors.KDF_LABEL;
+ buffers[0].pvBuffer = (IntPtr)pLabel;
+ buffers[0].cbBuffer = label.Length;
+ buffers[1].BufferType = CngBufferDescriptors.KDF_CONTEXT;
+ buffers[1].pvBuffer = (IntPtr)pContext;
+ buffers[1].cbBuffer = context.Length;
+ buffers[2].BufferType = CngBufferDescriptors.KDF_HASH_ALGORITHM;
+ buffers[2].pvBuffer = (IntPtr)pHashAlgorithm;
+ buffers[2].cbBuffer = (_hashAlgorithm.Name.Length + 1) * 2; // +1 for the null terminator.
+
+ Interop.BCrypt.BCryptBufferDesc bufferDesc;
+ bufferDesc.ulVersion = Interop.BCrypt.BCRYPTBUFFER_VERSION;
+ bufferDesc.cBuffers = BCryptBufferLength;
+ bufferDesc.pBuffers = (IntPtr)buffers;
+
+ NTSTATUS deriveStatus = Interop.BCrypt.BCryptKeyDerivation(
+ _keyHandle,
+ &bufferDesc,
+ pDestination,
+ destination.Length,
+ out uint resultLength,
+ dwFlags: 0);
+
+ if (deriveStatus != NTSTATUS.STATUS_SUCCESS)
+ {
+ throw Interop.BCrypt.CreateCryptographicException(deriveStatus);
+ }
+
+ if (destination.Length != resultLength)
+ {
+ Debug.Fail("BCryptKeyDerivation resultLength != destination.Length");
+ throw new CryptographicException();
+ }
+ }
+ }
+
+ internal override void DeriveBytes(ReadOnlySpan label, ReadOnlySpan context, Span destination)
+ {
+ using (Utf8DataEncoding labelData = new Utf8DataEncoding(label, stackalloc byte[CharToBytesStackBufferSize]))
+ using (Utf8DataEncoding contextData = new Utf8DataEncoding(context, stackalloc byte[CharToBytesStackBufferSize]))
+ {
+ DeriveBytes(labelData.Utf8Bytes, contextData.Utf8Bytes, destination);
+ }
+ }
+
+ internal static void DeriveBytesOneShot(
+ ReadOnlySpan key,
+ HashAlgorithmName hashAlgorithm,
+ ReadOnlySpan label,
+ ReadOnlySpan context,
+ Span destination)
+ {
+ Debug.Assert(destination.Length <= 0x1FFFFFFF);
+
+ using (SP800108HmacCounterKdfImplementationCng kdf = new SP800108HmacCounterKdfImplementationCng(key, hashAlgorithm))
+ {
+ kdf.DeriveBytes(label, context, destination);
+ }
+ }
+
+ internal static void DeriveBytesOneShot(
+ ReadOnlySpan key,
+ HashAlgorithmName hashAlgorithm,
+ ReadOnlySpan label,
+ ReadOnlySpan context,
+ Span destination)
+ {
+ if (destination.Length == 0)
+ {
+ return;
+ }
+
+ using (Utf8DataEncoding labelData = new Utf8DataEncoding(label, stackalloc byte[CharToBytesStackBufferSize]))
+ using (Utf8DataEncoding contextData = new Utf8DataEncoding(context, stackalloc byte[CharToBytesStackBufferSize]))
+ {
+ DeriveBytesOneShot(key, hashAlgorithm, labelData.Utf8Bytes, contextData.Utf8Bytes, destination);
+ }
+ }
+
+ private static unsafe SafeBCryptKeyHandle CreateSymmetricKey(byte* symmetricKey, int symmetricKeyLength)
+ {
+ NTSTATUS generateKeyStatus;
+ SafeBCryptKeyHandle keyHandle;
+
+ if (s_sp800108CtrHmacAlgorithmHandle is not null)
+ {
+ generateKeyStatus = Interop.BCrypt.BCryptGenerateSymmetricKey(
+ s_sp800108CtrHmacAlgorithmHandle,
+ out keyHandle,
+ pbKeyObject: IntPtr.Zero,
+ cbKeyObject: 0,
+ symmetricKey,
+ symmetricKeyLength,
+ dwFlags: 0);
+ }
+ else
+ {
+ generateKeyStatus = Interop.BCrypt.BCryptGenerateSymmetricKey(
+ BCRYPT_SP800108_CTR_HMAC_ALG_HANDLE,
+ out keyHandle,
+ pbKeyObject: IntPtr.Zero,
+ cbKeyObject: 0,
+ symmetricKey,
+ symmetricKeyLength,
+ dwFlags: 0);
+ }
+
+ if (generateKeyStatus != NTSTATUS.STATUS_SUCCESS)
+ {
+ keyHandle.Dispose();
+ throw Interop.BCrypt.CreateCryptographicException(generateKeyStatus);
+ }
+
+ Debug.Assert(!keyHandle.IsInvalid);
+
+ return keyHandle;
+ }
+
+ // Returns null if the platform is Windows 10+ and psuedo handles should be used.
+ private static SafeBCryptAlgorithmHandle? OpenAlgorithmHandle()
+ {
+ if (!Interop.BCrypt.PseudoHandlesSupported)
+ {
+ NTSTATUS openStatus = Interop.BCrypt.BCryptOpenAlgorithmProvider(
+ out SafeBCryptAlgorithmHandle sp800108CtrHmacAlgorithmHandle,
+ BCRYPT_SP800108_CTR_HMAC_ALGORITHM,
+ null,
+ Interop.BCrypt.BCryptOpenAlgorithmProviderFlags.None);
+
+ if (openStatus != NTSTATUS.STATUS_SUCCESS)
+ {
+ sp800108CtrHmacAlgorithmHandle.Dispose();
+ throw Interop.BCrypt.CreateCryptographicException(openStatus);
+ }
+
+ return sp800108CtrHmacAlgorithmHandle;
+ }
+
+ return null;
+ }
+
+ private static int GetHashBlockSize(string hashAlgorithmName)
+ {
+ // Block sizes per NIST FIPS pub 180-4.
+ switch (hashAlgorithmName)
+ {
+ case HashAlgorithmNames.SHA1:
+ case HashAlgorithmNames.SHA256:
+ return 512 / 8;
+ case HashAlgorithmNames.SHA384:
+ case HashAlgorithmNames.SHA512:
+ return 1024 / 8;
+ default:
+ Debug.Fail($"Unexpected hash algorithm '{hashAlgorithmName}'");
+ throw new CryptographicException();
+ }
+ }
+ }
+}
diff --git a/src/libraries/Common/src/System/Security/Cryptography/SP800108HmacCounterKdfImplementationManaged.cs b/src/libraries/Common/src/System/Security/Cryptography/SP800108HmacCounterKdfImplementationManaged.cs
new file mode 100644
index 0000000000000..c009b69ebd9af
--- /dev/null
+++ b/src/libraries/Common/src/System/Security/Cryptography/SP800108HmacCounterKdfImplementationManaged.cs
@@ -0,0 +1,111 @@
+// 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.Threading;
+using System.Runtime.Versioning;
+
+#pragma warning disable CA1513
+
+namespace System.Security.Cryptography
+{
+#if !NET7_0_OR_GREATER && NET
+ [UnsupportedOSPlatform("browser")]
+#endif
+ internal sealed partial class SP800108HmacCounterKdfImplementationManaged : SP800108HmacCounterKdfImplementationBase
+ {
+ private byte[] _key;
+ private int _keyReferenceCount = 1;
+ private int _disposed;
+ private readonly HashAlgorithmName _hashAlgorithm;
+
+ internal override void DeriveBytes(ReadOnlySpan label, ReadOnlySpan context, Span destination)
+ {
+ byte[] key = IncrementAndAcquireKey();
+
+ try
+ {
+ DeriveBytesOneShot(key, _hashAlgorithm, label, context, destination);
+ }
+ finally
+ {
+ ReleaseKey();
+ }
+ }
+
+ internal override void DeriveBytes(ReadOnlySpan label, ReadOnlySpan context, Span destination)
+ {
+ byte[] key = IncrementAndAcquireKey();
+
+ try
+ {
+ DeriveBytesOneShot(key, _hashAlgorithm, label, context, destination);
+ }
+ finally
+ {
+ ReleaseKey();
+ }
+ }
+
+ internal override void DeriveBytes(byte[] label, byte[] context, Span destination)
+ {
+ byte[] key = IncrementAndAcquireKey();
+
+ try
+ {
+ DeriveBytesOneShot(key, _hashAlgorithm, label, context, destination);
+ }
+ finally
+ {
+ ReleaseKey();
+ }
+ }
+
+ public override void Dispose()
+ {
+ if (Interlocked.Exchange(ref _disposed, 1) == 0)
+ {
+ ReleaseKey();
+ }
+ }
+
+ private byte[] IncrementAndAcquireKey()
+ {
+ while (true)
+ {
+ int current = Volatile.Read(ref _keyReferenceCount);
+
+ if (current == 0)
+ {
+ throw new ObjectDisposedException(nameof(SP800108HmacCounterKdfImplementationManaged));
+ }
+
+ Debug.Assert(current > 0);
+ int incrementedCount = checked(current + 1);
+
+ if (Interlocked.CompareExchange(ref _keyReferenceCount, incrementedCount, current) == current)
+ {
+ return _key;
+ }
+ }
+ }
+
+ public void ReleaseKey()
+ {
+ int newReferenceCount = Interlocked.Decrement(ref _keyReferenceCount);
+ Debug.Assert(newReferenceCount >= 0, newReferenceCount.ToString());
+
+ if (newReferenceCount == 0)
+ {
+ ZeroKey();
+ }
+ }
+
+ private void ZeroKey()
+ {
+ CryptographicOperations.ZeroMemory(_key);
+ _key = null!;
+ }
+ }
+}
diff --git a/src/libraries/Common/src/System/Security/Cryptography/Utf8DataEncoding.cs b/src/libraries/Common/src/System/Security/Cryptography/Utf8DataEncoding.cs
new file mode 100644
index 0000000000000..c550f0fdfca59
--- /dev/null
+++ b/src/libraries/Common/src/System/Security/Cryptography/Utf8DataEncoding.cs
@@ -0,0 +1,39 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System.Text;
+
+namespace System.Security.Cryptography
+{
+ internal readonly ref struct Utf8DataEncoding
+ {
+ internal static Encoding ThrowingUtf8Encoding { get; } = new UTF8Encoding(false, true);
+
+ private readonly byte[]? _rented;
+ private readonly Span _buffer;
+
+ internal Utf8DataEncoding(ReadOnlySpan data, Span stackBuffer)
+ {
+ int maxLength = ThrowingUtf8Encoding.GetMaxByteCount(data.Length);
+ _buffer = (uint)maxLength <= stackBuffer.Length ?
+ stackBuffer :
+ (_rented = CryptoPool.Rent(maxLength));
+
+ int written = ThrowingUtf8Encoding.GetBytes(data, _buffer);
+ _buffer = _buffer.Slice(0, written);
+ }
+
+ internal ReadOnlySpan Utf8Bytes => _buffer;
+
+ internal void Dispose()
+ {
+ CryptographicOperations.ZeroMemory(_buffer);
+
+ if (_rented is not null)
+ {
+ CryptoPool.Return(_rented, clearSize: 0);
+ }
+ }
+ }
+
+}
diff --git a/src/libraries/Common/tests/System/Security/Cryptography/SP800108HmacCounterKdfTests.ArgValidation.cs b/src/libraries/Common/tests/System/Security/Cryptography/SP800108HmacCounterKdfTests.ArgValidation.cs
new file mode 100644
index 0000000000000..a492b4b452f6b
--- /dev/null
+++ b/src/libraries/Common/tests/System/Security/Cryptography/SP800108HmacCounterKdfTests.ArgValidation.cs
@@ -0,0 +1,326 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System.Text;
+using Xunit;
+
+namespace System.Security.Cryptography.Tests
+{
+ public static partial class SP800108HmacCounterKdfTests
+ {
+ [Fact]
+ public static void DeriveBytes_Allocating_ArrayBytes_ArgValidation()
+ {
+ Assert.Throws("key", () =>
+ SP800108HmacCounterKdf.DeriveBytes(key: (byte[])null, HashAlgorithmName.SHA256, s_labelBytes, s_contextBytes, 42));
+
+ Assert.Throws("label", () =>
+ SP800108HmacCounterKdf.DeriveBytes(s_kdk, HashAlgorithmName.SHA256, label: (byte[])null, s_contextBytes, 42));
+
+ Assert.Throws("context", () =>
+ SP800108HmacCounterKdf.DeriveBytes(s_kdk, HashAlgorithmName.SHA256, s_labelBytes, context: (byte[])null, 42));
+
+ Assert.Throws("hashAlgorithm", () =>
+ SP800108HmacCounterKdf.DeriveBytes(s_kdk, s_nullHash, s_labelBytes, s_contextBytes, 42));
+
+ Assert.Throws("hashAlgorithm", () =>
+ SP800108HmacCounterKdf.DeriveBytes(s_kdk, s_emptyHash, s_labelBytes, s_contextBytes, 42));
+
+ CryptographicException ex = Assert.Throws(() =>
+ SP800108HmacCounterKdf.DeriveBytes(s_kdk, s_unknownHash, s_labelBytes, s_contextBytes, 42));
+ Assert.Contains(s_unknownHash.Name, ex.Message);
+
+ Assert.Throws("derivedKeyLengthInBytes", () =>
+ SP800108HmacCounterKdf.DeriveBytes(s_kdk, HashAlgorithmName.SHA256, s_labelBytes, s_contextBytes, -1));
+
+ Assert.Throws("derivedKeyLengthInBytes", () =>
+ SP800108HmacCounterKdf.DeriveBytes(s_kdk, HashAlgorithmName.SHA256, s_labelBytes, s_contextBytes, 0x20000000));
+ }
+
+ [Fact]
+ public static void DeriveBytes_Allocating_String_ArgValidation()
+ {
+ Assert.Throws("key", () =>
+ SP800108HmacCounterKdf.DeriveBytes(key: (byte[])null, HashAlgorithmName.SHA256, Label, Context, 42));
+
+ Assert.Throws("label", () =>
+ SP800108HmacCounterKdf.DeriveBytes(s_kdk, HashAlgorithmName.SHA256, label: (string)null, Context, 42));
+
+ Assert.Throws("context", () =>
+ SP800108HmacCounterKdf.DeriveBytes(s_kdk, HashAlgorithmName.SHA256, Label, context: (string)null, 42));
+
+ Assert.Throws("hashAlgorithm", () =>
+ SP800108HmacCounterKdf.DeriveBytes(s_kdk, s_nullHash, Label, Context, 42));
+
+ Assert.Throws("hashAlgorithm", () =>
+ SP800108HmacCounterKdf.DeriveBytes(s_kdk, s_emptyHash, Label, Context, 42));
+
+ CryptographicException ex = Assert.Throws(() =>
+ SP800108HmacCounterKdf.DeriveBytes(s_kdk, s_unknownHash, Label, Context, 42));
+ Assert.Contains(s_unknownHash.Name, ex.Message);
+
+ Assert.Throws("derivedKeyLengthInBytes", () =>
+ SP800108HmacCounterKdf.DeriveBytes(s_kdk, HashAlgorithmName.SHA256, Label, Context, -1));
+
+ Assert.Throws("derivedKeyLengthInBytes", () =>
+ SP800108HmacCounterKdf.DeriveBytes(s_kdk, HashAlgorithmName.SHA256, Label, Context, 0x20000000));
+ }
+
+ [Fact]
+ public static void DeriveBytes_Allocating_SpanBytes_ArgValidation()
+ {
+ Assert.Throws("hashAlgorithm", () =>
+ SP800108HmacCounterKdf.DeriveBytes(s_kdk.AsSpan(), s_nullHash, s_labelBytes.AsSpan(), s_contextBytes.AsSpan(), 42));
+
+ Assert.Throws("hashAlgorithm", () =>
+ SP800108HmacCounterKdf.DeriveBytes(s_kdk.AsSpan(), s_emptyHash, s_labelBytes.AsSpan(), s_contextBytes.AsSpan(), 42));
+
+ CryptographicException ex = Assert.Throws(() =>
+ SP800108HmacCounterKdf.DeriveBytes(s_kdk.AsSpan(), s_unknownHash, s_labelBytes.AsSpan(), s_contextBytes.AsSpan(), 42));
+ Assert.Contains(s_unknownHash.Name, ex.Message);
+
+ Assert.Throws("derivedKeyLengthInBytes", () =>
+ SP800108HmacCounterKdf.DeriveBytes(s_kdk.AsSpan(), HashAlgorithmName.SHA256, s_labelBytes.AsSpan(), s_contextBytes.AsSpan(), -1));
+
+ Assert.Throws("derivedKeyLengthInBytes", () =>
+ SP800108HmacCounterKdf.DeriveBytes(s_kdk.AsSpan(), HashAlgorithmName.SHA256, s_labelBytes.AsSpan(), s_contextBytes.AsSpan(), 0x20000000));
+ }
+
+ [Fact]
+ public static void DeriveBytes_BufferFill_SpanBytes_ArgValidation()
+ {
+ byte[] destination = new byte[42];
+
+ Assert.Throws("hashAlgorithm", () =>
+ SP800108HmacCounterKdf.DeriveBytes(s_kdk, s_nullHash, s_labelBytes, s_contextBytes, destination));
+
+ Assert.Throws("hashAlgorithm", () =>
+ SP800108HmacCounterKdf.DeriveBytes(s_kdk, s_emptyHash, s_labelBytes, s_contextBytes, destination));
+
+ CryptographicException ex = Assert.Throws(() =>
+ SP800108HmacCounterKdf.DeriveBytes(s_kdk, s_unknownHash, s_labelBytes, s_contextBytes, destination));
+ Assert.Contains(s_unknownHash.Name, ex.Message);
+
+ Assert.Throws("destination", () =>
+ SP800108HmacCounterKdf.DeriveBytes(s_kdk, HashAlgorithmName.SHA256, s_labelBytes, s_contextBytes, GetOversizedSpan()));
+ }
+
+ [Fact]
+ public static void DeriveBytes_Allocating_SpanChars_ArgValidation()
+ {
+ Assert.Throws("hashAlgorithm", () =>
+ SP800108HmacCounterKdf.DeriveBytes(s_kdk, s_nullHash, Label.AsSpan(), Context.AsSpan(), 42));
+
+ Assert.Throws("hashAlgorithm", () =>
+ SP800108HmacCounterKdf.DeriveBytes(s_kdk, s_emptyHash, Label.AsSpan(), Context.AsSpan(), 42));
+
+ CryptographicException ex = Assert.Throws(() =>
+ SP800108HmacCounterKdf.DeriveBytes(s_kdk, s_unknownHash, Label.AsSpan(), Context.AsSpan(), 42));
+ Assert.Contains(s_unknownHash.Name, ex.Message);
+
+ Assert.Throws("derivedKeyLengthInBytes", () =>
+ SP800108HmacCounterKdf.DeriveBytes(s_kdk, HashAlgorithmName.SHA256, Label.AsSpan(), Context.AsSpan(), -1));
+
+ Assert.Throws("derivedKeyLengthInBytes", () =>
+ SP800108HmacCounterKdf.DeriveBytes(s_kdk, HashAlgorithmName.SHA256, Label.AsSpan(), Context.AsSpan(), 0x20000000));
+ }
+
+ [Fact]
+ public static void DeriveBytes_BufferFill_SpanChars_ArgValidation()
+ {
+ byte[] destination = new byte[42];
+
+ Assert.Throws("hashAlgorithm", () =>
+ SP800108HmacCounterKdf.DeriveBytes(s_kdk, s_nullHash, Label.AsSpan(), Context.AsSpan(), destination));
+
+ Assert.Throws("hashAlgorithm", () =>
+ SP800108HmacCounterKdf.DeriveBytes(s_kdk, s_emptyHash, Label.AsSpan(), Context.AsSpan(), destination));
+
+ CryptographicException ex = Assert.Throws(() =>
+ SP800108HmacCounterKdf.DeriveBytes(s_kdk, s_unknownHash, Label.AsSpan(), Context.AsSpan(), destination));
+ Assert.Contains(s_unknownHash.Name, ex.Message);
+
+ Assert.Throws("destination", () =>
+ SP800108HmacCounterKdf.DeriveBytes(s_kdk, HashAlgorithmName.SHA256, Label.AsSpan(), Context.AsSpan(), GetOversizedSpan()));
+ }
+
+ [Fact]
+ public static void Ctor_KeyArray_ArgValidation()
+ {
+ Assert.Throws("key", () =>
+ new SP800108HmacCounterKdf((byte[])null, HashAlgorithmName.SHA256));
+
+ Assert.Throws("hashAlgorithm", () =>
+ new SP800108HmacCounterKdf(s_kdk, s_nullHash));
+
+ Assert.Throws("hashAlgorithm", () =>
+ new SP800108HmacCounterKdf(s_kdk, s_emptyHash));
+
+ CryptographicException ex = Assert.Throws(() =>
+ new SP800108HmacCounterKdf(s_kdk, s_unknownHash));
+ Assert.Contains(s_unknownHash.Name, ex.Message);
+ }
+
+ [Fact]
+ public static void Ctor_KeySpan_ArgValidation()
+ {
+ Assert.Throws("hashAlgorithm", () =>
+ new SP800108HmacCounterKdf(s_kdk.AsSpan(), s_nullHash));
+
+ Assert.Throws("hashAlgorithm", () =>
+ new SP800108HmacCounterKdf(s_kdk.AsSpan(), s_emptyHash));
+
+ CryptographicException ex = Assert.Throws(() =>
+ new SP800108HmacCounterKdf(s_kdk.AsSpan(), s_unknownHash));
+ Assert.Contains(s_unknownHash.Name, ex.Message);
+ }
+
+ [Fact]
+ public static void DeriveKey_Allocating_ArrayBytes_ArgValidation()
+ {
+ using SP800108HmacCounterKdf kdf = new SP800108HmacCounterKdf(s_kdk, HashAlgorithmName.SHA256);
+
+ Assert.Throws("label", () =>
+ kdf.DeriveKey((byte[])null, s_contextBytes, 42));
+
+ Assert.Throws("context", () =>
+ kdf.DeriveKey(s_labelBytes, (byte[])null, 42));
+
+ Assert.Throws("derivedKeyLengthInBytes", () =>
+ kdf.DeriveKey(s_labelBytes, s_contextBytes, -1));
+
+ Assert.Throws("derivedKeyLengthInBytes", () =>
+ kdf.DeriveKey(s_labelBytes, s_contextBytes, 0x20000000));
+ }
+
+ [Fact]
+ public static void DeriveKey_Allocating_SpanBytes_ArgValidation()
+ {
+ using SP800108HmacCounterKdf kdf = new SP800108HmacCounterKdf(s_kdk, HashAlgorithmName.SHA256);
+
+ Assert.Throws("derivedKeyLengthInBytes", () =>
+ kdf.DeriveKey(s_labelBytes.AsSpan(), s_contextBytes.AsSpan(), -1));
+
+ Assert.Throws("derivedKeyLengthInBytes", () =>
+ kdf.DeriveKey(s_labelBytes.AsSpan(), s_contextBytes.AsSpan(), 0x20000000));
+ }
+
+ [Fact]
+ public static void DeriveKey_BufferFill_SpanBytes_ArgValidation()
+ {
+ using SP800108HmacCounterKdf kdf = new SP800108HmacCounterKdf(s_kdk, HashAlgorithmName.SHA256);
+
+ Assert.Throws("destination", () =>
+ kdf.DeriveKey(s_labelBytes.AsSpan(), s_contextBytes.AsSpan(), GetOversizedSpan()));
+ }
+
+ [Fact]
+ public static void DeriveKey_Allocating_SpanChars_ArgValidation()
+ {
+ using SP800108HmacCounterKdf kdf = new SP800108HmacCounterKdf(s_kdk, HashAlgorithmName.SHA256);
+
+ Assert.Throws("derivedKeyLengthInBytes", () =>
+ kdf.DeriveKey(Label.AsSpan(), Context.AsSpan(), -1));
+
+ Assert.Throws("derivedKeyLengthInBytes", () =>
+ kdf.DeriveKey(Label.AsSpan(), Context.AsSpan(), 0x20000000));
+ }
+
+ [Fact]
+ public static void DeriveKey_BufferFill_SpanChars_ArgValidation()
+ {
+ using SP800108HmacCounterKdf kdf = new SP800108HmacCounterKdf(s_kdk, HashAlgorithmName.SHA256);
+
+ Assert.Throws("destination", () =>
+ kdf.DeriveKey(Label.AsSpan(), Context.AsSpan(), GetOversizedSpan()));
+ }
+
+ [Fact]
+ public static void DeriveKey_Allocating_String_ArgValidation()
+ {
+ using SP800108HmacCounterKdf kdf = new SP800108HmacCounterKdf(s_kdk, HashAlgorithmName.SHA256);
+
+ Assert.Throws("derivedKeyLengthInBytes", () =>
+ kdf.DeriveKey(Label, Context, -1));
+
+ Assert.Throws("derivedKeyLengthInBytes", () =>
+ kdf.DeriveKey(Label, Context, 0x20000000));
+ }
+
+ [Fact]
+ public static void DeriveKey_Allocating_String_InvalidUTF8()
+ {
+ using SP800108HmacCounterKdf kdf = new SP800108HmacCounterKdf(s_kdk, HashAlgorithmName.SHA256);
+
+ Assert.Throws(() =>
+ kdf.DeriveKey("\uD800", Context, 42));
+
+ Assert.Throws(() =>
+ kdf.DeriveKey(Label, "\uD800", 42));
+ }
+
+ [Fact]
+ public static void DeriveKey_BufferFill_SpanChars_InvalidUTF8()
+ {
+ using SP800108HmacCounterKdf kdf = new SP800108HmacCounterKdf(s_kdk, HashAlgorithmName.SHA256);
+ byte[] derivedKey = new byte[42];
+
+ Assert.Throws(() =>
+ kdf.DeriveKey("\uD800".AsSpan(), Context.AsSpan(), derivedKey));
+
+ Assert.Throws(() =>
+ kdf.DeriveKey(Label.AsSpan(), "\uD800".AsSpan(), derivedKey));
+ }
+
+ [Fact]
+ public static void DeriveKey_Allocating_SpanChars_InvalidUTF8()
+ {
+ using SP800108HmacCounterKdf kdf = new SP800108HmacCounterKdf(s_kdk, HashAlgorithmName.SHA256);
+
+ Assert.Throws(() =>
+ kdf.DeriveKey("\uD800".AsSpan(), Context.AsSpan(), 42));
+
+ Assert.Throws(() =>
+ kdf.DeriveKey(Label.AsSpan(), "\uD800".AsSpan(), 42));
+ }
+
+ [Fact]
+ public static void DeriveBytes_BufferFill_SpanChars_InvalidUTF8()
+ {
+ byte[] destination = new byte[42];
+
+ Assert.Throws(() =>
+ SP800108HmacCounterKdf.DeriveBytes(s_kdk, HashAlgorithmName.SHA256, Label.AsSpan(), "\uD800".AsSpan(), destination));
+
+ Assert.Throws(() =>
+ SP800108HmacCounterKdf.DeriveBytes(s_kdk, HashAlgorithmName.SHA256, "\uD800".AsSpan(), Context.AsSpan(), destination));
+ }
+
+ [Fact]
+ public static void DeriveBytes_Allocating_SpanChars_InvalidUTF8()
+ {
+ Assert.Throws(() =>
+ SP800108HmacCounterKdf.DeriveBytes(s_kdk, HashAlgorithmName.SHA256, Label.AsSpan(), "\uD800".AsSpan(), 42));
+
+ Assert.Throws(() =>
+ SP800108HmacCounterKdf.DeriveBytes(s_kdk, HashAlgorithmName.SHA256, "\uD800".AsSpan(), Context.AsSpan(), 42));
+ }
+
+ [Fact]
+ public static void DeriveBytes_Allocating_String_InvalidUTF8()
+ {
+ Assert.Throws(() =>
+ SP800108HmacCounterKdf.DeriveBytes(s_kdk, HashAlgorithmName.SHA256, Label, "\uD800", 42));
+
+ Assert.Throws(() =>
+ SP800108HmacCounterKdf.DeriveBytes(s_kdk, HashAlgorithmName.SHA256, "\uD800", Context, 42));
+ }
+
+ private unsafe static Span GetOversizedSpan()
+ {
+ // This creates an very large span over some address space. The memory in this span should never be read
+ // or written to; it should only be used for length checking of the span for argument validation.
+ return new Span((void*)0, 0x20000000);
+ }
+ }
+}
diff --git a/src/libraries/Common/tests/System/Security/Cryptography/SP800108HmacCounterKdfTests.Functional.cs b/src/libraries/Common/tests/System/Security/Cryptography/SP800108HmacCounterKdfTests.Functional.cs
new file mode 100644
index 0000000000000..338ffc7a2a652
--- /dev/null
+++ b/src/libraries/Common/tests/System/Security/Cryptography/SP800108HmacCounterKdfTests.Functional.cs
@@ -0,0 +1,357 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System.Collections.Generic;
+using System.Globalization;
+using Xunit;
+
+namespace System.Security.Cryptography.Tests
+{
+ public static partial class SP800108HmacCounterKdfTests
+ {
+ [Theory]
+ [InlineData("V47WmHzPSkdC2vkLAomIjCzZlDOAetll3yJLcSvon7LJFjJpEN+KnSNp+gIpeydKMsENkflbrIZ/3s6GkEaH")]
+ [InlineData("mVaFM4deXLl610CmnCteNzxgbM/VkmKznAlPauHcDBn0le06uOjAKLHx0LfoU2/Ttq9nd78Y6Nk6wArmdwJgJg==")]
+ [InlineData("GaHPeqdUxriFpjRtkYQYWr5/iqneD/+hPhVJQt4rXblxSpB1UUqGqL00DMU/FJkX0iMCfqUjQXtXyfks+p++Ev4=")]
+ public static void AspNetCoreTestVectors_Basic(string expectedBase64)
+ {
+ // These tests are from the dotnet/aspnetcore repo.
+ byte[] expected = Convert.FromBase64String(expectedBase64);
+ VerifyKbkdf(expected, s_kdk, HashAlgorithmName.SHA512, Label.ToCharArray(), Context.ToCharArray());
+ }
+
+ [Theory]
+ [InlineData("rt2hM6kkQ8hAXmkHx0TU4o3Q+S7fie6b3S1LAq107k++P9v8uSYA2G+WX3pJf9ZkpYrTKD7WUIoLkgA1R9lk")]
+ [InlineData("RKiXmHSrWq5gkiRSyNZWNJrMR0jDyYHJMt9odOayRAE5wLSX2caINpQmfzTH7voJQi3tbn5MmD//dcspghfBiw==")]
+ [InlineData("KedXO0zAIZ3AfnPqY1NnXxpC3HDHIxefG4bwD3g6nWYEc5+q7pjbam71Yqj0zgHMNC9Z7BX3wS1/tajFocRWZUk=")]
+ public static void AspNetCoreTestVectors_LargeKdk(string expectedBase64)
+ {
+ // These tests are from the dotnet/aspnetcore repo.
+ // Win32 BCryptKeyDerivation doesn't perform RFC 2104, section 2 key adjustment for the KDK.
+ // We do this for CNG so that there is no functional limit on the KDK size.
+ byte[] kdk = new byte[50000];
+
+ for (int i = 0; i < kdk.Length; i++)
+ {
+ kdk[i] = (byte)i;
+ }
+
+ byte[] expected = Convert.FromBase64String(expectedBase64);
+ VerifyKbkdf(expected, kdk, HashAlgorithmName.SHA512, Label.ToCharArray(), Context.ToCharArray());
+ }
+
+ [Theory]
+ [MemberData(nameof(GetRfc8009TestVectors))]
+ public static void Rfc8009Tests(byte[] kdk, byte[] expected, HashAlgorithmName hashAlgorithm)
+ {
+ VerifyKbkdf(expected, kdk, hashAlgorithm, "prf".ToCharArray(), "test".ToCharArray());
+ }
+
+ [Theory]
+ [InlineData(new byte[] { 0xcf, 0x4b, 0xfe, 0x4f, 0x85, 0xa1, 0x0b, 0xad }, nameof(HashAlgorithmName.SHA1))]
+ [InlineData(new byte[] { 0x00, 0x26, 0x4b, 0xbb, 0x14, 0x97, 0x40, 0x54 }, nameof(HashAlgorithmName.SHA256))]
+ [InlineData(new byte[] { 0xc7, 0x10, 0x27, 0x87, 0xd8, 0x96, 0xbc, 0x89 }, nameof(HashAlgorithmName.SHA384))]
+ [InlineData(new byte[] { 0xdb, 0x3a, 0x18, 0xd9, 0x6c, 0x4a, 0xd4, 0x1e }, nameof(HashAlgorithmName.SHA512))]
+ public static void SymCryptTestVectors(byte[] expected, string hashAlgorithm)
+ {
+ // These test vectors come from https://github.com/microsoft/SymCrypt.
+ // See sp800_108_hmacsha1.c, sp800_108_hmacsha256.c, and sp800_108_hmacsha512.c.
+ byte[] symCryptKey = new byte[]
+ {
+ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15,
+ 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31,
+ };
+
+ byte[] key = symCryptKey.AsSpan(0, 8).ToArray();
+ byte[] label = "Label"u8.ToArray();
+ byte[] context = symCryptKey.AsSpan(16, 16).ToArray();
+
+ HashAlgorithmName hashAlgorithmName = new HashAlgorithmName(hashAlgorithm);
+ VerifyKbkdfBytes(expected, key, hashAlgorithmName, label, context);
+ }
+
+ [Theory]
+ [InlineData(new byte[] { }, "label!", "context?", new byte[] { 0xb6, 0xff, 0x26, 0x61, 0xea, 0x43, 0x76, 0xd2 })]
+ [InlineData(new byte[] { 0xFE }, "", "context?", new byte[] { 0xed, 0xdf, 0x50, 0x06, 0x3c, 0x26, 0x3e, 0xd9 })]
+ [InlineData(new byte[] { 0xFE }, "label!", "", new byte[] { 0x98, 0x83, 0x67, 0x41, 0x3f, 0x2d, 0x90, 0x72 })]
+ [InlineData(new byte[] { }, "", "", new byte[] { 0x18, 0x0f, 0xf7, 0xa2, 0xbc, 0x8d, 0x6e, 0x98 })]
+ [InlineData(new byte[] { }, "", "", new byte[] { })]
+ public static void EmptyTests(byte[] key, string label, string context, byte[] expected)
+ {
+ VerifyKbkdf(expected, key, HashAlgorithmName.SHA256, label.ToCharArray(), context.ToCharArray());
+ }
+
+ [Theory]
+ [InlineData(nameof(HashAlgorithmName.SHA1), 512 / 8 - 1, new byte[] { 0xc9, 0x0f, 0x9d, 0x91, 0x85, 0xe5, 0xeb, 0x9b })]
+ [InlineData(nameof(HashAlgorithmName.SHA1), 512 / 8, new byte[] { 0x7b, 0xdb, 0x38, 0x28, 0xc0, 0x9f, 0x49, 0x05 })]
+ [InlineData(nameof(HashAlgorithmName.SHA1), 512 / 8 + 1, new byte[] { 0x6c, 0x3a, 0xba, 0x28, 0x38, 0xad, 0x51, 0x2c })]
+ [InlineData(nameof(HashAlgorithmName.SHA256), 512 / 8 - 1, new byte[] { 0x88, 0xaa, 0xc7, 0xee, 0x05, 0x65, 0xfd, 0xda })]
+ [InlineData(nameof(HashAlgorithmName.SHA256), 512 / 8, new byte[] { 0x3d, 0xdc, 0x7d, 0xec, 0x0a, 0xfd, 0x7a, 0xc0 })]
+ [InlineData(nameof(HashAlgorithmName.SHA256), 512 / 8 + 1, new byte[] { 0x47, 0x95, 0x00, 0xd5, 0x55, 0x1f, 0xb3, 0x85 })]
+ [InlineData(nameof(HashAlgorithmName.SHA384), 1024 / 8 - 1, new byte[] { 0x84, 0xd8, 0xfd, 0x33, 0x4f, 0x07, 0x81, 0x9b })]
+ [InlineData(nameof(HashAlgorithmName.SHA384), 1024 / 8, new byte[] { 0x6c, 0xa2, 0x5d, 0x4f, 0x61, 0x2d, 0x0f, 0x20 })]
+ [InlineData(nameof(HashAlgorithmName.SHA384), 1024 / 8 + 1, new byte[] { 0xe4, 0x0e, 0xbd, 0x41, 0x14, 0xe6, 0x80, 0x59 })]
+ [InlineData(nameof(HashAlgorithmName.SHA512), 1024 / 8 - 1, new byte[] { 0xa4, 0xe5, 0x24, 0xe8, 0x56, 0x2b, 0x48, 0xa4 })]
+ [InlineData(nameof(HashAlgorithmName.SHA512), 1024 / 8, new byte[] { 0xba, 0xf6, 0xed, 0xa7, 0x3a, 0xf7, 0x12, 0x27 })]
+ [InlineData(nameof(HashAlgorithmName.SHA512), 1024 / 8 + 1, new byte[] { 0x34, 0xdf, 0x2d, 0x21, 0xfd, 0xf1, 0x0e, 0x13 })]
+ public static void Kdk_HmacBlockBoundarySizes(string hashAlgorithmName, int kdkSize, byte[] expected)
+ {
+ // We do HMAC key adjust for the CNG implementation when the kdk exceeds the block size of the HMAC algorithm.
+ // This tests one byte below, at, and above the block size for each HMAC algorithm.
+ // Verified against OpenSSL 3. Example command used below. Adjust the digest and the seq upper boundary as needed.
+ // Note that OpenSSL calls the label "salt" and the context "info".
+ //
+ // openssl kdf -keylen 8 -kdfopt mac:HMAC -kdfopt digest:SHA1 \
+ // -kdfopt hexkey:$(seq 1 63 | awk '{ printf "%02x", $1 }') -kdfopt salt:icecream \
+ // -kdfopt info:sandwiches -binary KBKDF | xxd -i
+
+ byte[] kdk = new byte[kdkSize];
+
+ for (int i = 0; i < kdkSize; i++)
+ {
+ kdk[i] = (byte)checked(i + 1);
+ }
+
+ HashAlgorithmName alg = new HashAlgorithmName(hashAlgorithmName);
+ VerifyKbkdf(expected, kdk, alg, "icecream".ToCharArray(), "sandwiches".ToCharArray());
+ }
+
+ [Theory]
+ [MemberData(nameof(GetOutputLengthBoundaries))]
+ public static void OutputLength_AroundPrfOutputBoundaries(string hashAlgorithmName, byte[] expected)
+ {
+ byte[] kdk = new byte[] { 1, 2, 3, 4, 5, 6, 7, 8 };
+ HashAlgorithmName alg = new HashAlgorithmName(hashAlgorithmName);
+ VerifyKbkdf(expected, kdk, alg, "mustard".ToCharArray(), "ketchup".ToCharArray());
+ }
+
+ [Fact]
+ public static void MultipleDisposes_NoThrow()
+ {
+ SP800108HmacCounterKdf kdf = new SP800108HmacCounterKdf(s_kdk, HashAlgorithmName.SHA256);
+ kdf.Dispose();
+
+ Assert.Throws(() => kdf.DeriveKey(s_labelBytes, s_contextBytes, 42));
+ kdf.Dispose();
+ }
+
+ public static IEnumerable