Add support for SP800-108 CTR Key Derivation Function
authorKevin Jones <kevin@vcsjones.com>
Tue, 7 Feb 2023 17:25:31 +0000 (12:25 -0500)
committerGitHub <noreply@github.com>
Tue, 7 Feb 2023 17:25:31 +0000 (09:25 -0800)
This introduces a class for producing values per the "KDF in Counter Mode" section in NIST Special Publication 800-108r1 (Recommendation for Key Derivation Using Pseudorandom Functions).

The `SP800108HmacCounterKdf` class is part of the inbox cryptography library on .NET 8.  Based on demonstrated need for older TFMs, the type is also being exposed via a NuGet package: Microsoft.Bcl.Cryptography.  This package may, in the future, contain other types that belong as part of inbox cryptography but have a demonstrated need to be available to older TFMs.

30 files changed:
src/libraries/Common/src/Interop/Windows/BCrypt/Interop.BCryptAlgPseudoHandle.cs
src/libraries/Common/src/System/Security/Cryptography/SP800108HmacCounterKdf.cs [new file with mode: 0644]
src/libraries/Common/src/System/Security/Cryptography/SP800108HmacCounterKdfImplementationBase.cs [new file with mode: 0644]
src/libraries/Common/src/System/Security/Cryptography/SP800108HmacCounterKdfImplementationCng.cs [new file with mode: 0644]
src/libraries/Common/src/System/Security/Cryptography/SP800108HmacCounterKdfImplementationManaged.cs [new file with mode: 0644]
src/libraries/Common/src/System/Security/Cryptography/Utf8DataEncoding.cs [new file with mode: 0644]
src/libraries/Common/tests/System/Security/Cryptography/SP800108HmacCounterKdfTests.ArgValidation.cs [new file with mode: 0644]
src/libraries/Common/tests/System/Security/Cryptography/SP800108HmacCounterKdfTests.Functional.cs [new file with mode: 0644]
src/libraries/Common/tests/System/Security/Cryptography/SP800108HmacCounterKdfTests.Helpers.cs [new file with mode: 0644]
src/libraries/Common/tests/System/Security/Cryptography/SP800108HmacCounterKdfTests.ThreadSafety.cs [new file with mode: 0644]
src/libraries/Microsoft.Bcl.Cryptography/Microsoft.Bcl.Cryptography.sln [new file with mode: 0644]
src/libraries/Microsoft.Bcl.Cryptography/ref/Microsoft.Bcl.Cryptography.Forwards.cs [new file with mode: 0644]
src/libraries/Microsoft.Bcl.Cryptography/ref/Microsoft.Bcl.Cryptography.cs [new file with mode: 0644]
src/libraries/Microsoft.Bcl.Cryptography/ref/Microsoft.Bcl.Cryptography.csproj [new file with mode: 0644]
src/libraries/Microsoft.Bcl.Cryptography/src/Microsoft.Bcl.Cryptography.csproj [new file with mode: 0644]
src/libraries/Microsoft.Bcl.Cryptography/src/Resources/Strings.resx [new file with mode: 0644]
src/libraries/Microsoft.Bcl.Cryptography/src/System/Security/Cryptography/HashAlgorithmNames.cs [new file with mode: 0644]
src/libraries/Microsoft.Bcl.Cryptography/src/System/Security/Cryptography/NetStandardShims.cs [new file with mode: 0644]
src/libraries/Microsoft.Bcl.Cryptography/src/System/Security/Cryptography/SP800108HmacCounterKdf.cs [new file with mode: 0644]
src/libraries/Microsoft.Bcl.Cryptography/src/System/Security/Cryptography/SP800108HmacCounterKdfImplementationCng.cs [new file with mode: 0644]
src/libraries/Microsoft.Bcl.Cryptography/src/System/Security/Cryptography/SP800108HmacCounterKdfImplementationManaged.cs [new file with mode: 0644]
src/libraries/Microsoft.Bcl.Cryptography/tests/Microsoft.Bcl.Cryptography.Tests.csproj [new file with mode: 0644]
src/libraries/System.Security.Cryptography/ref/System.Security.Cryptography.cs
src/libraries/System.Security.Cryptography/src/Resources/Strings.resx
src/libraries/System.Security.Cryptography/src/System.Security.Cryptography.csproj
src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/SP800108HmacCounterKdf.Managed.cs [new file with mode: 0644]
src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/SP800108HmacCounterKdf.Windows.cs [new file with mode: 0644]
src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/SP800108HmacCounterKdfImplementationCng.cs [new file with mode: 0644]
src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/SP800108HmacCounterKdfImplementationManaged.cs [new file with mode: 0644]
src/libraries/System.Security.Cryptography/tests/System.Security.Cryptography.Tests.csproj

index 433e6f5..57c508b 100644 (file)
@@ -25,6 +25,15 @@ internal static partial class Interop
             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 (file)
index 0000000..6598136
--- /dev/null
@@ -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
+{
+    /// <summary>
+    ///   NIST SP 800-108 HMAC CTR Key-Based Key Derivation (KBKDF)
+    /// </summary>
+    /// <remarks>
+    ///   <para>
+    ///     This implements NIST SP 800-108 HMAC in counter mode. The implemented KDF assumes the form of
+    ///     <c>PRF (KI, [i]2 || Label || 0x00 || Context || [L]2)</c> where <c>[i]2</c> and <c>[L]2</c> are encoded as
+    ///     unsigned 32-bit integers, big endian.
+    ///   </para>
+    ///   <para>
+    ///     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 <see cref="ObjectDisposedException" /> or produce a valid
+    ///     derived key.
+    ///   </para>
+    /// </remarks>
+    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<byte> key,
+            HashAlgorithmName hashAlgorithm);
+
+        /// <summary>
+        ///   Initializes a new instance of <see cref="SP800108HmacCounterKdf" /> using a specified key and HMAC algorithm.
+        /// </summary>
+        /// <param name="key">The key-derivation key.</param>
+        /// <param name="hashAlgorithm">The HMAC algorithm.</param>
+        /// <exception cref="ArgumentNullException">
+        ///   <paramref name="hashAlgorithm" /> has a <see cref="HashAlgorithmName.Name" /> which is <see langword="null" />.
+        /// </exception>
+        /// <exception cref="ArgumentException">
+        ///   <paramref name="hashAlgorithm" /> has a <see cref="HashAlgorithmName.Name" /> which is empty.
+        /// </exception>
+        /// <exception cref="CryptographicException">
+        ///   <paramref name="hashAlgorithm"/> is not a known or supported hash algorithm.
+        /// </exception>
+        /// <exception cref="PlatformNotSupportedException">
+        ///   The current platform does not have a supported implementation of HMAC.
+        /// </exception>
+        public SP800108HmacCounterKdf(ReadOnlySpan<byte> key, HashAlgorithmName hashAlgorithm)
+        {
+            CheckHashAlgorithm(hashAlgorithm);
+            _implementation = CreateImplementation(key, hashAlgorithm);
+        }
+
+        /// <summary>
+        ///   Initializes a new instance of <see cref="SP800108HmacCounterKdf" /> using a specified key and HMAC algorithm.
+        /// </summary>
+        /// <param name="key">The key-derivation key.</param>
+        /// <param name="hashAlgorithm">The HMAC algorithm.</param>
+        /// <exception cref="ArgumentNullException">
+        ///   <para>
+        ///     <paramref name="hashAlgorithm" /> has a <see cref="HashAlgorithmName.Name" /> which is <see langword="null" />.
+        ///   </para>
+        ///   <para> -or- </para>
+        ///   <para>
+        ///     <paramref name="key" /> is <see langword="null" />.
+        ///   </para>
+        /// </exception>
+        /// <exception cref="ArgumentException">
+        ///   <paramref name="hashAlgorithm" /> has a <see cref="HashAlgorithmName.Name" /> which is empty.
+        /// </exception>
+        /// <exception cref="CryptographicException">
+        ///   <paramref name="hashAlgorithm"/> is not a known or supported hash algorithm.
+        /// </exception>
+        /// <exception cref="PlatformNotSupportedException">
+        ///   The current platform does not have a supported implementation of HMAC.
+        /// </exception>
+        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);
+        }
+
+        /// <summary>
+        ///   Derives a key of a specified length.
+        /// </summary>
+        /// <param name="key">The key-derivation key.</param>
+        /// <param name="hashAlgorithm">The HMAC algorithm.</param>
+        /// <param name="label">The label that identifies the purpose for the derived key.</param>
+        /// <param name="context">The context containing information related to the derived key.</param>
+        /// <param name="derivedKeyLengthInBytes">The length of the derived key, in bytes.</param>
+        /// <returns>An array containing the derived key.</returns>
+        /// <exception cref="ArgumentNullException">
+        ///   <para>
+        ///     <paramref name="key" /> is <see langword="null" />.
+        ///   </para>
+        ///   <para> -or- </para>
+        ///   <para>
+        ///     <paramref name="label" /> is <see langword="null" />.
+        ///   </para>
+        ///   <para> -or- </para>
+        ///   <para>
+        ///     <paramref name="context" /> is <see langword="null" />.
+        ///   </para>
+        ///   <para> -or- </para>
+        ///   <para>
+        ///     <paramref name="hashAlgorithm" /> has a <see cref="HashAlgorithmName.Name" /> which is <see langword="null" />.
+        ///   </para>
+        /// </exception>
+        /// <exception cref="ArgumentException">
+        ///   <paramref name="hashAlgorithm" /> has a <see cref="HashAlgorithmName.Name" /> which is empty.
+        /// </exception>
+        /// <exception cref="ArgumentOutOfRangeException">
+        ///     <paramref name="derivedKeyLengthInBytes" /> is negative or larger than the maximum number of bytes
+        ///     that can be derived.
+        /// </exception>
+        /// <exception cref="CryptographicException">
+        ///   <paramref name="hashAlgorithm"/> is not a known or supported hash algorithm.
+        /// </exception>
+        /// <exception cref="PlatformNotSupportedException">
+        ///   The current platform does not have a supported implementation of HMAC.
+        /// </exception>
+        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);
+        }
+
+        /// <summary>
+        ///   Derives a key of a specified length.
+        /// </summary>
+        /// <param name="key">The key-derivation key.</param>
+        /// <param name="hashAlgorithm">The HMAC algorithm.</param>
+        /// <param name="label">The label that identifies the purpose for the derived key.</param>
+        /// <param name="context">The context containing information related to the derived key.</param>
+        /// <param name="derivedKeyLengthInBytes">The length of the derived key, in bytes.</param>
+        /// <returns>An array containing the derived key.</returns>
+        /// <exception cref="ArgumentNullException">
+        ///   <para>
+        ///     <paramref name="key" /> is <see langword="null" />.
+        ///   </para>
+        ///   <para> -or- </para>
+        ///   <para>
+        ///     <paramref name="label" /> is <see langword="null" />.
+        ///   </para>
+        ///   <para> -or- </para>
+        ///   <para>
+        ///     <paramref name="context" /> is <see langword="null" />.
+        ///   </para>
+        ///   <para> -or- </para>
+        ///   <para>
+        ///     <paramref name="hashAlgorithm" /> has a <see cref="HashAlgorithmName.Name" /> which is <see langword="null" />.
+        ///   </para>
+        /// </exception>
+        /// <exception cref="ArgumentException">
+        ///   <paramref name="hashAlgorithm" /> has a <see cref="HashAlgorithmName.Name" /> which is empty.
+        /// </exception>
+        /// <exception cref="ArgumentOutOfRangeException">
+        ///     <paramref name="derivedKeyLengthInBytes" /> is negative or larger than the maximum number of bytes
+        ///     that can be derived.
+        /// </exception>
+        /// <exception cref="CryptographicException">
+        ///   <paramref name="hashAlgorithm"/> is not a known or supported hash algorithm.
+        /// </exception>
+        /// <exception cref="EncoderFallbackException">
+        ///   <paramref name="label" /> or <paramref name="context" /> contains text that cannot be converted to UTF8.
+        /// </exception>
+        /// <remarks>
+        ///   <paramref name="label" /> and <paramref name="context" /> 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.
+        /// </remarks>
+        /// <exception cref="PlatformNotSupportedException">
+        ///   The current platform does not have a supported implementation of HMAC.
+        /// </exception>
+        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;
+        }
+
+        /// <summary>
+        ///   Derives a key of a specified length.
+        /// </summary>
+        /// <param name="key">The key-derivation key.</param>
+        /// <param name="hashAlgorithm">The HMAC algorithm.</param>
+        /// <param name="label">The label that identifies the purpose for the derived key.</param>
+        /// <param name="context">The context containing information related to the derived key.</param>
+        /// <param name="derivedKeyLengthInBytes">The length of the derived key, in bytes.</param>
+        /// <returns>An array containing the derived key.</returns>
+        /// <exception cref="ArgumentNullException">
+        ///   <paramref name="hashAlgorithm" /> has a <see cref="HashAlgorithmName.Name" /> which is <see langword="null" />.
+        /// </exception>
+        /// <exception cref="ArgumentException">
+        ///   <paramref name="hashAlgorithm" /> has a <see cref="HashAlgorithmName.Name" /> which is empty.
+        /// </exception>
+        /// <exception cref="ArgumentOutOfRangeException">
+        ///     <paramref name="derivedKeyLengthInBytes" /> is negative or larger than the maximum number of bytes
+        ///     that can be derived.
+        /// </exception>
+        /// <exception cref="CryptographicException">
+        ///   <paramref name="hashAlgorithm"/> is not a known or supported hash algorithm.
+        /// </exception>
+        /// <exception cref="PlatformNotSupportedException">
+        ///   The current platform does not have a supported implementation of HMAC.
+        /// </exception>
+        public static byte[] DeriveBytes(ReadOnlySpan<byte> key, HashAlgorithmName hashAlgorithm, ReadOnlySpan<byte> label, ReadOnlySpan<byte> context, int derivedKeyLengthInBytes)
+        {
+            CheckPrfOutputLength(derivedKeyLengthInBytes, nameof(derivedKeyLengthInBytes));
+
+            byte[] result = new byte[derivedKeyLengthInBytes];
+            DeriveBytes(key, hashAlgorithm, label, context, result);
+            return result;
+        }
+
+        /// <summary>
+        ///   Fills a buffer with a derived key.
+        /// </summary>
+        /// <param name="key">The key-derivation key.</param>
+        /// <param name="hashAlgorithm">The HMAC algorithm.</param>
+        /// <param name="label">The label that identifies the purpose for the derived key.</param>
+        /// <param name="context">The context containing information related to the derived key.</param>
+        /// <param name="destination">The buffer which will receive the derived key.</param>
+        /// <exception cref="ArgumentNullException">
+        ///   <paramref name="hashAlgorithm" /> has a <see cref="HashAlgorithmName.Name" /> which is <see langword="null" />.
+        /// </exception>
+        /// <exception cref="ArgumentException">
+        ///   <paramref name="hashAlgorithm" /> has a <see cref="HashAlgorithmName.Name" /> which is empty.
+        /// </exception>
+        /// <exception cref="ArgumentOutOfRangeException">
+        ///     <paramref name="destination" /> is larger than the maximum number of bytes that can be derived.
+        /// </exception>
+        /// <exception cref="CryptographicException">
+        ///   <paramref name="hashAlgorithm"/> is not a known or supported hash algorithm.
+        /// </exception>
+        /// <exception cref="PlatformNotSupportedException">
+        ///   The current platform does not have a supported implementation of HMAC.
+        /// </exception>
+        public static void DeriveBytes(ReadOnlySpan<byte> key, HashAlgorithmName hashAlgorithm, ReadOnlySpan<byte> label, ReadOnlySpan<byte> context, Span<byte> destination)
+        {
+            CheckHashAlgorithm(hashAlgorithm);
+            CheckPrfOutputLength(destination.Length, nameof(destination));
+            DeriveBytesCore(key, hashAlgorithm, label, context, destination);
+        }
+
+        /// <summary>
+        ///   Derives a key of a specified length.
+        /// </summary>
+        /// <param name="key">The key-derivation key.</param>
+        /// <param name="hashAlgorithm">The HMAC algorithm.</param>
+        /// <param name="label">The label that identifies the purpose for the derived key.</param>
+        /// <param name="context">The context containing information related to the derived key.</param>
+        /// <param name="derivedKeyLengthInBytes">The length of the derived key, in bytes.</param>
+        /// <returns>An array containing the derived key.</returns>
+        /// <exception cref="ArgumentNullException">
+        ///   <paramref name="hashAlgorithm" /> has a <see cref="HashAlgorithmName.Name" /> which is <see langword="null" />.
+        /// </exception>
+        /// <exception cref="ArgumentException">
+        ///   <paramref name="hashAlgorithm" /> has a <see cref="HashAlgorithmName.Name" /> which is empty.
+        /// </exception>
+        /// <exception cref="ArgumentOutOfRangeException">
+        ///   <paramref name="derivedKeyLengthInBytes" /> is negative or larger than the maximum number of bytes
+        ///   that can be derived.
+        /// </exception>
+        /// <exception cref="CryptographicException">
+        ///   <paramref name="hashAlgorithm"/> is not a known or supported hash algorithm.
+        /// </exception>
+        /// <exception cref="EncoderFallbackException">
+        ///   <paramref name="label" /> or <paramref name="context" /> contains text that cannot be converted to UTF8.
+        /// </exception>
+        /// <exception cref="PlatformNotSupportedException">
+        ///   The current platform does not have a supported implementation of HMAC.
+        /// </exception>
+        /// <remarks>
+        ///   <paramref name="label" /> and <paramref name="context" /> 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.
+        /// </remarks>
+        public static byte[] DeriveBytes(ReadOnlySpan<byte> key, HashAlgorithmName hashAlgorithm, ReadOnlySpan<char> label, ReadOnlySpan<char> context, int derivedKeyLengthInBytes)
+        {
+            CheckPrfOutputLength(derivedKeyLengthInBytes, nameof(derivedKeyLengthInBytes));
+
+            byte[] result = new byte[derivedKeyLengthInBytes];
+            DeriveBytes(key, hashAlgorithm, label, context, result);
+            return result;
+        }
+
+        /// <summary>
+        ///   Fills a buffer with a derived key.
+        /// </summary>
+        /// <param name="key">The key-derivation key.</param>
+        /// <param name="hashAlgorithm">The HMAC algorithm.</param>
+        /// <param name="label">The label that identifies the purpose for the derived key.</param>
+        /// <param name="context">The context containing information related to the derived key.</param>
+        /// <param name="destination">The buffer which will receive the derived key.</param>
+        /// <exception cref="ArgumentNullException">
+        ///   <paramref name="hashAlgorithm" /> has a <see cref="HashAlgorithmName.Name" /> which is <see langword="null" />.
+        /// </exception>
+        /// <exception cref="ArgumentException">
+        ///   <paramref name="hashAlgorithm" /> has a <see cref="HashAlgorithmName.Name" /> which is empty.
+        /// </exception>
+        /// <exception cref="ArgumentOutOfRangeException">
+        ///     <paramref name="destination" /> is larger than the maximum number of bytes that can be derived.
+        /// </exception>
+        /// <exception cref="CryptographicException">
+        ///   <paramref name="hashAlgorithm"/> is not a known or supported hash algorithm.
+        /// </exception>
+        /// <exception cref="EncoderFallbackException">
+        ///   <paramref name="label" /> or <paramref name="context" /> contains text that cannot be converted to UTF8.
+        /// </exception>
+        /// <exception cref="PlatformNotSupportedException">
+        ///   The current platform does not have a supported implementation of HMAC.
+        /// </exception>
+        /// <remarks>
+        ///   <paramref name="label" /> and <paramref name="context" /> 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.
+        /// </remarks>
+        public static void DeriveBytes(ReadOnlySpan<byte> key, HashAlgorithmName hashAlgorithm, ReadOnlySpan<char> label, ReadOnlySpan<char> context, Span<byte> destination)
+        {
+            CheckHashAlgorithm(hashAlgorithm);
+            CheckPrfOutputLength(destination.Length, nameof(destination));
+            DeriveBytesCore(key, hashAlgorithm, label, context, destination);
+        }
+
+        /// <summary>
+        ///   Derives a key of a specified length.
+        /// </summary>
+        /// <param name="label">The label that identifies the purpose for the derived key.</param>
+        /// <param name="context">The context containing information related to the derived key.</param>
+        /// <param name="derivedKeyLengthInBytes">The length of the derived key, in bytes.</param>
+        /// <returns>An array containing the derived key.</returns>
+        /// <exception cref="ArgumentNullException">
+        ///   <para>
+        ///     <paramref name="label" /> is <see langword="null" />.
+        ///   </para>
+        ///   <para> -or- </para>
+        ///   <para>
+        ///     <paramref name="context" /> is <see langword="null" />.
+        ///   </para>
+        /// </exception>
+        /// <exception cref="ArgumentOutOfRangeException">
+        ///   <paramref name="derivedKeyLengthInBytes" /> is negative or larger than the maximum number of bytes
+        ///   that can be derived.
+        /// </exception>
+        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;
+        }
+
+        /// <summary>
+        ///   Derives a key of a specified length.
+        /// </summary>
+        /// <param name="label">The label that identifies the purpose for the derived key.</param>
+        /// <param name="context">The context containing information related to the derived key.</param>
+        /// <param name="derivedKeyLengthInBytes">The length of the derived key, in bytes.</param>
+        /// <returns>An array containing the derived key.</returns>
+        /// <exception cref="ArgumentOutOfRangeException">
+        ///   <paramref name="derivedKeyLengthInBytes" /> is negative or larger than the maximum number of bytes
+        ///   that can be derived.
+        /// </exception>
+        public byte[] DeriveKey(ReadOnlySpan<byte> label, ReadOnlySpan<byte> context, int derivedKeyLengthInBytes)
+        {
+            CheckPrfOutputLength(derivedKeyLengthInBytes, nameof(derivedKeyLengthInBytes));
+
+            byte[] result = new byte[derivedKeyLengthInBytes];
+            DeriveKey(label, context, result);
+            return result;
+        }
+
+        /// <summary>
+        ///   Fills a buffer with a derived key.
+        /// </summary>
+        /// <param name="label">The label that identifies the purpose for the derived key.</param>
+        /// <param name="context">The context containing information related to the derived key.</param>
+        /// <param name="destination">The buffer which will receive the derived key.</param>
+        /// <exception cref="ArgumentNullException">
+        ///   <para>
+        ///     <paramref name="label" /> is <see langword="null" />.
+        ///   </para>
+        ///   <para> -or- </para>
+        ///   <para>
+        ///     <paramref name="context" /> is <see langword="null" />.
+        ///   </para>
+        /// </exception>
+        /// <exception cref="ArgumentOutOfRangeException">
+        ///   <paramref name="destination" /> is larger than the maximum number of bytes that can be derived.
+        /// </exception>
+        public void DeriveKey(ReadOnlySpan<byte> label, ReadOnlySpan<byte> context, Span<byte> destination)
+        {
+            CheckPrfOutputLength(destination.Length, nameof(destination));
+            DeriveKeyCore(label, context, destination);
+        }
+
+        /// <summary>
+        ///   Derives a key of a specified length.
+        /// </summary>
+        /// <param name="label">The label that identifies the purpose for the derived key.</param>
+        /// <param name="context">The context containing information related to the derived key.</param>
+        /// <param name="derivedKeyLengthInBytes">The length of the derived key, in bytes.</param>
+        /// <returns>An array containing the derived key.</returns>
+        /// <exception cref="ArgumentOutOfRangeException">
+        ///   <paramref name="derivedKeyLengthInBytes" /> is negative or larger than the maximum number of bytes
+        ///   that can be derived.
+        /// </exception>
+        /// <exception cref="EncoderFallbackException">
+        ///   <paramref name="label" /> or <paramref name="context" /> contains text that cannot be converted to UTF8.
+        /// </exception>
+        /// <remarks>
+        ///   <paramref name="label" /> and <paramref name="context" /> 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.
+        /// </remarks>
+        public byte[] DeriveKey(ReadOnlySpan<char> label, ReadOnlySpan<char> context, int derivedKeyLengthInBytes)
+        {
+            CheckPrfOutputLength(derivedKeyLengthInBytes, nameof(derivedKeyLengthInBytes));
+
+            byte[] result = new byte[derivedKeyLengthInBytes];
+            DeriveKeyCore(label, context, result);
+            return result;
+        }
+
+        /// <summary>
+        ///   Fills a buffer with a derived key.
+        /// </summary>
+        /// <param name="label">The label that identifies the purpose for the derived key.</param>
+        /// <param name="context">The context containing information related to the derived key.</param>
+        /// <param name="destination">The buffer which will receive the derived key.</param>
+        /// <exception cref="ArgumentOutOfRangeException">
+        ///   <paramref name="destination" /> is larger than the maximum number of bytes that can be derived.
+        /// </exception>
+        /// <exception cref="EncoderFallbackException">
+        ///   <paramref name="label" /> or <paramref name="context" /> contains text that cannot be converted to UTF8.
+        /// </exception>
+        /// <remarks>
+        ///   <paramref name="label" /> and <paramref name="context" /> 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.
+        /// </remarks>
+        public void DeriveKey(ReadOnlySpan<char> label, ReadOnlySpan<char> context, Span<byte> destination)
+        {
+            CheckPrfOutputLength(destination.Length, nameof(destination));
+            DeriveKeyCore(label, context, destination);
+        }
+
+        /// <summary>
+        ///   Derives a key of a specified length.
+        /// </summary>
+        /// <param name="label">The label that identifies the purpose for the derived key.</param>
+        /// <param name="context">The context containing information related to the derived key.</param>
+        /// <param name="derivedKeyLengthInBytes">The length of the derived key, in bytes.</param>
+        /// <returns>An array containing the derived key.</returns>
+        /// <exception cref="ArgumentNullException">
+        ///   <para>
+        ///     <paramref name="label" /> is <see langword="null" />.
+        ///   </para>
+        ///   <para> -or- </para>
+        ///   <para>
+        ///     <paramref name="context" /> is <see langword="null" />.
+        ///   </para>
+        /// </exception>
+        /// <exception cref="ArgumentOutOfRangeException">
+        ///   <paramref name="derivedKeyLengthInBytes" /> is negative or larger than the maximum number of bytes
+        ///   that can be derived.
+        /// </exception>
+        /// <exception cref="EncoderFallbackException">
+        ///   <paramref name="label" /> or <paramref name="context" /> contains text that cannot be converted to UTF8.
+        /// </exception>
+        /// <remarks>
+        ///   <paramref name="label" /> and <paramref name="context" /> 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.
+        /// </remarks>
+        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);
+        }
+
+        /// <summary>
+        ///   Releases all resources used by the current instance of <see cref="SP800108HmacCounterKdf"/>.
+        /// </summary>
+        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<byte> key,
+            HashAlgorithmName hashAlgorithm,
+            ReadOnlySpan<byte> label,
+            ReadOnlySpan<byte> context,
+            Span<byte> destination);
+
+        private static partial void DeriveBytesCore(
+            ReadOnlySpan<byte> key,
+            HashAlgorithmName hashAlgorithm,
+            ReadOnlySpan<char> label,
+            ReadOnlySpan<char> context,
+            Span<byte> destination);
+
+        private void DeriveKeyCore(ReadOnlySpan<byte> label, ReadOnlySpan<byte> context, Span<byte> destination)
+        {
+            _implementation.DeriveBytes(label, context, destination);
+        }
+
+        private void DeriveKeyCore(ReadOnlySpan<char> label, ReadOnlySpan<char> context, Span<byte> 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 (file)
index 0000000..698c59d
--- /dev/null
@@ -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<byte> label, ReadOnlySpan<byte> context, Span<byte> destination);
+        internal abstract void DeriveBytes(byte[] label, byte[] context, Span<byte> destination);
+        internal abstract void DeriveBytes(ReadOnlySpan<char> label, ReadOnlySpan<char> context, Span<byte> 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 (file)
index 0000000..ca8f72c
--- /dev/null
@@ -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<byte> destination)
+        {
+            DeriveBytes(new ReadOnlySpan<byte>(label), new ReadOnlySpan<byte>(context), destination);
+        }
+
+        internal override unsafe void DeriveBytes(ReadOnlySpan<byte> label, ReadOnlySpan<byte> context, Span<byte> 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<char> label, ReadOnlySpan<char> context, Span<byte> 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<byte> key,
+            HashAlgorithmName hashAlgorithm,
+            ReadOnlySpan<byte> label,
+            ReadOnlySpan<byte> context,
+            Span<byte> destination)
+        {
+            Debug.Assert(destination.Length <= 0x1FFFFFFF);
+
+            using (SP800108HmacCounterKdfImplementationCng kdf = new SP800108HmacCounterKdfImplementationCng(key, hashAlgorithm))
+            {
+                kdf.DeriveBytes(label, context, destination);
+            }
+        }
+
+        internal static void DeriveBytesOneShot(
+            ReadOnlySpan<byte> key,
+            HashAlgorithmName hashAlgorithm,
+            ReadOnlySpan<char> label,
+            ReadOnlySpan<char> context,
+            Span<byte> 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 (file)
index 0000000..c009b69
--- /dev/null
@@ -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<byte> label, ReadOnlySpan<byte> context, Span<byte> destination)
+        {
+            byte[] key = IncrementAndAcquireKey();
+
+            try
+            {
+                DeriveBytesOneShot(key, _hashAlgorithm, label, context, destination);
+            }
+            finally
+            {
+                ReleaseKey();
+            }
+        }
+
+        internal override void DeriveBytes(ReadOnlySpan<char> label, ReadOnlySpan<char> context, Span<byte> destination)
+        {
+            byte[] key = IncrementAndAcquireKey();
+
+            try
+            {
+                DeriveBytesOneShot(key, _hashAlgorithm, label, context, destination);
+            }
+            finally
+            {
+                ReleaseKey();
+            }
+        }
+
+        internal override void DeriveBytes(byte[] label, byte[] context, Span<byte> 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 (file)
index 0000000..c550f0f
--- /dev/null
@@ -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<byte> _buffer;
+
+        internal Utf8DataEncoding(ReadOnlySpan<char> data, Span<byte> 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<byte> 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 (file)
index 0000000..a492b4b
--- /dev/null
@@ -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<ArgumentNullException>("key", () =>
+                SP800108HmacCounterKdf.DeriveBytes(key: (byte[])null, HashAlgorithmName.SHA256, s_labelBytes, s_contextBytes, 42));
+
+            Assert.Throws<ArgumentNullException>("label", () =>
+                SP800108HmacCounterKdf.DeriveBytes(s_kdk, HashAlgorithmName.SHA256, label: (byte[])null, s_contextBytes, 42));
+
+            Assert.Throws<ArgumentNullException>("context", () =>
+                SP800108HmacCounterKdf.DeriveBytes(s_kdk, HashAlgorithmName.SHA256, s_labelBytes, context: (byte[])null, 42));
+
+            Assert.Throws<ArgumentNullException>("hashAlgorithm", () =>
+                SP800108HmacCounterKdf.DeriveBytes(s_kdk, s_nullHash, s_labelBytes, s_contextBytes, 42));
+
+            Assert.Throws<ArgumentException>("hashAlgorithm", () =>
+                SP800108HmacCounterKdf.DeriveBytes(s_kdk, s_emptyHash, s_labelBytes, s_contextBytes, 42));
+
+            CryptographicException ex = Assert.Throws<CryptographicException>(() =>
+                SP800108HmacCounterKdf.DeriveBytes(s_kdk, s_unknownHash, s_labelBytes, s_contextBytes, 42));
+            Assert.Contains(s_unknownHash.Name, ex.Message);
+
+            Assert.Throws<ArgumentOutOfRangeException>("derivedKeyLengthInBytes", () =>
+                SP800108HmacCounterKdf.DeriveBytes(s_kdk, HashAlgorithmName.SHA256, s_labelBytes, s_contextBytes, -1));
+
+            Assert.Throws<ArgumentOutOfRangeException>("derivedKeyLengthInBytes", () =>
+                SP800108HmacCounterKdf.DeriveBytes(s_kdk, HashAlgorithmName.SHA256, s_labelBytes, s_contextBytes, 0x20000000));
+        }
+
+        [Fact]
+        public static void DeriveBytes_Allocating_String_ArgValidation()
+        {
+            Assert.Throws<ArgumentNullException>("key", () =>
+                SP800108HmacCounterKdf.DeriveBytes(key: (byte[])null, HashAlgorithmName.SHA256, Label, Context, 42));
+
+            Assert.Throws<ArgumentNullException>("label", () =>
+                SP800108HmacCounterKdf.DeriveBytes(s_kdk, HashAlgorithmName.SHA256, label: (string)null, Context, 42));
+
+            Assert.Throws<ArgumentNullException>("context", () =>
+                SP800108HmacCounterKdf.DeriveBytes(s_kdk, HashAlgorithmName.SHA256, Label, context: (string)null, 42));
+
+            Assert.Throws<ArgumentNullException>("hashAlgorithm", () =>
+                SP800108HmacCounterKdf.DeriveBytes(s_kdk, s_nullHash, Label, Context, 42));
+
+            Assert.Throws<ArgumentException>("hashAlgorithm", () =>
+                SP800108HmacCounterKdf.DeriveBytes(s_kdk, s_emptyHash, Label, Context, 42));
+
+            CryptographicException ex = Assert.Throws<CryptographicException>(() =>
+                SP800108HmacCounterKdf.DeriveBytes(s_kdk, s_unknownHash, Label, Context, 42));
+            Assert.Contains(s_unknownHash.Name, ex.Message);
+
+            Assert.Throws<ArgumentOutOfRangeException>("derivedKeyLengthInBytes", () =>
+                SP800108HmacCounterKdf.DeriveBytes(s_kdk, HashAlgorithmName.SHA256, Label, Context, -1));
+
+            Assert.Throws<ArgumentOutOfRangeException>("derivedKeyLengthInBytes", () =>
+                SP800108HmacCounterKdf.DeriveBytes(s_kdk, HashAlgorithmName.SHA256, Label, Context, 0x20000000));
+        }
+
+        [Fact]
+        public static void DeriveBytes_Allocating_SpanBytes_ArgValidation()
+        {
+            Assert.Throws<ArgumentNullException>("hashAlgorithm", () =>
+                SP800108HmacCounterKdf.DeriveBytes(s_kdk.AsSpan(), s_nullHash, s_labelBytes.AsSpan(), s_contextBytes.AsSpan(), 42));
+
+            Assert.Throws<ArgumentException>("hashAlgorithm", () =>
+                SP800108HmacCounterKdf.DeriveBytes(s_kdk.AsSpan(), s_emptyHash, s_labelBytes.AsSpan(), s_contextBytes.AsSpan(), 42));
+
+            CryptographicException ex = Assert.Throws<CryptographicException>(() =>
+                SP800108HmacCounterKdf.DeriveBytes(s_kdk.AsSpan(), s_unknownHash, s_labelBytes.AsSpan(), s_contextBytes.AsSpan(), 42));
+            Assert.Contains(s_unknownHash.Name, ex.Message);
+
+            Assert.Throws<ArgumentOutOfRangeException>("derivedKeyLengthInBytes", () =>
+                SP800108HmacCounterKdf.DeriveBytes(s_kdk.AsSpan(), HashAlgorithmName.SHA256, s_labelBytes.AsSpan(), s_contextBytes.AsSpan(), -1));
+
+            Assert.Throws<ArgumentOutOfRangeException>("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<ArgumentNullException>("hashAlgorithm", () =>
+                SP800108HmacCounterKdf.DeriveBytes(s_kdk, s_nullHash, s_labelBytes, s_contextBytes, destination));
+
+            Assert.Throws<ArgumentException>("hashAlgorithm", () =>
+                SP800108HmacCounterKdf.DeriveBytes(s_kdk, s_emptyHash, s_labelBytes, s_contextBytes, destination));
+
+            CryptographicException ex = Assert.Throws<CryptographicException>(() =>
+                SP800108HmacCounterKdf.DeriveBytes(s_kdk, s_unknownHash, s_labelBytes, s_contextBytes, destination));
+            Assert.Contains(s_unknownHash.Name, ex.Message);
+
+            Assert.Throws<ArgumentOutOfRangeException>("destination", () =>
+                SP800108HmacCounterKdf.DeriveBytes(s_kdk, HashAlgorithmName.SHA256, s_labelBytes, s_contextBytes, GetOversizedSpan()));
+        }
+
+        [Fact]
+        public static void DeriveBytes_Allocating_SpanChars_ArgValidation()
+        {
+            Assert.Throws<ArgumentNullException>("hashAlgorithm", () =>
+                SP800108HmacCounterKdf.DeriveBytes(s_kdk, s_nullHash, Label.AsSpan(), Context.AsSpan(), 42));
+
+            Assert.Throws<ArgumentException>("hashAlgorithm", () =>
+                SP800108HmacCounterKdf.DeriveBytes(s_kdk, s_emptyHash, Label.AsSpan(), Context.AsSpan(), 42));
+
+            CryptographicException ex = Assert.Throws<CryptographicException>(() =>
+                SP800108HmacCounterKdf.DeriveBytes(s_kdk, s_unknownHash, Label.AsSpan(), Context.AsSpan(), 42));
+            Assert.Contains(s_unknownHash.Name, ex.Message);
+
+            Assert.Throws<ArgumentOutOfRangeException>("derivedKeyLengthInBytes", () =>
+                SP800108HmacCounterKdf.DeriveBytes(s_kdk, HashAlgorithmName.SHA256, Label.AsSpan(), Context.AsSpan(), -1));
+
+            Assert.Throws<ArgumentOutOfRangeException>("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<ArgumentNullException>("hashAlgorithm", () =>
+                SP800108HmacCounterKdf.DeriveBytes(s_kdk, s_nullHash, Label.AsSpan(), Context.AsSpan(), destination));
+
+            Assert.Throws<ArgumentException>("hashAlgorithm", () =>
+                SP800108HmacCounterKdf.DeriveBytes(s_kdk, s_emptyHash, Label.AsSpan(), Context.AsSpan(), destination));
+
+            CryptographicException ex = Assert.Throws<CryptographicException>(() =>
+                SP800108HmacCounterKdf.DeriveBytes(s_kdk, s_unknownHash, Label.AsSpan(), Context.AsSpan(), destination));
+            Assert.Contains(s_unknownHash.Name, ex.Message);
+
+            Assert.Throws<ArgumentOutOfRangeException>("destination", () =>
+                SP800108HmacCounterKdf.DeriveBytes(s_kdk, HashAlgorithmName.SHA256, Label.AsSpan(), Context.AsSpan(), GetOversizedSpan()));
+        }
+
+        [Fact]
+        public static void Ctor_KeyArray_ArgValidation()
+        {
+            Assert.Throws<ArgumentNullException>("key", () =>
+                new SP800108HmacCounterKdf((byte[])null, HashAlgorithmName.SHA256));
+
+            Assert.Throws<ArgumentNullException>("hashAlgorithm", () =>
+                new SP800108HmacCounterKdf(s_kdk, s_nullHash));
+
+            Assert.Throws<ArgumentException>("hashAlgorithm", () =>
+                new SP800108HmacCounterKdf(s_kdk, s_emptyHash));
+
+            CryptographicException ex = Assert.Throws<CryptographicException>(() =>
+                new SP800108HmacCounterKdf(s_kdk, s_unknownHash));
+            Assert.Contains(s_unknownHash.Name, ex.Message);
+        }
+
+        [Fact]
+        public static void Ctor_KeySpan_ArgValidation()
+        {
+            Assert.Throws<ArgumentNullException>("hashAlgorithm", () =>
+                new SP800108HmacCounterKdf(s_kdk.AsSpan(), s_nullHash));
+
+            Assert.Throws<ArgumentException>("hashAlgorithm", () =>
+                new SP800108HmacCounterKdf(s_kdk.AsSpan(), s_emptyHash));
+
+            CryptographicException ex = Assert.Throws<CryptographicException>(() =>
+                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<ArgumentNullException>("label", () =>
+                kdf.DeriveKey((byte[])null, s_contextBytes, 42));
+
+            Assert.Throws<ArgumentNullException>("context", () =>
+                kdf.DeriveKey(s_labelBytes, (byte[])null, 42));
+
+            Assert.Throws<ArgumentOutOfRangeException>("derivedKeyLengthInBytes", () =>
+                kdf.DeriveKey(s_labelBytes, s_contextBytes, -1));
+
+            Assert.Throws<ArgumentOutOfRangeException>("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<ArgumentOutOfRangeException>("derivedKeyLengthInBytes", () =>
+                kdf.DeriveKey(s_labelBytes.AsSpan(), s_contextBytes.AsSpan(), -1));
+
+            Assert.Throws<ArgumentOutOfRangeException>("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<ArgumentOutOfRangeException>("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<ArgumentOutOfRangeException>("derivedKeyLengthInBytes", () =>
+                kdf.DeriveKey(Label.AsSpan(), Context.AsSpan(), -1));
+
+            Assert.Throws<ArgumentOutOfRangeException>("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<ArgumentOutOfRangeException>("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<ArgumentOutOfRangeException>("derivedKeyLengthInBytes", () =>
+                kdf.DeriveKey(Label, Context, -1));
+
+            Assert.Throws<ArgumentOutOfRangeException>("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<EncoderFallbackException>(() =>
+                kdf.DeriveKey("\uD800", Context, 42));
+
+            Assert.Throws<EncoderFallbackException>(() =>
+                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<EncoderFallbackException>(() =>
+                kdf.DeriveKey("\uD800".AsSpan(), Context.AsSpan(), derivedKey));
+
+            Assert.Throws<EncoderFallbackException>(() =>
+                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<EncoderFallbackException>(() =>
+                kdf.DeriveKey("\uD800".AsSpan(), Context.AsSpan(), 42));
+
+            Assert.Throws<EncoderFallbackException>(() =>
+                kdf.DeriveKey(Label.AsSpan(), "\uD800".AsSpan(), 42));
+        }
+
+        [Fact]
+        public static void DeriveBytes_BufferFill_SpanChars_InvalidUTF8()
+        {
+            byte[] destination = new byte[42];
+
+            Assert.Throws<EncoderFallbackException>(() =>
+                SP800108HmacCounterKdf.DeriveBytes(s_kdk, HashAlgorithmName.SHA256, Label.AsSpan(), "\uD800".AsSpan(), destination));
+
+            Assert.Throws<EncoderFallbackException>(() =>
+                SP800108HmacCounterKdf.DeriveBytes(s_kdk, HashAlgorithmName.SHA256, "\uD800".AsSpan(), Context.AsSpan(), destination));
+        }
+
+        [Fact]
+        public static void DeriveBytes_Allocating_SpanChars_InvalidUTF8()
+        {
+            Assert.Throws<EncoderFallbackException>(() =>
+                SP800108HmacCounterKdf.DeriveBytes(s_kdk, HashAlgorithmName.SHA256, Label.AsSpan(), "\uD800".AsSpan(), 42));
+
+            Assert.Throws<EncoderFallbackException>(() =>
+                SP800108HmacCounterKdf.DeriveBytes(s_kdk, HashAlgorithmName.SHA256, "\uD800".AsSpan(), Context.AsSpan(), 42));
+        }
+
+        [Fact]
+        public static void DeriveBytes_Allocating_String_InvalidUTF8()
+        {
+            Assert.Throws<EncoderFallbackException>(() =>
+                SP800108HmacCounterKdf.DeriveBytes(s_kdk, HashAlgorithmName.SHA256, Label, "\uD800", 42));
+
+            Assert.Throws<EncoderFallbackException>(() =>
+                SP800108HmacCounterKdf.DeriveBytes(s_kdk, HashAlgorithmName.SHA256, "\uD800", Context, 42));
+        }
+
+        private unsafe static Span<byte> 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<byte>((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 (file)
index 0000000..338ffc7
--- /dev/null
@@ -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<ObjectDisposedException>(() => kdf.DeriveKey(s_labelBytes, s_contextBytes, 42));
+            kdf.Dispose();
+        }
+
+        public static IEnumerable<object[]> GetOutputLengthBoundaries()
+        {
+            // All outputs assume a kdk of 1, 2, 3, 4, 5, 6, 7, 8 with a label of "mustard" and context of "ketchup".
+            // Outputs were generated from the "openssl kdf" command.
+
+            // HMACSHA1 output size is 20 bytes
+            yield return new object[]
+            {
+                nameof(HashAlgorithmName.SHA1),
+                new byte[39]
+                {
+                    0x05, 0xed, 0x32, 0x78, 0xfe, 0x72, 0x9e, 0x7a, 0x1b, 0x49, 0x2c, 0x1b, 0x40, 0x3b, 0x31, 0xd8,
+                    0xef, 0x74, 0xfc, 0xe6, 0x1f, 0x75, 0x4b, 0xa5, 0x47, 0x8e, 0xb9, 0x1d, 0x9e, 0x0c, 0x4f, 0x03,
+                    0xf7, 0x92, 0x68, 0x8a, 0x94, 0x0c, 0xea,
+                },
+            };
+
+            yield return new object[]
+            {
+                nameof (HashAlgorithmName.SHA1),
+                new byte[40]
+                {
+                    0xa5, 0x4a, 0x74, 0xe5, 0x01, 0x0c, 0x69, 0x0e, 0xc8, 0x9e, 0x24, 0x95, 0x94, 0x6a, 0x57, 0x06,
+                    0xe8, 0x88, 0x63, 0x3b, 0xae, 0x1d, 0x13, 0xd8, 0x9d, 0x80, 0x4c, 0xcd, 0x60, 0x2b, 0x15, 0x3a,
+                    0xb9, 0xd3, 0x77, 0xc7, 0xc4, 0xb7, 0x2a, 0xe6,
+                }
+            };
+
+            yield return new object[]
+            {
+                nameof(HashAlgorithmName.SHA1),
+                new byte[41]
+                {
+                    0x44, 0xdc, 0x8a, 0x94, 0x18, 0xd1, 0x3a, 0x35, 0x39, 0xb8, 0xfb, 0x33, 0x0d, 0xf8, 0x1d, 0x3d,
+                    0x95, 0xa3, 0x3a, 0xfb, 0x0c, 0x7f, 0x54, 0x20, 0x32, 0x75, 0x3c, 0x9c, 0xf0, 0xe2, 0xce, 0x4c,
+                    0xe0, 0x98, 0xbe, 0x6b, 0x61, 0x19, 0xd0, 0x79, 0xea,
+                }
+            };
+
+            // HMACSHA256 output size is 32 bytes
+            yield return new object[]
+            {
+                nameof(HashAlgorithmName.SHA256),
+                new byte[63]
+                {
+                    0x86, 0x02, 0xc7, 0x0a, 0x80, 0xf3, 0xc9, 0x46, 0xb4, 0x10, 0xa8, 0xde, 0x36, 0x03, 0x5a, 0xb2,
+                    0x29, 0x48, 0x16, 0xc6, 0x97, 0x99, 0xa4, 0x48, 0x85, 0x55, 0x34, 0xbc, 0xa6, 0x29, 0x68, 0x88,
+                    0x8a, 0xd3, 0x42, 0x42, 0x22, 0x55, 0xa1, 0xf9, 0xaf, 0x1b, 0xb4, 0xfb, 0x69, 0xd3, 0x9d, 0x2e,
+                    0x89, 0x4f, 0x0e, 0x19, 0x5a, 0x98, 0xce, 0x5c, 0x7e, 0xfd, 0xba, 0xc7, 0x51, 0x18, 0xdf,
+                },
+            };
+
+            yield return new object[]
+            {
+                nameof (HashAlgorithmName.SHA256),
+                new byte[64]
+                {
+                    0xcc, 0x06, 0x77, 0xaf, 0x45, 0xae, 0x66, 0x3f, 0x58, 0x8f, 0xd4, 0xa5, 0x6f, 0x31, 0xef, 0xd7,
+                    0x29, 0x9e, 0x45, 0xb1, 0xac, 0x5b, 0x27, 0xb4, 0x10, 0xc1, 0xaf, 0x63, 0xe9, 0xbb, 0xb2, 0xe6,
+                    0x76, 0x65, 0xbf, 0x2d, 0xd3, 0x14, 0xfb, 0xdf, 0xab, 0x6b, 0x95, 0xf4, 0x67, 0x53, 0xe0, 0xd7,
+                    0x46, 0xd3, 0x4c, 0x72, 0xf3, 0x08, 0x95, 0x37, 0xbf, 0xa4, 0x67, 0xb0, 0xe0, 0x00, 0x46, 0x79,
+                }
+            };
+
+            yield return new object[]
+            {
+                nameof(HashAlgorithmName.SHA256),
+                new byte[65]
+                {
+                    0x2b, 0x57, 0x00, 0xe5, 0xef, 0xda, 0x41, 0x7b, 0x75, 0x04, 0xe8, 0x37, 0x7a, 0x7f, 0xfd, 0x30,
+                    0xd6, 0x56, 0x07, 0x69, 0x00, 0x75, 0x7b, 0xb9, 0x64, 0x15, 0x51, 0xac, 0x88, 0x55, 0x87, 0x24,
+                    0xcc, 0xb9, 0x8b, 0xb2, 0x55, 0xcc, 0x02, 0xda, 0xf1, 0x4e, 0xc9, 0xa2, 0x40, 0x95, 0xfb, 0xff,
+                    0xa0, 0x57, 0x73, 0x51, 0x66, 0x4d, 0x65, 0xd4, 0xc9, 0xc0, 0xe6, 0xf4, 0x40, 0xf4, 0x30, 0x17,
+                    0x7b,
+                }
+            };
+
+            // HMACSHA384 output size is 48 bytes
+            yield return new object[]
+            {
+                nameof(HashAlgorithmName.SHA384),
+                new byte[95]
+                {
+                    0x82, 0x1d, 0x9b, 0x3c, 0x7f, 0xad, 0xd4, 0x1b, 0x91, 0xdc, 0x6e, 0x4f, 0xf5, 0xd8, 0xf7, 0xc8,
+                    0x33, 0x18, 0xc8, 0xf8, 0x23, 0x3f, 0x5d, 0xf4, 0x95, 0x32, 0x81, 0x72, 0x96, 0xbd, 0xb9, 0xcc,
+                    0xc1, 0x91, 0x0c, 0x5b, 0x5c, 0x86, 0x2c, 0x0d, 0x5b, 0xe4, 0xfb, 0xc6, 0x70, 0xc4, 0x20, 0xd6,
+                    0x9c, 0xfd, 0x67, 0x56, 0x86, 0x16, 0xd6, 0xf8, 0x05, 0x86, 0x5c, 0xa0, 0x64, 0x5f, 0x72, 0xe0,
+                    0xa5, 0x52, 0x5d, 0x72, 0xe8, 0x5e, 0x07, 0xf1, 0xf5, 0xcf, 0xf9, 0x63, 0x85, 0xc2, 0x77, 0x87,
+                    0x89, 0x75, 0x9d, 0xd2, 0xc6, 0x2b, 0xf3, 0x23, 0x73, 0xd9, 0x1d, 0x01, 0x17, 0x9c, 0x01,
+                },
+            };
+
+            yield return new object[]
+            {
+                nameof (HashAlgorithmName.SHA384),
+                new byte[96]
+                {
+                    0xe4, 0x30, 0x8b, 0x7e, 0x5b, 0x64, 0xcd, 0xd7, 0x3d, 0x27, 0xd9, 0x3a, 0x9e, 0xee, 0xcc, 0xc6,
+                    0x79, 0xa7, 0x39, 0xca, 0x91, 0xb8, 0x93, 0xcd, 0xe8, 0xb8, 0xb7, 0x8a, 0x48, 0xad, 0xb4, 0x3d,
+                    0x3a, 0x02, 0xb9, 0xba, 0x81, 0x81, 0x01, 0x5f, 0xef, 0x8a, 0xc1, 0xcd, 0x6b, 0xae, 0x99, 0xb9,
+                    0xfd, 0xaf, 0x28, 0x18, 0xcf, 0x48, 0xa1, 0xfa, 0x57, 0xce, 0x0a, 0x79, 0x1f, 0xbf, 0xc8, 0x7f,
+                    0xd8, 0x34, 0x34, 0x16, 0x27, 0xc1, 0x12, 0x6e, 0x4c, 0x8c, 0x62, 0xc6, 0x11, 0x01, 0xb8, 0xb8,
+                    0xa5, 0x06, 0xc8, 0x4a, 0x2f, 0xf2, 0x91, 0x08, 0x1a, 0x02, 0x5e, 0x72, 0x48, 0xd1, 0x11, 0x6a,
+
+                }
+            };
+
+            yield return new object[]
+            {
+                nameof(HashAlgorithmName.SHA384),
+                new byte[97]
+                {
+                    0xa1, 0x01, 0x9f, 0x83, 0x55, 0x8f, 0x4a, 0x67, 0x2f, 0xbf, 0xf7, 0x7b, 0xbb, 0xfd, 0x22, 0x35,
+                    0x35, 0x97, 0x01, 0x53, 0x5d, 0x9f, 0x7e, 0xa4, 0xbf, 0xb9, 0xfd, 0xdf, 0x7d, 0x86, 0x8f, 0x86,
+                    0x98, 0x9d, 0x87, 0x21, 0x41, 0x75, 0x4e, 0x2c, 0xaf, 0x10, 0x25, 0x40, 0xe8, 0x26, 0xb8, 0x4d,
+                    0x37, 0xe2, 0x43, 0x5f, 0x2b, 0x30, 0x03, 0xde, 0xb3, 0x1e, 0x31, 0x8c, 0x59, 0x2d, 0x26, 0xd5,
+                    0x08, 0xe4, 0x55, 0x46, 0x0d, 0x76, 0xe7, 0x58, 0xe5, 0xa9, 0xaf, 0xe8, 0xe7, 0x7c, 0xa3, 0x05,
+                    0x61, 0x7b, 0xcb, 0x98, 0x6e, 0xaa, 0xea, 0xd0, 0x7e, 0x52, 0x20, 0xf8, 0xfe, 0x49, 0x02, 0x08,
+                    0xee,
+                }
+            };
+
+            // HMACSHA512 output size is 64 bytes
+            yield return new object[]
+            {
+                nameof(HashAlgorithmName.SHA512),
+                new byte[127]
+                {
+                    0x3d, 0x72, 0x04, 0x07, 0x9c, 0x28, 0x34, 0x33, 0x7a, 0x2f, 0x8e, 0x22, 0x00, 0x1f, 0x1f, 0x22,
+                    0xd9, 0x24, 0x38, 0xd1, 0x56, 0xa4, 0x03, 0x46, 0x89, 0x09, 0xde, 0x13, 0xb3, 0x9c, 0xa9, 0x24,
+                    0x0d, 0x3e, 0x4c, 0x97, 0x7f, 0xd5, 0x9f, 0x86, 0x45, 0x36, 0xcd, 0xc8, 0x0f, 0x44, 0x9a, 0xad,
+                    0xe3, 0xba, 0xfc, 0x93, 0x08, 0x21, 0xa8, 0xfd, 0xde, 0xef, 0x4f, 0xe3, 0xaa, 0xb3, 0xcf, 0xc1,
+                    0x81, 0x1b, 0x44, 0xf9, 0xae, 0xf6, 0x73, 0xc7, 0xf0, 0x71, 0xd6, 0x14, 0x8e, 0x18, 0x5d, 0x43,
+                    0xa1, 0xfb, 0x09, 0x11, 0x24, 0x84, 0x56, 0x9c, 0x97, 0x6e, 0x2e, 0x5a, 0xcd, 0xd3, 0xa5, 0xfb,
+                    0x81, 0x33, 0x6a, 0x3d, 0x95, 0xa5, 0xd9, 0xcd, 0x04, 0x36, 0x76, 0xc2, 0x4c, 0xed, 0x65, 0x81,
+                    0x6f, 0x8c, 0xec, 0xfd, 0xde, 0xdd, 0x3c, 0xd9, 0x1a, 0xe1, 0xf1, 0x02, 0x7e, 0xb8, 0x3a,
+                },
+            };
+
+            yield return new object[]
+            {
+                nameof (HashAlgorithmName.SHA512),
+                new byte[128]
+                {
+                    0x0e, 0x6d, 0x21, 0x43, 0xca, 0xa7, 0x88, 0x13, 0x70, 0x0e, 0xc5, 0x7b, 0x5e, 0x5a, 0x41, 0x21,
+                    0x03, 0x07, 0x30, 0x35, 0x10, 0xe9, 0x42, 0x12, 0x80, 0x64, 0x10, 0x71, 0x5d, 0x41, 0xb9, 0xf5,
+                    0x3a, 0xb2, 0xcd, 0xf8, 0x71, 0x52, 0x01, 0xf8, 0xc5, 0x27, 0x65, 0xc0, 0x6b, 0x31, 0x35, 0xfc,
+                    0x0d, 0x38, 0xbb, 0xf4, 0xc2, 0xeb, 0x9a, 0x85, 0x3f, 0x16, 0xf0, 0x25, 0x40, 0x33, 0x57, 0xc1,
+                    0x08, 0x25, 0xcf, 0x31, 0x10, 0xbf, 0x78, 0x3a, 0x37, 0x64, 0x13, 0xa7, 0x2f, 0xd8, 0x32, 0x2b,
+                    0x93, 0x1f, 0x0b, 0x6d, 0x6c, 0x6c, 0x45, 0x9f, 0x6a, 0xdb, 0x97, 0x8b, 0x33, 0x7d, 0x31, 0xa8,
+                    0xd9, 0x92, 0xe4, 0x50, 0x38, 0x25, 0x22, 0x09, 0x98, 0x11, 0xce, 0x55, 0xf8, 0x6d, 0x27, 0x36,
+                    0x5f, 0xab, 0xca, 0x4b, 0x54, 0x78, 0x11, 0xf9, 0xaf, 0x57, 0xfa, 0x02, 0x31, 0x27, 0x52, 0x69,
+                }
+            };
+
+            yield return new object[]
+            {
+                nameof(HashAlgorithmName.SHA512),
+                new byte[129]
+                {
+                    0x9e, 0xd0, 0xad, 0x78, 0x5d, 0xb3, 0x50, 0xce, 0x0a, 0x1b, 0xa2, 0xd1, 0x1d, 0xb8, 0x43, 0xc0,
+                    0xc1, 0x0f, 0x2f, 0x13, 0xbe, 0x6b, 0x56, 0x53, 0x4e, 0x9f, 0x18, 0x76, 0xcf, 0xf2, 0xf0, 0xa1,
+                    0xa6, 0xe8, 0x57, 0x2b, 0x05, 0xf2, 0x2e, 0x3e, 0xbf, 0xfe, 0x5c, 0x20, 0x93, 0x5e, 0x73, 0xca,
+                    0x23, 0xda, 0x63, 0x24, 0xdf, 0x6c, 0xb7, 0x5c, 0xe7, 0xe9, 0x6e, 0x2a, 0x0c, 0x3a, 0xa9, 0xb7,
+                    0x65, 0x25, 0x8a, 0x8c, 0xf0, 0xae, 0x0e, 0x53, 0x84, 0x82, 0x8b, 0xa6, 0xd9, 0xed, 0x3d, 0xfe,
+                    0x6f, 0x1d, 0xbf, 0x36, 0x7c, 0xfd, 0xa2, 0xf9, 0x22, 0x79, 0x79, 0x54, 0x5f, 0xed, 0x3b, 0xb9,
+                    0xab, 0x84, 0x75, 0xba, 0xb0, 0x12, 0x22, 0x6a, 0x07, 0xee, 0x35, 0xc4, 0x9a, 0xca, 0xd8, 0x28,
+                    0x3c, 0x0b, 0xcd, 0x85, 0xbb, 0x6e, 0x7b, 0x0e, 0xaa, 0xcb, 0xf9, 0xb1, 0xa4, 0xcd, 0x65, 0x87,
+                    0x61,
+                }
+            };
+        }
+
+        public static IEnumerable<object[]> GetRfc8009TestVectors()
+        {
+            // See RFC 8009 Appendix A "Sample pseudorandom function (PRF) invocations"
+            // section. These vectors are also used by OpenSSL 3 for KBKDF.
+            // Section 5 of RFC 8009 defines KDF-HMAC-SHA2 as always using "prf" as the label.
+            // The appendix defines the context as "test".
+            // (kdk, expected, hmac algorithm)
+            yield return new object[]
+            {
+                new byte[]
+                {
+                    0x37, 0x05, 0xD9, 0x60, 0x80, 0xC1, 0x77, 0x28,
+                    0xA0, 0xE8, 0x00, 0xEA, 0xB6, 0xE0, 0xD2, 0x3C,
+                },
+                new byte[]
+                {
+                    0x9D, 0x18, 0x86, 0x16, 0xF6, 0x38, 0x52, 0xFE,
+                    0x86, 0x91, 0x5B, 0xB8, 0x40, 0xB4, 0xA8, 0x86,
+                    0xFF, 0x3E, 0x6B, 0xB0, 0xF8, 0x19, 0xB4, 0x9B,
+                    0x89, 0x33, 0x93, 0xD3, 0x93, 0x85, 0x42, 0x95,
+                },
+                HashAlgorithmName.SHA256,
+            };
+
+            yield return new object[]
+            {
+                new byte[]
+                {
+                    0x6D, 0x40, 0x4D, 0x37, 0xFA, 0xF7, 0x9F, 0x9D,
+                    0xF0, 0xD3, 0x35, 0x68, 0xD3, 0x20, 0x66, 0x98,
+                    0x00, 0xEB, 0x48, 0x36, 0x47, 0x2E, 0xA8, 0xA0,
+                    0x26, 0xD1, 0x6B, 0x71, 0x82, 0x46, 0x0C, 0x52,
+                },
+                new byte[]
+                {
+                    0x98, 0x01, 0xF6, 0x9A, 0x36, 0x8C, 0x2B, 0xF6,
+                    0x75, 0xE5, 0x95, 0x21, 0xE1, 0x77, 0xD9, 0xA0,
+                    0x7F, 0x67, 0xEF, 0xE1, 0xCF, 0xDE, 0x8D, 0x3C,
+                    0x8D, 0x6F, 0x6A, 0x02, 0x56, 0xE3, 0xB1, 0x7D,
+                    0xB3, 0xC1, 0xB6, 0x2A, 0xD1, 0xB8, 0x55, 0x33,
+                    0x60, 0xD1, 0x73, 0x67, 0xEB, 0x15, 0x14, 0xD2,
+                },
+                HashAlgorithmName.SHA384,
+            };
+        }
+    }
+}
diff --git a/src/libraries/Common/tests/System/Security/Cryptography/SP800108HmacCounterKdfTests.Helpers.cs b/src/libraries/Common/tests/System/Security/Cryptography/SP800108HmacCounterKdfTests.Helpers.cs
new file mode 100644 (file)
index 0000000..527d068
--- /dev/null
@@ -0,0 +1,169 @@
+// 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 System.Threading;
+using Xunit;
+
+namespace System.Security.Cryptography.Tests
+{
+    public static partial class SP800108HmacCounterKdfTests
+    {
+        private const string Label = "label";
+        private const string Context = "contextHeadercontext";
+        private static readonly byte[] s_labelBytes = "label"u8.ToArray();
+        private static readonly byte[] s_kdk = "kdk"u8.ToArray();
+        private static readonly byte[] s_contextBytes = "contextHeadercontext"u8.ToArray();
+        private static readonly HashAlgorithmName s_unknownHash = HashAlgorithmName.MD5;
+        private static readonly HashAlgorithmName s_nullHash = new HashAlgorithmName(null);
+        private static readonly HashAlgorithmName s_emptyHash = new HashAlgorithmName("");
+
+        private static void VerifyKbkdfBytes(byte[] expected, byte[] key, HashAlgorithmName hashAlgorithm, byte[] labelBytes, byte[] contextBytes)
+        {
+            byte[] result;
+
+            using (SP800108HmacCounterKdf kdf = new SP800108HmacCounterKdf(key, hashAlgorithm))
+            {
+                result = kdf.DeriveKey(labelBytes, contextBytes, expected.Length);
+                Assert.Equal(expected, result);
+            }
+
+            using (SP800108HmacCounterKdf kdf = new SP800108HmacCounterKdf(key, hashAlgorithm))
+            {
+                result = kdf.DeriveKey(new ReadOnlySpan<byte>(labelBytes), new ReadOnlySpan<byte>(contextBytes), expected.Length);
+                Assert.Equal(expected, result);
+            }
+
+            using (SP800108HmacCounterKdf kdf = new SP800108HmacCounterKdf(key, hashAlgorithm))
+            {
+                result = new byte[expected.Length];
+                kdf.DeriveKey(new ReadOnlySpan<byte>(labelBytes), new ReadOnlySpan<byte>(contextBytes), result);
+                Assert.Equal(expected, result);
+            }
+
+            result = SP800108HmacCounterKdf.DeriveBytes(
+                key,
+                hashAlgorithm,
+                labelBytes,
+                contextBytes,
+                expected.Length);
+            Assert.Equal(expected, result);
+
+            result = SP800108HmacCounterKdf.DeriveBytes(
+                new ReadOnlySpan<byte>(key),
+                hashAlgorithm,
+                new ReadOnlySpan<byte>(labelBytes),
+                new ReadOnlySpan<byte>(contextBytes),
+                expected.Length);
+            Assert.Equal(expected, result);
+
+            result = new byte[expected.Length];
+            SP800108HmacCounterKdf.DeriveBytes(
+                new ReadOnlySpan<byte>(key),
+                hashAlgorithm,
+                new ReadOnlySpan<byte>(labelBytes),
+                new ReadOnlySpan<byte>(contextBytes),
+                result);
+            Assert.Equal(expected, result);
+        }
+
+        private static void VerifyKbkdf(byte[] expected, byte[] key, HashAlgorithmName hashAlgorithm, char[] label, char[] context)
+        {
+            // The actual implementation uses a stricter UTF8 encoding/decoding but we know our test data does not contain
+            // invalid UTF8.
+            byte[] labelBytes = System.Text.Encoding.UTF8.GetBytes(label);
+            byte[] contextBytes = System.Text.Encoding.UTF8.GetBytes(context);
+            byte[] result;
+
+            VerifyKbkdfBytes(expected, key, hashAlgorithm, labelBytes, contextBytes);
+
+            using (SP800108HmacCounterKdf kdf = new SP800108HmacCounterKdf(key, hashAlgorithm))
+            {
+                result = kdf.DeriveKey(new ReadOnlySpan<char>(label), new ReadOnlySpan<char>(context), expected.Length);
+                Assert.Equal(expected, result);
+            }
+
+            using (SP800108HmacCounterKdf kdf = new SP800108HmacCounterKdf(key, hashAlgorithm))
+            {
+                result = new byte[expected.Length];
+                kdf.DeriveKey(new ReadOnlySpan<char>(label), new ReadOnlySpan<char>(context), result);
+                Assert.Equal(expected, result);
+            }
+
+            using (SP800108HmacCounterKdf kdf = new SP800108HmacCounterKdf(key, hashAlgorithm))
+            {
+                result = kdf.DeriveKey(new string(label), new string(context), expected.Length);
+                Assert.Equal(expected, result);
+            }
+
+            result = SP800108HmacCounterKdf.DeriveBytes(
+                key,
+                hashAlgorithm,
+                new string(label),
+                new string(context),
+                expected.Length);
+            Assert.Equal(expected, result);
+
+            result = SP800108HmacCounterKdf.DeriveBytes(
+                new ReadOnlySpan<byte>(key),
+                hashAlgorithm,
+                new ReadOnlySpan<char>(label),
+                new ReadOnlySpan<char>(context),
+                expected.Length);
+            Assert.Equal(expected, result);
+
+            result = new byte[expected.Length];
+            SP800108HmacCounterKdf.DeriveBytes(
+                new ReadOnlySpan<byte>(key),
+                hashAlgorithm,
+                new ReadOnlySpan<char>(label),
+                new ReadOnlySpan<char>(context),
+                result);
+            Assert.Equal(expected, result);
+        }
+
+        private static void RaceCalls(byte[] expected1, byte[] expected2, bool isDisposing, Func<int, byte[]> call1, Func<int, byte[]> call2)
+        {
+            const int Iterations = 1_000;
+
+            void ThreadCallback(object state)
+            {
+                (Func<int, byte[]> act, byte[] expected) = ((Func<int, byte[]>, byte[]))state;
+                byte[][] results = new byte[Iterations][];
+
+                for (int i = 0; i < Iterations; i++)
+                {
+                    // defer asserting until after the loop so that the assert doesn't dominate the work of the
+                    // threads interacting.
+                    try
+                    {
+                        results[i] = act(i);
+                    }
+                    catch (ObjectDisposedException) when (isDisposing)
+                    {
+                        results[i] = null;
+                    }
+                }
+
+                for (int i = 0; i < Iterations; i++)
+                {
+                    byte[] result = results[i];
+
+                    if (result is not null)
+                    {
+                        Assert.Equal(expected, result);
+                    }
+                }
+            }
+
+            Thread t1 = new Thread(ThreadCallback);
+            Thread t2 = new Thread(ThreadCallback);
+
+            t1.Start((call1, expected1));
+            t2.Start((call2, expected2));
+            t1.Join();
+            t2.Join();
+        }
+    }
+}
diff --git a/src/libraries/Common/tests/System/Security/Cryptography/SP800108HmacCounterKdfTests.ThreadSafety.cs b/src/libraries/Common/tests/System/Security/Cryptography/SP800108HmacCounterKdfTests.ThreadSafety.cs
new file mode 100644 (file)
index 0000000..cf1339e
--- /dev/null
@@ -0,0 +1,200 @@
+// 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 Xunit;
+
+namespace System.Security.Cryptography.Tests
+{
+    public static partial class SP800108HmacCounterKdfTests
+    {
+        [Fact]
+        [ActiveIssue("https://github.com/dotnet/runtime/issues/68162", TestPlatforms.Browser)] // wasm threading support
+        public static void Race_ReusingOneInstance_Allocating()
+        {
+            using (SP800108HmacCounterKdf kdf = new("kdf"u8, HashAlgorithmName.SHA256))
+            {
+                RaceCalls(
+                    new byte[] { 0xDC, 0xD6, 0x23, 0xE8, 0x59, 0xB8, 0x4B, 0x95, 0xBF, 0x44, 0x32, 0x6E, 0x2B, 0xA6, 0x34, 0xF0 },
+                    new byte[] { 0x92, 0xB0, 0xD7, 0xDA, 0x2C, 0xB1, 0xAA, 0x8C, 0xD5, 0xDF, 0x97, 0x9E, 0x61, 0xA3, 0x57, 0xD6 },
+                    isDisposing: false,
+                    _ => kdf.DeriveKey("label"u8, "context"u8, derivedKeyLengthInBytes: 16),
+                    _ => kdf.DeriveKey("label"u8, "bananas"u8, derivedKeyLengthInBytes: 16));
+            }
+
+            using (SP800108HmacCounterKdf kdf = new("kdf"u8, HashAlgorithmName.SHA256))
+            {
+                RaceCalls(
+                    new byte[] { 0xDC, 0xD6, 0x23, 0xE8, 0x59, 0xB8, 0x4B, 0x95, 0xBF, 0x44, 0x32, 0x6E, 0x2B, 0xA6, 0x34, 0xF0 },
+                    new byte[] { 0x92, 0xB0, 0xD7, 0xDA, 0x2C, 0xB1, 0xAA, 0x8C, 0xD5, 0xDF, 0x97, 0x9E, 0x61, 0xA3, 0x57, 0xD6 },
+                    isDisposing: false,
+                    _ => kdf.DeriveKey("label"u8.ToArray(), "context"u8.ToArray(), derivedKeyLengthInBytes: 16),
+                    _ => kdf.DeriveKey("label"u8.ToArray(), "bananas"u8.ToArray(), derivedKeyLengthInBytes: 16));
+            }
+
+            using (SP800108HmacCounterKdf kdf = new("kdf"u8, HashAlgorithmName.SHA256))
+            {
+                RaceCalls(
+                    new byte[] { 0xDC, 0xD6, 0x23, 0xE8, 0x59, 0xB8, 0x4B, 0x95, 0xBF, 0x44, 0x32, 0x6E, 0x2B, 0xA6, 0x34, 0xF0 },
+                    new byte[] { 0x92, 0xB0, 0xD7, 0xDA, 0x2C, 0xB1, 0xAA, 0x8C, 0xD5, 0xDF, 0x97, 0x9E, 0x61, 0xA3, 0x57, 0xD6 },
+                    isDisposing: false,
+                    _ => kdf.DeriveKey("label", "context", derivedKeyLengthInBytes: 16),
+                    _ => kdf.DeriveKey("label", "bananas", derivedKeyLengthInBytes: 16));
+            }
+
+            using (SP800108HmacCounterKdf kdf = new("kdf"u8, HashAlgorithmName.SHA256))
+            {
+                RaceCalls(
+                    new byte[] { 0xDC, 0xD6, 0x23, 0xE8, 0x59, 0xB8, 0x4B, 0x95, 0xBF, 0x44, 0x32, 0x6E, 0x2B, 0xA6, 0x34, 0xF0 },
+                    new byte[] { 0x92, 0xB0, 0xD7, 0xDA, 0x2C, 0xB1, 0xAA, 0x8C, 0xD5, 0xDF, 0x97, 0x9E, 0x61, 0xA3, 0x57, 0xD6 },
+                    isDisposing: false,
+                    _ => kdf.DeriveKey("label".AsSpan(), "context".AsSpan(), derivedKeyLengthInBytes: 16),
+                    _ => kdf.DeriveKey("label".AsSpan(), "bananas".AsSpan(), derivedKeyLengthInBytes: 16));
+            }
+        }
+
+        [Fact]
+        [ActiveIssue("https://github.com/dotnet/runtime/issues/68162", TestPlatforms.Browser)] // wasm threading support
+        public static void Race_ReusingOneInstance_Buffering()
+        {
+            using (SP800108HmacCounterKdf kdf = new("kdf"u8, HashAlgorithmName.SHA256))
+            {
+                RaceCalls(
+                    new byte[] { 0xDC, 0xD6, 0x23, 0xE8, 0x59, 0xB8, 0x4B, 0x95, 0xBF, 0x44, 0x32, 0x6E, 0x2B, 0xA6, 0x34, 0xF0 },
+                    new byte[] { 0x92, 0xB0, 0xD7, 0xDA, 0x2C, 0xB1, 0xAA, 0x8C, 0xD5, 0xDF, 0x97, 0x9E, 0x61, 0xA3, 0x57, 0xD6 },
+                    isDisposing: false,
+                    _ => {
+                        byte[] result = new byte[16];
+                        kdf.DeriveKey("label"u8, "context"u8, result);
+                        return result;
+                    },
+                    _ => {
+                        byte[] result = new byte[16];
+                        kdf.DeriveKey("label"u8, "bananas"u8, result);
+                        return result;
+                    });
+            }
+
+            using (SP800108HmacCounterKdf kdf = new("kdf"u8, HashAlgorithmName.SHA256))
+            {
+                RaceCalls(
+                    new byte[] { 0xDC, 0xD6, 0x23, 0xE8, 0x59, 0xB8, 0x4B, 0x95, 0xBF, 0x44, 0x32, 0x6E, 0x2B, 0xA6, 0x34, 0xF0 },
+                    new byte[] { 0x92, 0xB0, 0xD7, 0xDA, 0x2C, 0xB1, 0xAA, 0x8C, 0xD5, 0xDF, 0x97, 0x9E, 0x61, 0xA3, 0x57, 0xD6 },
+                    isDisposing: false,
+                    _ => {
+                        byte[] result = new byte[16];
+                        kdf.DeriveKey("label"u8.ToArray(), "context"u8.ToArray(), result);
+                        return result;
+                    },
+                    _ => {
+                        byte[] result = new byte[16];
+                        kdf.DeriveKey("label"u8.ToArray(), "bananas"u8.ToArray(), result);
+                        return result;
+                    });
+            }
+
+            using (SP800108HmacCounterKdf kdf = new("kdf"u8, HashAlgorithmName.SHA256))
+            {
+                RaceCalls(
+                    new byte[] { 0xDC, 0xD6, 0x23, 0xE8, 0x59, 0xB8, 0x4B, 0x95, 0xBF, 0x44, 0x32, 0x6E, 0x2B, 0xA6, 0x34, 0xF0 },
+                    new byte[] { 0x92, 0xB0, 0xD7, 0xDA, 0x2C, 0xB1, 0xAA, 0x8C, 0xD5, 0xDF, 0x97, 0x9E, 0x61, 0xA3, 0x57, 0xD6 },
+                    isDisposing: false,
+                    _ => {
+                        return kdf.DeriveKey("label", "context", 16);
+                    },
+                    _ => {
+                        return kdf.DeriveKey("label", "bananas", 16);
+                    });
+            }
+
+            using (SP800108HmacCounterKdf kdf = new("kdf"u8, HashAlgorithmName.SHA256))
+            {
+                RaceCalls(
+                    new byte[] { 0xDC, 0xD6, 0x23, 0xE8, 0x59, 0xB8, 0x4B, 0x95, 0xBF, 0x44, 0x32, 0x6E, 0x2B, 0xA6, 0x34, 0xF0 },
+                    new byte[] { 0x92, 0xB0, 0xD7, 0xDA, 0x2C, 0xB1, 0xAA, 0x8C, 0xD5, 0xDF, 0x97, 0x9E, 0x61, 0xA3, 0x57, 0xD6 },
+                    isDisposing: false,
+                    _ => {
+                        byte[] result = new byte[16];
+                        kdf.DeriveKey("label".AsSpan(), "context".AsSpan(), result);
+                        return result;
+                    },
+                    _ => {
+                        byte[] result = new byte[16];
+                        kdf.DeriveKey("label".AsSpan(), "bananas".AsSpan(), result);
+                        return result;
+                    });
+            }
+        }
+
+        [Fact]
+        [ActiveIssue("https://github.com/dotnet/runtime/issues/68162", TestPlatforms.Browser)] // wasm threading support
+        public static void Race_UseAndDisposeOneInstance_Allocating()
+        {
+            SP800108HmacCounterKdf kdf;
+
+            kdf = new SP800108HmacCounterKdf("kdf"u8, HashAlgorithmName.SHA256);
+            RaceCalls(
+                new byte[] { 0xDC, 0xD6, 0x23, 0xE8, 0x59, 0xB8, 0x4B, 0x95, 0xBF, 0x44, 0x32, 0x6E, 0x2B, 0xA6, 0x34, 0xF0 },
+                new byte[] { 0x92, 0xB0, 0xD7, 0xDA, 0x2C, 0xB1, 0xAA, 0x8C, 0xD5, 0xDF, 0x97, 0x9E, 0x61, 0xA3, 0x57, 0xD6 },
+                isDisposing: true,
+                _ => kdf.DeriveKey("label"u8, "context"u8, derivedKeyLengthInBytes: 16),
+                i => {
+                    if (i == 50)
+                    {
+                        kdf.Dispose();
+                        kdf.Dispose();
+                    }
+
+                    return null;
+                });
+
+            kdf = new SP800108HmacCounterKdf("kdf"u8, HashAlgorithmName.SHA256);
+            RaceCalls(
+                new byte[] { 0xDC, 0xD6, 0x23, 0xE8, 0x59, 0xB8, 0x4B, 0x95, 0xBF, 0x44, 0x32, 0x6E, 0x2B, 0xA6, 0x34, 0xF0 },
+                new byte[] { 0x92, 0xB0, 0xD7, 0xDA, 0x2C, 0xB1, 0xAA, 0x8C, 0xD5, 0xDF, 0x97, 0x9E, 0x61, 0xA3, 0x57, 0xD6 },
+                isDisposing: true,
+                _ => kdf.DeriveKey("label"u8.ToArray(), "context"u8.ToArray(), derivedKeyLengthInBytes: 16),
+                i => {
+                    if (i == 50)
+                    {
+                        kdf.Dispose();
+                        kdf.Dispose();
+                    }
+
+                    return null;
+                });
+
+            kdf = new SP800108HmacCounterKdf("kdf"u8, HashAlgorithmName.SHA256);
+            RaceCalls(
+                new byte[] { 0xDC, 0xD6, 0x23, 0xE8, 0x59, 0xB8, 0x4B, 0x95, 0xBF, 0x44, 0x32, 0x6E, 0x2B, 0xA6, 0x34, 0xF0 },
+                new byte[] { 0x92, 0xB0, 0xD7, 0xDA, 0x2C, 0xB1, 0xAA, 0x8C, 0xD5, 0xDF, 0x97, 0x9E, 0x61, 0xA3, 0x57, 0xD6 },
+                isDisposing: true,
+                _ => kdf.DeriveKey("label", "context", derivedKeyLengthInBytes: 16),
+                i => {
+                    if (i == 50)
+                    {
+                        kdf.Dispose();
+                        kdf.Dispose();
+                    }
+
+                    return null;
+                });
+
+            kdf = new SP800108HmacCounterKdf("kdf"u8, HashAlgorithmName.SHA256);
+            RaceCalls(
+                new byte[] { 0xDC, 0xD6, 0x23, 0xE8, 0x59, 0xB8, 0x4B, 0x95, 0xBF, 0x44, 0x32, 0x6E, 0x2B, 0xA6, 0x34, 0xF0 },
+                new byte[] { 0x92, 0xB0, 0xD7, 0xDA, 0x2C, 0xB1, 0xAA, 0x8C, 0xD5, 0xDF, 0x97, 0x9E, 0x61, 0xA3, 0x57, 0xD6 },
+                isDisposing: true,
+                _ => kdf.DeriveKey("label".AsSpan(), "context".AsSpan(), derivedKeyLengthInBytes: 16),
+                i => {
+                    if (i == 50)
+                    {
+                        kdf.Dispose();
+                        kdf.Dispose();
+                    }
+
+                    return null;
+                });
+        }
+    }
+}
diff --git a/src/libraries/Microsoft.Bcl.Cryptography/Microsoft.Bcl.Cryptography.sln b/src/libraries/Microsoft.Bcl.Cryptography/Microsoft.Bcl.Cryptography.sln
new file mode 100644 (file)
index 0000000..da578eb
--- /dev/null
@@ -0,0 +1,67 @@
+Microsoft Visual Studio Solution File, Format Version 12.00
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "ref", "ref", "{7E9F6DE1-771B-4E25-A603-EC43D0291C8B}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Bcl.Cryptography", "ref\Microsoft.Bcl.Cryptography.csproj", "{63655B2E-6A06-4E48-9F01-D0B910063165}"
+EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{98708A22-7268-4EDB-AE37-70AA958A772A}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Bcl.Cryptography", "src\Microsoft.Bcl.Cryptography.csproj", "{B0716D7E-B824-4866-A1ED-DF31BA2970B9}"
+EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{8C3BD4AD-1A56-4204-9826-F8B74251D19F}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TestUtilities", "..\Common\tests\TestUtilities\TestUtilities.csproj", "{1AEF7C7B-5A86-4A5E-9F8B-3933F7624751}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Bcl.Cryptography.Tests", "tests\Microsoft.Bcl.Cryptography.Tests.csproj", "{E66D17AB-BBAF-4F2B-AC9C-8E89BDCC6191}"
+EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "gen", "gen", "{34897637-11A1-48A4-AF1F-E11463A61D0B}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Interop.SourceGeneration", "..\System.Runtime.InteropServices\gen\Microsoft.Interop.SourceGeneration\Microsoft.Interop.SourceGeneration.csproj", "{A4C2BDDC-1AFB-45A8-9E9B-4AD7396A4DF2}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LibraryImportGenerator", "..\System.Runtime.InteropServices\gen\LibraryImportGenerator\LibraryImportGenerator.csproj", "{E9271403-BEF5-46E9-B68B-16EF69AA7149}"
+EndProject
+Global
+       GlobalSection(SolutionConfigurationPlatforms) = preSolution
+               Debug|Any CPU = Debug|Any CPU
+               Release|Any CPU = Release|Any CPU
+       EndGlobalSection
+       GlobalSection(ProjectConfigurationPlatforms) = postSolution
+               {63655B2E-6A06-4E48-9F01-D0B910063165}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+               {63655B2E-6A06-4E48-9F01-D0B910063165}.Debug|Any CPU.Build.0 = Debug|Any CPU
+               {63655B2E-6A06-4E48-9F01-D0B910063165}.Release|Any CPU.ActiveCfg = Release|Any CPU
+               {63655B2E-6A06-4E48-9F01-D0B910063165}.Release|Any CPU.Build.0 = Release|Any CPU
+               {B0716D7E-B824-4866-A1ED-DF31BA2970B9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+               {B0716D7E-B824-4866-A1ED-DF31BA2970B9}.Debug|Any CPU.Build.0 = Debug|Any CPU
+               {B0716D7E-B824-4866-A1ED-DF31BA2970B9}.Release|Any CPU.ActiveCfg = Release|Any CPU
+               {B0716D7E-B824-4866-A1ED-DF31BA2970B9}.Release|Any CPU.Build.0 = Release|Any CPU
+               {1AEF7C7B-5A86-4A5E-9F8B-3933F7624751}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+               {1AEF7C7B-5A86-4A5E-9F8B-3933F7624751}.Debug|Any CPU.Build.0 = Debug|Any CPU
+               {1AEF7C7B-5A86-4A5E-9F8B-3933F7624751}.Release|Any CPU.ActiveCfg = Release|Any CPU
+               {1AEF7C7B-5A86-4A5E-9F8B-3933F7624751}.Release|Any CPU.Build.0 = Release|Any CPU
+               {E66D17AB-BBAF-4F2B-AC9C-8E89BDCC6191}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+               {E66D17AB-BBAF-4F2B-AC9C-8E89BDCC6191}.Debug|Any CPU.Build.0 = Debug|Any CPU
+               {E66D17AB-BBAF-4F2B-AC9C-8E89BDCC6191}.Release|Any CPU.ActiveCfg = Release|Any CPU
+               {E66D17AB-BBAF-4F2B-AC9C-8E89BDCC6191}.Release|Any CPU.Build.0 = Release|Any CPU
+               {A4C2BDDC-1AFB-45A8-9E9B-4AD7396A4DF2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+               {A4C2BDDC-1AFB-45A8-9E9B-4AD7396A4DF2}.Debug|Any CPU.Build.0 = Debug|Any CPU
+               {A4C2BDDC-1AFB-45A8-9E9B-4AD7396A4DF2}.Release|Any CPU.ActiveCfg = Release|Any CPU
+               {A4C2BDDC-1AFB-45A8-9E9B-4AD7396A4DF2}.Release|Any CPU.Build.0 = Release|Any CPU
+               {E9271403-BEF5-46E9-B68B-16EF69AA7149}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+               {E9271403-BEF5-46E9-B68B-16EF69AA7149}.Debug|Any CPU.Build.0 = Debug|Any CPU
+               {E9271403-BEF5-46E9-B68B-16EF69AA7149}.Release|Any CPU.ActiveCfg = Release|Any CPU
+               {E9271403-BEF5-46E9-B68B-16EF69AA7149}.Release|Any CPU.Build.0 = Release|Any CPU
+       EndGlobalSection
+       GlobalSection(SolutionProperties) = preSolution
+               HideSolutionNode = FALSE
+       EndGlobalSection
+       GlobalSection(NestedProjects) = preSolution
+               {63655B2E-6A06-4E48-9F01-D0B910063165} = {7E9F6DE1-771B-4E25-A603-EC43D0291C8B}
+               {B0716D7E-B824-4866-A1ED-DF31BA2970B9} = {98708A22-7268-4EDB-AE37-70AA958A772A}
+               {1AEF7C7B-5A86-4A5E-9F8B-3933F7624751} = {8C3BD4AD-1A56-4204-9826-F8B74251D19F}
+               {E66D17AB-BBAF-4F2B-AC9C-8E89BDCC6191} = {8C3BD4AD-1A56-4204-9826-F8B74251D19F}
+               {A4C2BDDC-1AFB-45A8-9E9B-4AD7396A4DF2} = {34897637-11A1-48A4-AF1F-E11463A61D0B}
+               {E9271403-BEF5-46E9-B68B-16EF69AA7149} = {34897637-11A1-48A4-AF1F-E11463A61D0B}
+       EndGlobalSection
+       GlobalSection(ExtensibilityGlobals) = postSolution
+               SolutionGuid = {EAA35B12-9858-4428-8510-F09B19933FB9}
+       EndGlobalSection
+EndGlobal
diff --git a/src/libraries/Microsoft.Bcl.Cryptography/ref/Microsoft.Bcl.Cryptography.Forwards.cs b/src/libraries/Microsoft.Bcl.Cryptography/ref/Microsoft.Bcl.Cryptography.Forwards.cs
new file mode 100644 (file)
index 0000000..5f6e0fb
--- /dev/null
@@ -0,0 +1,4 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+[assembly: System.Runtime.CompilerServices.TypeForwardedTo(typeof(System.Security.Cryptography.SP800108HmacCounterKdf))]
diff --git a/src/libraries/Microsoft.Bcl.Cryptography/ref/Microsoft.Bcl.Cryptography.cs b/src/libraries/Microsoft.Bcl.Cryptography/ref/Microsoft.Bcl.Cryptography.cs
new file mode 100644 (file)
index 0000000..83dc7a9
--- /dev/null
@@ -0,0 +1,27 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// ------------------------------------------------------------------------------
+// Changes to this file must follow the https://aka.ms/api-review process.
+// ------------------------------------------------------------------------------
+
+namespace System.Security.Cryptography
+{
+    public sealed partial class SP800108HmacCounterKdf : System.IDisposable
+    {
+        public SP800108HmacCounterKdf(byte[] key, System.Security.Cryptography.HashAlgorithmName hashAlgorithm) { }
+        public SP800108HmacCounterKdf(System.ReadOnlySpan<byte> key, System.Security.Cryptography.HashAlgorithmName hashAlgorithm) { }
+        public static byte[] DeriveBytes(byte[] key, System.Security.Cryptography.HashAlgorithmName hashAlgorithm, byte[] label, byte[] context, int derivedKeyLengthInBytes) { throw null; }
+        public static byte[] DeriveBytes(byte[] key, System.Security.Cryptography.HashAlgorithmName hashAlgorithm, string label, string context, int derivedKeyLengthInBytes) { throw null; }
+        public static byte[] DeriveBytes(System.ReadOnlySpan<byte> key, System.Security.Cryptography.HashAlgorithmName hashAlgorithm, System.ReadOnlySpan<byte> label, System.ReadOnlySpan<byte> context, int derivedKeyLengthInBytes) { throw null; }
+        public static void DeriveBytes(System.ReadOnlySpan<byte> key, System.Security.Cryptography.HashAlgorithmName hashAlgorithm, System.ReadOnlySpan<byte> label, System.ReadOnlySpan<byte> context, System.Span<byte> destination) { }
+        public static byte[] DeriveBytes(System.ReadOnlySpan<byte> key, System.Security.Cryptography.HashAlgorithmName hashAlgorithm, System.ReadOnlySpan<char> label, System.ReadOnlySpan<char> context, int derivedKeyLengthInBytes) { throw null; }
+        public static void DeriveBytes(System.ReadOnlySpan<byte> key, System.Security.Cryptography.HashAlgorithmName hashAlgorithm, System.ReadOnlySpan<char> label, System.ReadOnlySpan<char> context, System.Span<byte> destination) { }
+        public byte[] DeriveKey(byte[] label, byte[] context, int derivedKeyLengthInBytes) { throw null; }
+        public byte[] DeriveKey(System.ReadOnlySpan<byte> label, System.ReadOnlySpan<byte> context, int derivedKeyLengthInBytes) { throw null; }
+        public void DeriveKey(System.ReadOnlySpan<byte> label, System.ReadOnlySpan<byte> context, System.Span<byte> destination) { }
+        public byte[] DeriveKey(System.ReadOnlySpan<char> label, System.ReadOnlySpan<char> context, int derivedKeyLengthInBytes) { throw null; }
+        public void DeriveKey(System.ReadOnlySpan<char> label, System.ReadOnlySpan<char> context, System.Span<byte> destination) { }
+        public byte[] DeriveKey(string label, string context, int derivedKeyLengthInBytes) { throw null; }
+        public void Dispose() { }
+    }
+}
diff --git a/src/libraries/Microsoft.Bcl.Cryptography/ref/Microsoft.Bcl.Cryptography.csproj b/src/libraries/Microsoft.Bcl.Cryptography/ref/Microsoft.Bcl.Cryptography.csproj
new file mode 100644 (file)
index 0000000..8e799cf
--- /dev/null
@@ -0,0 +1,17 @@
+<Project Sdk="Microsoft.NET.Sdk">
+  <PropertyGroup>
+    <TargetFrameworks>$(NetCoreAppCurrent);netstandard2.0;$(NetFrameworkMinimum)</TargetFrameworks>
+  </PropertyGroup>
+
+  <ItemGroup Condition="'$(TargetFrameworkIdentifier)' != '.NETCoreApp'">
+    <Compile Include="Microsoft.Bcl.Cryptography.cs" />
+  </ItemGroup>
+  <ItemGroup Condition="'$(TargetFrameworkIdentifier)' == '.NETCoreApp'">
+    <Compile Include="Microsoft.Bcl.Cryptography.Forwards.cs" />
+  </ItemGroup>
+
+  <ItemGroup Condition="'$(TargetFrameworkIdentifier)' != '.NETCoreApp'">
+    <PackageReference Include="System.Memory" Version="$(SystemMemoryVersion)" />
+  </ItemGroup>
+
+</Project>
diff --git a/src/libraries/Microsoft.Bcl.Cryptography/src/Microsoft.Bcl.Cryptography.csproj b/src/libraries/Microsoft.Bcl.Cryptography/src/Microsoft.Bcl.Cryptography.csproj
new file mode 100644 (file)
index 0000000..ba7b0ce
--- /dev/null
@@ -0,0 +1,79 @@
+<Project Sdk="Microsoft.NET.Sdk">
+  <PropertyGroup>
+    <TargetFrameworks>$(NetCoreAppCurrent);netstandard2.0;$(NetFrameworkMinimum)</TargetFrameworks>
+    <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
+    <IsPackable>true</IsPackable>
+    <!-- Disabling baseline validation since this is a brand new package.
+       Once this package has shipped a stable version, the following line
+       should be removed in order to re-enable validation. -->
+    <DisablePackageBaselineValidation>true</DisablePackageBaselineValidation>
+    <PackageDescription>Provides support for some cryptographic primitives for .NET Framework and .NET Standard.
+
+Commonly Used Types:
+System.Security.Cryptography.SP800108HmacCounterKdf</PackageDescription>
+  </PropertyGroup>
+
+  <PropertyGroup>
+    <IsPartialFacadeAssembly Condition="$([MSBuild]::IsTargetFrameworkCompatible('$(TargetFramework)', 'net8.0'))">true</IsPartialFacadeAssembly>
+    <OmitResources Condition="'$(IsPartialFacadeAssembly)' == 'true'">true</OmitResources>
+  </PropertyGroup>
+
+  <ItemGroup Condition="'$(IsPartialFacadeAssembly)' != 'true'">
+    <Compile Include="$(CommonPath)Microsoft\Win32\SafeHandles\SafeBCryptAlgorithmHandle.cs"
+             Link="Microsoft\Win32\SafeHandles\SafeBCryptAlgorithmHandle.cs" />
+    <Compile Include="$(CommonPath)Microsoft\Win32\SafeHandles\SafeBCryptHandle.cs"
+             Link="Common\Microsoft\Win32\SafeHandles\SafeBCryptHandle.cs" />
+    <Compile Include="$(CommonPath)Interop\Windows\BCrypt\Interop.BCryptAlgPseudoHandle.cs"
+             Link="Common\Interop\Windows\BCrypt\Interop.BCryptAlgPseudoHandle.cs" />
+    <Compile Include="$(CommonPath)Interop\Windows\BCrypt\Interop.BCryptCloseAlgorithmProvider.cs"
+             Link="Common\Interop\Windows\BCrypt\Interop.BCryptCloseAlgorithmProvider.cs" />
+    <Compile Include="$(CommonPath)Interop\Windows\Interop.Libraries.cs"
+             Link="Common\Interop\Windows\Interop.Libraries.cs" />
+    <Compile Include="$(CommonPath)Interop\Windows\BCrypt\Interop.CreateCryptographicException.cs"
+             Link="Common\Interop\Windows\BCrypt\Interop.CreateCryptographicException.cs" />
+    <Compile Include="$(CommonPath)Interop\Windows\BCrypt\Interop.NTSTATUS.cs"
+             Link="Common\Interop\Windows\BCrypt\Interop.NTSTATUS.cs" />
+    <Compile Include="$(CommonPath)System\Security\Cryptography\CryptoThrowHelper.Windows.cs"
+             Link="Common\System\Security\Cryptography\CryptoThrowHelper.Windows.cs" />
+    <Compile Include="$(CommonPath)Interop\Windows\Kernel32\Interop.FormatMessage.cs"
+             Link="Common\Internal\Windows\Kernel32\Interop.FormatMessage.cs" />
+    <Compile Include="$(CommonPath)Interop\Windows\BCrypt\Interop.BCryptOpenAlgorithmProvider.cs"
+             Link="Common\Interop\Windows\BCrypt\Interop.BCryptOpenAlgorithmProvider.cs" />
+    <Compile Include="$(CommonPath)Interop\Windows\BCrypt\Interop.BCryptGenerateSymmetricKey.cs"
+             Link="Common\Interop\Windows\BCrypt\Interop.BCryptGenerateSymmetricKey.cs" />
+    <Compile Include="$(CommonPath)Microsoft\Win32\SafeHandles\SafeBCryptKeyHandle.cs"
+             Link="Microsoft\Win32\SafeHandles\SafeBCryptKeyHandle.cs" />
+    <Compile Include="$(CommonPath)Interop\Windows\BCrypt\Interop.BCryptDestroyKey.cs"
+             Link="Common\Interop\Windows\BCrypt\Interop.BCryptDestroyKey.cs" />
+    <Compile Include="$(CommonPath)Interop\Windows\BCrypt\Interop.Blobs.cs"
+             Link="Common\Interop\Windows\BCrypt\Interop.Blobs.cs" />
+    <Compile Include="$(CommonPath)Interop\Windows\BCrypt\Interop.BCryptKeyDerivation.cs"
+             Link="Common\Interop\Windows\BCrypt\Interop.BCryptKeyDerivation.cs" />
+    <Compile Include="$(CommonPath)System\Security\Cryptography\CryptoPool.cs"
+             Link="Common\System\Security\Cryptography\CryptoPool.cs" />
+    <Compile Include="$(CommonPath)System\Security\Cryptography\SP800108HmacCounterKdf.cs"
+             Link="Common\System\Security\Cryptography\SP800108HmacCounterKdf.cs" />
+    <Compile Include="$(CommonPath)System\Security\Cryptography\SP800108HmacCounterKdfImplementationBase.cs"
+             Link="Common\System\Security\Cryptography\SP800108HmacCounterKdfImplementationBase.cs" />
+    <Compile Include="$(CommonPath)System\Security\Cryptography\SP800108HmacCounterKdfImplementationCng.cs"
+             Link="Common\System\Security\Cryptography\SP800108HmacCounterKdfImplementationCng.cs" />
+    <Compile Include="$(CommonPath)System\Security\Cryptography\SP800108HmacCounterKdfImplementationManaged.cs"
+             Link="Common\System\Security\Cryptography\SP800108HmacCounterKdfImplementationManaged.cs" />
+    <Compile Include="$(CommonPath)System\Security\Cryptography\Utf8DataEncoding.cs"
+             Link="Common\System\Security\Cryptography\Utf8DataEncoding.cs" />
+
+    <Compile Include="System\Security\Cryptography\HashAlgorithmNames.cs" />
+    <Compile Include="System\Security\Cryptography\NetStandardShims.cs" />
+    <Compile Include="System\Security\Cryptography\SP800108HmacCounterKdf.cs" />
+    <Compile Include="System\Security\Cryptography\SP800108HmacCounterKdfImplementationCng.cs" />
+    <Compile Include="System\Security\Cryptography\SP800108HmacCounterKdfImplementationManaged.cs" />
+  </ItemGroup>
+
+  <ItemGroup Condition="'$(TargetFrameworkIdentifier)' != '.NETCoreApp'">
+    <PackageReference Include="System.Memory" Version="$(SystemMemoryVersion)" />
+  </ItemGroup>
+
+  <ItemGroup Condition="'$(TargetFrameworkIdentifier)' == '.NETFramework'">
+    <PackageReference Include="System.Security.Cryptography.Algorithms" Version="$(SystemSecurityCryptographyAlgorithmsVersion)" />
+  </ItemGroup>
+</Project>
diff --git a/src/libraries/Microsoft.Bcl.Cryptography/src/Resources/Strings.resx b/src/libraries/Microsoft.Bcl.Cryptography/src/Resources/Strings.resx
new file mode 100644 (file)
index 0000000..85b7fab
--- /dev/null
@@ -0,0 +1,72 @@
+<root>
+  <xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
+    <xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
+    <xsd:element name="root" msdata:IsDataSet="true">
+      <xsd:complexType>
+        <xsd:choice maxOccurs="unbounded">
+          <xsd:element name="metadata">
+            <xsd:complexType>
+              <xsd:sequence>
+                <xsd:element name="value" type="xsd:string" minOccurs="0" />
+              </xsd:sequence>
+              <xsd:attribute name="name" use="required" type="xsd:string" />
+              <xsd:attribute name="type" type="xsd:string" />
+              <xsd:attribute name="mimetype" type="xsd:string" />
+              <xsd:attribute ref="xml:space" />
+            </xsd:complexType>
+          </xsd:element>
+          <xsd:element name="assembly">
+            <xsd:complexType>
+              <xsd:attribute name="alias" type="xsd:string" />
+              <xsd:attribute name="name" type="xsd:string" />
+            </xsd:complexType>
+          </xsd:element>
+          <xsd:element name="data">
+            <xsd:complexType>
+              <xsd:sequence>
+                <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
+                <xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
+              </xsd:sequence>
+              <xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
+              <xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
+              <xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
+              <xsd:attribute ref="xml:space" />
+            </xsd:complexType>
+          </xsd:element>
+          <xsd:element name="resheader">
+            <xsd:complexType>
+              <xsd:sequence>
+                <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
+              </xsd:sequence>
+              <xsd:attribute name="name" type="xsd:string" use="required" />
+            </xsd:complexType>
+          </xsd:element>
+        </xsd:choice>
+      </xsd:complexType>
+    </xsd:element>
+  </xsd:schema>
+  <resheader name="resmimetype">
+    <value>text/microsoft-resx</value>
+  </resheader>
+  <resheader name="version">
+    <value>2.0</value>
+  </resheader>
+  <resheader name="reader">
+    <value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+  </resheader>
+  <resheader name="writer">
+    <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+  </resheader>
+  <data name="ArgumentOutOfRange_NeedNonNegNum" xml:space="preserve">
+    <value>Non-negative number required.</value>
+  </data>
+  <data name="ArgumentOutOfRange_KOut_Too_Large" xml:space="preserve">
+    <value>The number of bytes requested is too large. The number of bytes produced by SP800108HmacCounterKdf cannot exceed 536,870,911 bytes.</value>
+  </data>
+  <data name="Argument_EmptyString" xml:space="preserve">
+    <value>The value cannot be an empty string.</value>
+  </data>
+  <data name="Cryptography_UnknownHashAlgorithm" xml:space="preserve">
+    <value>'{0}' is not a known hash algorithm.</value>
+  </data>
+</root>
diff --git a/src/libraries/Microsoft.Bcl.Cryptography/src/System/Security/Cryptography/HashAlgorithmNames.cs b/src/libraries/Microsoft.Bcl.Cryptography/src/System/Security/Cryptography/HashAlgorithmNames.cs
new file mode 100644 (file)
index 0000000..8f10590
--- /dev/null
@@ -0,0 +1,13 @@
+// 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 static class HashAlgorithmNames
+    {
+        internal const string SHA1 = nameof(SHA1);
+        internal const string SHA256 = nameof(SHA256);
+        internal const string SHA384 = nameof(SHA384);
+        internal const string SHA512 = nameof(SHA512);
+    }
+}
diff --git a/src/libraries/Microsoft.Bcl.Cryptography/src/System/Security/Cryptography/NetStandardShims.cs b/src/libraries/Microsoft.Bcl.Cryptography/src/System/Security/Cryptography/NetStandardShims.cs
new file mode 100644 (file)
index 0000000..63cd4cd
--- /dev/null
@@ -0,0 +1,34 @@
+// 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 System.Runtime.CompilerServices;
+
+namespace System.Security.Cryptography
+{
+    internal static class NetStandardShims
+    {
+        internal static unsafe int GetBytes(this Encoding encoding, ReadOnlySpan<char> str, Span<byte> destination)
+        {
+            if (str.IsEmpty)
+            {
+                return 0;
+            }
+
+            fixed (char* pStr = str)
+            fixed (byte* pDestination = destination)
+            {
+                return encoding.GetBytes(pStr, str.Length, pDestination, destination.Length);
+            }
+        }
+    }
+
+    internal static class CryptographicOperations
+    {
+        [MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.NoOptimization)]
+        internal static void ZeroMemory(Span<byte> buffer)
+        {
+            buffer.Clear();
+        }
+    }
+}
diff --git a/src/libraries/Microsoft.Bcl.Cryptography/src/System/Security/Cryptography/SP800108HmacCounterKdf.cs b/src/libraries/Microsoft.Bcl.Cryptography/src/System/Security/Cryptography/SP800108HmacCounterKdf.cs
new file mode 100644 (file)
index 0000000..c4850f1
--- /dev/null
@@ -0,0 +1,100 @@
+// 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;
+
+namespace System.Security.Cryptography
+{
+    public sealed partial class SP800108HmacCounterKdf : IDisposable
+    {
+        private static readonly bool s_useCngKeyDerivation = IsWindows8OrGreater();
+
+        private static partial SP800108HmacCounterKdfImplementationBase CreateImplementation(
+            ReadOnlySpan<byte> key,
+            HashAlgorithmName hashAlgorithm)
+        {
+            if (s_useCngKeyDerivation)
+            {
+                return new SP800108HmacCounterKdfImplementationCng(key, hashAlgorithm);
+            }
+            else
+            {
+                return new SP800108HmacCounterKdfImplementationManaged(key, hashAlgorithm);
+            }
+        }
+
+        private static partial byte[] DeriveBytesCore(
+            byte[] key,
+            HashAlgorithmName hashAlgorithm,
+            byte[] label,
+            byte[] context,
+            int derivedKeyLengthInBytes)
+        {
+            byte[] result = new byte[derivedKeyLengthInBytes];
+
+            if (s_useCngKeyDerivation)
+            {
+                SP800108HmacCounterKdfImplementationCng.DeriveBytesOneShot(key, hashAlgorithm, label, context, result);
+            }
+            else
+            {
+                SP800108HmacCounterKdfImplementationManaged.DeriveBytesOneShot(key, hashAlgorithm, label, context, result);
+            }
+
+            return result;
+        }
+
+        private static partial void DeriveBytesCore(
+            ReadOnlySpan<byte> key,
+            HashAlgorithmName hashAlgorithm,
+            ReadOnlySpan<byte> label,
+            ReadOnlySpan<byte> context,
+            Span<byte> destination)
+        {
+            if (s_useCngKeyDerivation)
+            {
+                SP800108HmacCounterKdfImplementationCng.DeriveBytesOneShot(key, hashAlgorithm, label, context, destination);
+            }
+            else
+            {
+                SP800108HmacCounterKdfImplementationManaged.DeriveBytesOneShot(key, hashAlgorithm, label, context, destination);
+            }
+        }
+
+        private static partial void DeriveBytesCore(
+            ReadOnlySpan<byte> key,
+            HashAlgorithmName hashAlgorithm,
+            ReadOnlySpan<char> label,
+            ReadOnlySpan<char> context,
+            Span<byte> destination)
+        {
+            if (s_useCngKeyDerivation)
+            {
+                SP800108HmacCounterKdfImplementationCng.DeriveBytesOneShot(key, hashAlgorithm, label, context, destination);
+            }
+            else
+            {
+                SP800108HmacCounterKdfImplementationManaged.DeriveBytesOneShot(key, hashAlgorithm, label, context, destination);
+            }
+        }
+
+        private static bool IsWindows8OrGreater()
+        {
+#if NET
+            return OperatingSystem.IsWindowsVersionAtLeast(6, 2);
+#elif NETSTANDARD
+            bool isWindows = RuntimeInformation.IsOSPlatform(OSPlatform.Windows);
+            Version version = Environment.OSVersion.Version;
+            return isWindows && (version.Major > 6 || (version.Major == 6 && version.Minor >= 2));
+#elif NETFRAMEWORK
+            Version version = Environment.OSVersion.Version;
+            return version.Major > 6 || (version.Major == 6 && version.Minor >= 2);
+#else
+#error Unhandled platform target
+#endif
+        }
+    }
+}
diff --git a/src/libraries/Microsoft.Bcl.Cryptography/src/System/Security/Cryptography/SP800108HmacCounterKdfImplementationCng.cs b/src/libraries/Microsoft.Bcl.Cryptography/src/System/Security/Cryptography/SP800108HmacCounterKdfImplementationCng.cs
new file mode 100644 (file)
index 0000000..8252fc3
--- /dev/null
@@ -0,0 +1,113 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System.Diagnostics;
+
+namespace System.Security.Cryptography
+{
+    internal sealed partial class SP800108HmacCounterKdfImplementationCng
+    {
+        internal unsafe SP800108HmacCounterKdfImplementationCng(ReadOnlySpan<byte> key, HashAlgorithmName hashAlgorithm)
+        {
+            Debug.Assert(hashAlgorithm.Name is not null);
+
+            scoped ReadOnlySpan<byte> symmetricKeyMaterial;
+            scoped Span<byte> clearSpan = default;
+            int symmetricKeyMaterialLength;
+            int hashAlgorithmBlockSize = GetHashBlockSize(hashAlgorithm.Name);
+
+            if (key.Length > hashAlgorithmBlockSize)
+            {
+                byte[] keyArray = new byte[key.Length];
+
+                fixed (byte* pKeyArray = keyArray)
+                {
+                    key.CopyTo(keyArray);
+                    clearSpan = HashOneShot(hashAlgorithm, keyArray);
+                    CryptographicOperations.ZeroMemory(keyArray);
+                }
+
+                symmetricKeyMaterial = clearSpan;
+                symmetricKeyMaterialLength = symmetricKeyMaterial.Length;
+            }
+            else if (!key.IsEmpty)
+            {
+                symmetricKeyMaterial = key;
+                symmetricKeyMaterialLength = key.Length;
+            }
+            else
+            {
+                // CNG requires a non-null pointer even when the length is zero.
+                symmetricKeyMaterial = stackalloc byte[] { 0 };
+                symmetricKeyMaterialLength = 0;
+            }
+
+            try
+            {
+                fixed (byte* pSymmetricKeyMaterial = symmetricKeyMaterial)
+                {
+                    _keyHandle = CreateSymmetricKey(pSymmetricKeyMaterial, symmetricKeyMaterialLength);
+                }
+            }
+            finally
+            {
+                CryptographicOperations.ZeroMemory(clearSpan);
+            }
+
+            _hashAlgorithm = hashAlgorithm;
+        }
+
+        // For .NET Standard / .NET Framework, provide a byte overload so that we don't go from array->span->array
+        // when we need to adjust keys that are too large.
+        internal unsafe SP800108HmacCounterKdfImplementationCng(byte[] key, HashAlgorithmName hashAlgorithm)
+        {
+            Debug.Assert(hashAlgorithm.Name is not null);
+
+            scoped ReadOnlySpan<byte> symmetricKeyMaterial;
+            scoped Span<byte> clearSpan = default;
+            int symmetricKeyMaterialLength;
+            int hashAlgorithmBlockSize = GetHashBlockSize(hashAlgorithm.Name);
+
+            if (key.Length > hashAlgorithmBlockSize)
+            {
+                clearSpan = HashOneShot(hashAlgorithm, key);
+                symmetricKeyMaterial = clearSpan;
+                symmetricKeyMaterialLength = symmetricKeyMaterial.Length;
+            }
+            else if (key.Length > 0)
+            {
+                symmetricKeyMaterial = key;
+                symmetricKeyMaterialLength = key.Length;
+            }
+            else
+            {
+                // CNG requires a non-null pointer even when the length is zero.
+                symmetricKeyMaterial = stackalloc byte[] { 0 };
+                symmetricKeyMaterialLength = 0;
+            }
+
+            try
+            {
+                fixed (byte* pSymmetricKeyMaterial = symmetricKeyMaterial)
+                {
+                    _keyHandle = CreateSymmetricKey(pSymmetricKeyMaterial, symmetricKeyMaterialLength);
+                }
+            }
+            finally
+            {
+                CryptographicOperations.ZeroMemory(clearSpan);
+            }
+
+            _hashAlgorithm = hashAlgorithm;
+        }
+
+        private static byte[] HashOneShot(HashAlgorithmName hashAlgorithm, byte[] data)
+        {
+            using (IncrementalHash hash = IncrementalHash.CreateHash(hashAlgorithm))
+            {
+                hash.AppendData(data);
+                return hash.GetHashAndReset();
+            }
+        }
+    }
+}
diff --git a/src/libraries/Microsoft.Bcl.Cryptography/src/System/Security/Cryptography/SP800108HmacCounterKdfImplementationManaged.cs b/src/libraries/Microsoft.Bcl.Cryptography/src/System/Security/Cryptography/SP800108HmacCounterKdfImplementationManaged.cs
new file mode 100644 (file)
index 0000000..60e438f
--- /dev/null
@@ -0,0 +1,203 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System.Diagnostics;
+using System.Text;
+
+namespace System.Security.Cryptography
+{
+    internal sealed partial class SP800108HmacCounterKdfImplementationManaged
+    {
+        public SP800108HmacCounterKdfImplementationManaged(ReadOnlySpan<byte> key, HashAlgorithmName hashAlgorithm)
+        {
+            _key = key.ToArray();
+            _hashAlgorithm = hashAlgorithm;
+        }
+
+        internal static unsafe void DeriveBytesOneShot(
+            ReadOnlySpan<byte> key,
+            HashAlgorithmName hashAlgorithm,
+            ReadOnlySpan<byte> label,
+            ReadOnlySpan<byte> context,
+            Span<byte> destination)
+        {
+            if (destination.IsEmpty)
+            {
+                return;
+            }
+
+            // IncrementalHash needs an array of the correct size, so we can't rent for the key.
+            byte[] keyBuffer = new byte[key.Length];
+            byte[] labelBuffer = CryptoPool.Rent(label.Length);
+            byte[] contextBuffer = CryptoPool.Rent(context.Length);
+
+            // Fixed to prevent GC moves.
+            fixed (byte* pKeyBuffer = keyBuffer)
+            {
+                try
+                {
+                    key.CopyTo(keyBuffer);
+                    label.CopyTo(labelBuffer);
+                    context.CopyTo(contextBuffer);
+
+                    DeriveBytesOneShot(
+                        keyBuffer,
+                        hashAlgorithm,
+                        labelBuffer,
+                        label.Length,
+                        contextBuffer,
+                        context.Length,
+                        destination);
+                }
+                finally
+                {
+                    CryptographicOperations.ZeroMemory(keyBuffer);
+                    CryptoPool.Return(labelBuffer, clearSize: label.Length);
+                    CryptoPool.Return(contextBuffer, clearSize: context.Length);
+                }
+            }
+        }
+
+        internal static void DeriveBytesOneShot(
+            byte[] key,
+            HashAlgorithmName hashAlgorithm,
+            byte[] label,
+            byte[] context,
+            Span<byte> destination)
+        {
+            DeriveBytesOneShot(key, hashAlgorithm, label, label.Length, context, context.Length, destination);
+        }
+
+         internal static unsafe void DeriveBytesOneShot(
+            ReadOnlySpan<byte> key,
+            HashAlgorithmName hashAlgorithm,
+            ReadOnlySpan<char> label,
+            ReadOnlySpan<char> context,
+            Span<byte> destination)
+        {
+            if (destination.Length == 0)
+            {
+                return;
+            }
+
+            // The netstandard implementation needs arrays for all inputs, so always rent and don't perform
+            // the UTF8 encoding on the stack since that will just end up renting again anyway.
+
+            Encoding utf8ThrowingEncoding = Utf8DataEncoding.ThrowingUtf8Encoding;
+            byte[] labelBuffer = CryptoPool.Rent(utf8ThrowingEncoding.GetMaxByteCount(label.Length));
+            byte[] contextBuffer = CryptoPool.Rent(utf8ThrowingEncoding.GetMaxByteCount(context.Length));
+            int labelWritten = 0;
+            int contextWritten = 0;
+
+            byte[] keyBuffer = new byte[key.Length];
+
+            fixed (byte* pKeyBuffer = keyBuffer)
+            {
+                try
+                {
+                    labelWritten = utf8ThrowingEncoding.GetBytes(label, labelBuffer);
+                    contextWritten = utf8ThrowingEncoding.GetBytes(context, contextBuffer);
+                    key.CopyTo(keyBuffer);
+
+                    DeriveBytesOneShot(
+                        keyBuffer,
+                        hashAlgorithm,
+                        labelBuffer,
+                        labelWritten,
+                        contextBuffer,
+                        contextWritten,
+                        destination);
+                }
+                finally
+                {
+                    CryptographicOperations.ZeroMemory(keyBuffer);
+                    CryptoPool.Return(labelBuffer, labelWritten);
+                    CryptoPool.Return(contextBuffer, contextWritten);
+                }
+            }
+        }
+
+        private static void DeriveBytesOneShot(
+            byte[] key,
+            HashAlgorithmName hashAlgorithm,
+            byte[] label,
+            int labelLength,
+            byte[] context,
+            int contextLength,
+            Span<byte> destination)
+        {
+            if (destination.Length == 0)
+            {
+                return;
+            }
+
+            Debug.Assert(destination.Length <= 0x1FFFFFFF);
+
+            // Do everything as checked. Over/underflows are never expected.
+            checked
+            {
+                // The KDF is defined as K(i) := PRF (KI, [i]2 || Label || 0x00 || Context || [L]2)
+                // We know L is already less than 0x1FFFFFFF. h = ceil(L / h) where H is the hash length in bits.
+                // So we don't expect i to overflow.
+                using (IncrementalHash hash = IncrementalHash.CreateHMAC(hashAlgorithm, key))
+                {
+                    // We use this rented buffer for three things. The first two uints for i and L, and last byte
+                    // for the zero separator. So this is
+                    // uint(L) | uint(i) | byte(0)
+                    // This lets us use a single rent for the L, i, and 0x00.
+                    const int LOffset = 0;
+                    const int LLength = sizeof(uint);
+                    const int IOffset = LLength + LOffset;
+                    const int ILength = sizeof(uint);
+                    const int ZeroOffset = IOffset + ILength;
+                    const int ZeroLength = sizeof(byte);
+                    const int RentSize = ZeroOffset + ZeroLength;
+
+                    byte[]? rentedBuffer = null;
+
+                    try
+                    {
+                        rentedBuffer = CryptoPool.Rent(RentSize);
+
+                        WriteUInt32BigEndian((uint)destination.Length * 8U, rentedBuffer.AsSpan(LOffset, LLength));
+                        rentedBuffer[ZeroOffset] = 0;
+
+                        for (uint i = 1; !destination.IsEmpty; i++)
+                        {
+                            WriteUInt32BigEndian(i, rentedBuffer.AsSpan(IOffset, ILength));
+                            hash.AppendData(rentedBuffer, IOffset, ILength);
+                            hash.AppendData(label, 0, labelLength);
+                            hash.AppendData(rentedBuffer, ZeroOffset, ZeroLength);
+                            hash.AppendData(context, 0, contextLength);
+                            hash.AppendData(rentedBuffer, LOffset, LLength);
+
+                            byte[] hmac = hash.GetHashAndReset();
+                            int needed = Math.Min(destination.Length, hmac.Length);
+                            hmac.AsSpan(0, needed).CopyTo(destination);
+                            destination = destination.Slice(needed);
+
+                            // Best effort to zero out the key material.
+                            CryptographicOperations.ZeroMemory(hmac);
+                        }
+                    }
+                    finally
+                    {
+                        if (rentedBuffer is not null)
+                        {
+                            CryptoPool.Return(rentedBuffer, clearSize: RentSize);
+                        }
+                    }
+                }
+            }
+        }
+
+        private static void WriteUInt32BigEndian(uint value, Span<byte> destination)
+        {
+            Debug.Assert(destination.Length == sizeof(uint));
+            destination[0] = (byte)(value >> 24);
+            destination[1] = (byte)(value >> 16);
+            destination[2] = (byte)(value >> 8);
+            destination[3] = (byte)(value);
+        }
+    }
+}
diff --git a/src/libraries/Microsoft.Bcl.Cryptography/tests/Microsoft.Bcl.Cryptography.Tests.csproj b/src/libraries/Microsoft.Bcl.Cryptography/tests/Microsoft.Bcl.Cryptography.Tests.csproj
new file mode 100644 (file)
index 0000000..02ee9aa
--- /dev/null
@@ -0,0 +1,22 @@
+<Project Sdk="Microsoft.NET.Sdk">
+  <PropertyGroup>
+    <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
+    <TargetFrameworks>$(NetCoreAppCurrent);$(NetFrameworkMinimum)</TargetFrameworks>
+  </PropertyGroup>
+  <ItemGroup>
+    <Compile Include="$(CommonTestPath)System\Security\Cryptography\SP800108HmacCounterKdfTests.ArgValidation.cs"
+             Link="CommonTest\System\Security\Cryptography\SP800108HmacCounterKdfTests.ArgValidation.cs" />
+    <Compile Include="$(CommonTestPath)System\Security\Cryptography\SP800108HmacCounterKdfTests.Functional.cs"
+             Link="CommonTest\System\Security\Cryptography\SP800108HmacCounterKdfTests.Functional.cs" />
+    <Compile Include="$(CommonTestPath)System\Security\Cryptography\SP800108HmacCounterKdfTests.Helpers.cs"
+             Link="CommonTest\System\Security\Cryptography\SP800108HmacCounterKdfTests.Helpers.cs" />
+    <Compile Include="$(CommonTestPath)System\Security\Cryptography\SP800108HmacCounterKdfTests.ThreadSafety.cs"
+             Link="CommonTest\System\Security\Cryptography\SP800108HmacCounterKdfTests.ThreadSafety.cs" />
+  </ItemGroup>
+  <ItemGroup>
+    <ProjectReference Include="..\src\Microsoft.Bcl.Cryptography.csproj" />
+  </ItemGroup>
+  <ItemGroup Condition="'$(TargetFrameworkIdentifier)' == '.NETFramework'">
+    <PackageReference Include="System.ValueTuple" Version="$(SystemValueTupleVersion)" />
+  </ItemGroup>
+</Project>
index 41682a4..914f180 100644 (file)
@@ -2340,6 +2340,24 @@ namespace System.Security.Cryptography
         [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCodeAttribute("CreateFormatter is not trim compatible because the algorithm implementation referenced by FormatterAlgorithm might be removed.")]
         public virtual System.Security.Cryptography.AsymmetricSignatureFormatter CreateFormatter(System.Security.Cryptography.AsymmetricAlgorithm key) { throw null; }
     }
+    public sealed partial class SP800108HmacCounterKdf : System.IDisposable
+    {
+        public SP800108HmacCounterKdf(byte[] key, System.Security.Cryptography.HashAlgorithmName hashAlgorithm) { }
+        public SP800108HmacCounterKdf(System.ReadOnlySpan<byte> key, System.Security.Cryptography.HashAlgorithmName hashAlgorithm) { }
+        public static byte[] DeriveBytes(byte[] key, System.Security.Cryptography.HashAlgorithmName hashAlgorithm, byte[] label, byte[] context, int derivedKeyLengthInBytes) { throw null; }
+        public static byte[] DeriveBytes(byte[] key, System.Security.Cryptography.HashAlgorithmName hashAlgorithm, string label, string context, int derivedKeyLengthInBytes) { throw null; }
+        public static byte[] DeriveBytes(System.ReadOnlySpan<byte> key, System.Security.Cryptography.HashAlgorithmName hashAlgorithm, System.ReadOnlySpan<byte> label, System.ReadOnlySpan<byte> context, int derivedKeyLengthInBytes) { throw null; }
+        public static void DeriveBytes(System.ReadOnlySpan<byte> key, System.Security.Cryptography.HashAlgorithmName hashAlgorithm, System.ReadOnlySpan<byte> label, System.ReadOnlySpan<byte> context, System.Span<byte> destination) { }
+        public static byte[] DeriveBytes(System.ReadOnlySpan<byte> key, System.Security.Cryptography.HashAlgorithmName hashAlgorithm, System.ReadOnlySpan<char> label, System.ReadOnlySpan<char> context, int derivedKeyLengthInBytes) { throw null; }
+        public static void DeriveBytes(System.ReadOnlySpan<byte> key, System.Security.Cryptography.HashAlgorithmName hashAlgorithm, System.ReadOnlySpan<char> label, System.ReadOnlySpan<char> context, System.Span<byte> destination) { }
+        public byte[] DeriveKey(byte[] label, byte[] context, int derivedKeyLengthInBytes) { throw null; }
+        public byte[] DeriveKey(System.ReadOnlySpan<byte> label, System.ReadOnlySpan<byte> context, int derivedKeyLengthInBytes) { throw null; }
+        public void DeriveKey(System.ReadOnlySpan<byte> label, System.ReadOnlySpan<byte> context, System.Span<byte> destination) { }
+        public byte[] DeriveKey(System.ReadOnlySpan<char> label, System.ReadOnlySpan<char> context, int derivedKeyLengthInBytes) { throw null; }
+        public void DeriveKey(System.ReadOnlySpan<char> label, System.ReadOnlySpan<char> context, System.Span<byte> destination) { }
+        public byte[] DeriveKey(string label, string context, int derivedKeyLengthInBytes) { throw null; }
+        public void Dispose() { }
+    }
     public abstract partial class SymmetricAlgorithm : System.IDisposable
     {
         protected int BlockSizeValue;
index 6ce867e..f31af3b 100644 (file)
@@ -93,6 +93,9 @@
   <data name="Argument_DestinationTooShort" xml:space="preserve">
     <value>Destination is too short.</value>
   </data>
+  <data name="Argument_EmptyString" xml:space="preserve">
+    <value>The value cannot be an empty string.</value>
+  </data>
   <data name="Argument_Invalid_SafeHandleInvalidOrClosed" xml:space="preserve">
     <value>The method cannot be called with an invalid or closed SafeHandle.</value>
   </data>
   <data name="ArgumentOutOfRange_IndexMustBeLessOrEqual" xml:space="preserve">
     <value>Index was out of range. Must be non-negative and less than or equal to the size of the collection.</value>
   </data>
+  <data name="ArgumentOutOfRange_KOut_Too_Large" xml:space="preserve">
+    <value>The number of bytes requested is too large. The number of bytes produced by SP800108HmacCounterKdf cannot exceed 536,870,911 bytes.</value>
+  </data>
   <data name="ArgumentOutOfRange_NeedNonNegNum" xml:space="preserve">
     <value>Non-negative number required.</value>
   </data>
index aa1ef1c..af007a2 100644 (file)
              Link="Common\System\Security\Cryptography\RSAKeyFormatHelper.Encrypted.cs" />
     <Compile Include="$(CommonPath)System\Security\Cryptography\RsaPaddingProcessor.cs"
              Link="Common\System\Security\Cryptography\RsaPaddingProcessor.cs" />
+    <Compile Include="$(CommonPath)System\Security\Cryptography\SP800108HmacCounterKdf.cs"
+             Link="Common\System\Security\Cryptography\SP800108HmacCounterKdf.cs" />
+    <Compile Include="$(CommonPath)System\Security\Cryptography\SP800108HmacCounterKdfImplementationBase.cs"
+             Link="Common\System\Security\Cryptography\SP800108HmacCounterKdfImplementationBase.cs" />
+    <Compile Include="$(CommonPath)System\Security\Cryptography\Utf8DataEncoding.cs"
+             Link="Common\System\Security\Cryptography\Utf8DataEncoding.cs" />
     <Compile Include="$(CommonPath)System\Text\ValueStringBuilder.cs"
              Link="Common\System\Text\ValueStringBuilder.cs" />
     <Compile Include="$(CommonPath)System\Threading\Tasks\TaskToApm.cs"
              Link="Common\Interop\Browser\Interop.Libraries.cs" />
     <Compile Include="$(CommonPath)System\Sha1ForNonSecretPurposes.cs"
              Link="Common\System\Sha1ForNonSecretPurposes.cs" />
+    <Compile Include="$(CommonPath)System\Security\Cryptography\SP800108HmacCounterKdfImplementationManaged.cs"
+             Link="Common\System\Security\Cryptography\SP800108HmacCounterKdfImplementationManaged.cs" />
     <Compile Include="System\Security\Cryptography\AesCcm.NotSupported.cs" />
     <Compile Include="System\Security\Cryptography\AesGcm.NotSupported.cs" />
     <Compile Include="System\Security\Cryptography\AesImplementation.NotSupported.cs" />
     <Compile Include="System\Security\Cryptography\RC2Implementation.NotSupported.cs" />
     <Compile Include="System\Security\Cryptography\RSACryptoServiceProvider.NotSupported.cs" />
     <Compile Include="System\Security\Cryptography\RSA.Create.NotSupported.cs" />
+    <Compile Include="System\Security\Cryptography\SP800108HmacCounterKdf.Managed.cs" />
+    <Compile Include="System\Security\Cryptography\SP800108HmacCounterKdfImplementationManaged.cs" />
     <Compile Include="System\Security\Cryptography\SHAHashProvider.Browser.Managed.cs" />
     <Compile Include="System\Security\Cryptography\TripleDESCryptoServiceProvider.NotSupported.cs" />
     <Compile Include="System\Security\Cryptography\TripleDesImplementation.NotSupported.cs" />
              Link="Common\System\Security\Cryptography\ECOpenSsl.ImportExport.cs" />
     <Compile Include="$(CommonPath)System\Security\Cryptography\RSAOpenSsl.cs"
              Link="Common\System\Security\Cryptography\RSAOpenSsl.cs" />
+    <Compile Include="$(CommonPath)System\Security\Cryptography\SP800108HmacCounterKdfImplementationManaged.cs"
+             Link="Common\System\Security\Cryptography\SP800108HmacCounterKdfImplementationManaged.cs" />
     <AsnXml Include="$(CommonPath)System\Security\Cryptography\Asn1\DigestInfoAsn.xml">
       <Link>Common\System\Security\Cryptography\Asn1\DigestInfoAsn.xml</Link>
     </AsnXml>
     <Compile Include="System\Security\Cryptography\RSAOpenSsl.cs" />
     <Compile Include="System\Security\Cryptography\RSAWrapper.cs" />
     <Compile Include="System\Security\Cryptography\SafeEvpPKeyHandle.OpenSsl.cs" />
+    <Compile Include="System\Security\Cryptography\SP800108HmacCounterKdf.Managed.cs" />
+    <Compile Include="System\Security\Cryptography\SP800108HmacCounterKdfImplementationManaged.cs" />
     <Compile Include="System\Security\Cryptography\TripleDESCryptoServiceProvider.Wrap.cs" />
     <Compile Include="System\Security\Cryptography\TripleDesImplementation.OpenSsl.cs" />
     <AsnXml Include="System\Security\Cryptography\X509Certificates\Asn1\DistributionPointAsn.xml" />
              Link="Common\System\Security\Cryptography\ECDsaAndroid.cs" />
     <Compile Include="$(CommonPath)System\Security\Cryptography\RSAAndroid.cs"
              Link="Common\System\Security\Cryptography\RSAAndroid.cs" />
+    <Compile Include="$(CommonPath)System\Security\Cryptography\SP800108HmacCounterKdfImplementationManaged.cs"
+             Link="Common\System\Security\Cryptography\SP800108HmacCounterKdfImplementationManaged.cs" />
     <AsnXml Include="$(CommonPath)System\Security\Cryptography\Asn1\DigestInfoAsn.xml">
       <Link>Common\System\Security\Cryptography\Asn1\DigestInfoAsn.xml</Link>
     </AsnXml>
     <Compile Include="System\Security\Cryptography\RC2Implementation.NotSupported.cs" />
     <Compile Include="System\Security\Cryptography\RSA.Create.Android.cs" />
     <Compile Include="System\Security\Cryptography\RSACryptoServiceProvider.Unix.cs" />
+    <Compile Include="System\Security\Cryptography\SP800108HmacCounterKdf.Managed.cs" />
+<Compile Include="System\Security\Cryptography\SP800108HmacCounterKdfImplementationManaged.cs" />
     <Compile Include="System\Security\Cryptography\TripleDESCryptoServiceProvider.Wrap.cs" />
     <Compile Include="System\Security\Cryptography\TripleDesImplementation.OpenSsl.cs" />
     <Compile Include="System\Security\Cryptography\X509Certificates\AndroidCertificatePal.cs" />
              Link="Common\System\Security\Cryptography\ECDsaSecurityTransforms.cs" />
     <Compile Include="$(CommonPath)System\Security\Cryptography\RSASecurityTransforms.cs"
              Link="Common\System\Security\Cryptography\RSASecurityTransforms.cs" />
+    <Compile Include="$(CommonPath)System\Security\Cryptography\SP800108HmacCounterKdfImplementationManaged.cs"
+             Link="Common\System\Security\Cryptography\SP800108HmacCounterKdfImplementationManaged.cs" />
     <Compile Include="$(CommonPath)System\Security\Cryptography\SecKeyPair.cs"
              Link="Common\System\Security\Cryptography\SecKeyPair.cs" />
     <Compile Include="System\Security\Cryptography\AesImplementation.Apple.cs" />
     <Compile Include="System\Security\Cryptography\RC2Implementation.Apple.cs" />
     <Compile Include="System\Security\Cryptography\RSA.Create.SecurityTransforms.cs" />
     <Compile Include="System\Security\Cryptography\RSACryptoServiceProvider.Unix.cs" />
+    <Compile Include="System\Security\Cryptography\SP800108HmacCounterKdf.Managed.cs" />
+    <Compile Include="System\Security\Cryptography\SP800108HmacCounterKdfImplementationManaged.cs" />
     <Compile Include="System\Security\Cryptography\TripleDESCryptoServiceProvider.Wrap.cs" />
     <Compile Include="System\Security\Cryptography\TripleDesImplementation.Apple.cs" />
     <Compile Include="System\Security\Cryptography\X509Certificates\AppleCertificatePal.cs" />
              Link="Common\System\Security\Cryptography\RSACng.ImportExport.cs" />
     <Compile Include="$(CommonPath)System\Security\Cryptography\RSACng.SignVerify.cs"
              Link="Common\System\Security\Cryptography\RSACng.SignVerify.cs" />
+    <Compile Include="$(CommonPath)System\Security\Cryptography\SP800108HmacCounterKdfImplementationCng.cs"
+             Link="Common\System\Security\Cryptography\SP800108HmacCounterKdfImplementationCng.cs" />
+    <Compile Include="$(CommonPath)System\Security\Cryptography\SP800108HmacCounterKdfImplementationManaged.cs"
+             Link="Common\System\Security\Cryptography\SP800108HmacCounterKdfImplementationManaged.cs" />
     <Compile Include="Microsoft\Win32\SafeHandles\NCryptSafeHandles.cs" />
     <Compile Include="System\Security\Cryptography\AeadCommon.Windows.cs" />
     <Compile Include="System\Security\Cryptography\AesCcm.Windows.cs" />
     <Compile Include="System\Security\Cryptography\RSACng.cs" />
     <Compile Include="System\Security\Cryptography\RSACng.ImportExport.cs" />
     <Compile Include="System\Security\Cryptography\RSACng.Key.cs" />
+    <Compile Include="System\Security\Cryptography\SP800108HmacCounterKdf.Windows.cs" />
+    <Compile Include="System\Security\Cryptography\SP800108HmacCounterKdfImplementationCng.cs" />
+    <Compile Include="System\Security\Cryptography\SP800108HmacCounterKdfImplementationManaged.cs" />
     <Compile Include="System\Security\Cryptography\TripleDESCng.Windows.cs" />
     <Compile Include="System\Security\Cryptography\TripleDESCryptoServiceProvider.Wrap.cs" />
     <Compile Include="System\Security\Cryptography\TripleDesImplementation.Windows.cs" />
diff --git a/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/SP800108HmacCounterKdf.Managed.cs b/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/SP800108HmacCounterKdf.Managed.cs
new file mode 100644 (file)
index 0000000..bcef2ff
--- /dev/null
@@ -0,0 +1,54 @@
+// 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;
+
+namespace System.Security.Cryptography
+{
+    public sealed partial class SP800108HmacCounterKdf : IDisposable
+    {
+        private static partial SP800108HmacCounterKdfImplementationBase CreateImplementation(
+            ReadOnlySpan<byte> key,
+            HashAlgorithmName hashAlgorithm)
+        {
+            return new SP800108HmacCounterKdfImplementationManaged(key, hashAlgorithm);
+        }
+
+        private static partial byte[] DeriveBytesCore(
+            byte[] key,
+            HashAlgorithmName hashAlgorithm,
+            byte[] label,
+            byte[] context,
+            int derivedKeyLengthInBytes)
+        {
+            byte[] result = new byte[derivedKeyLengthInBytes];
+
+            SP800108HmacCounterKdfImplementationManaged.DeriveBytesOneShot(key, hashAlgorithm, label, context, result);
+
+            return result;
+        }
+
+        private static partial void DeriveBytesCore(
+            ReadOnlySpan<byte> key,
+            HashAlgorithmName hashAlgorithm,
+            ReadOnlySpan<byte> label,
+            ReadOnlySpan<byte> context,
+            Span<byte> destination)
+        {
+            SP800108HmacCounterKdfImplementationManaged.DeriveBytesOneShot(key, hashAlgorithm, label, context, destination);
+        }
+
+        private static partial void DeriveBytesCore(
+            ReadOnlySpan<byte> key,
+            HashAlgorithmName hashAlgorithm,
+            ReadOnlySpan<char> label,
+            ReadOnlySpan<char> context,
+            Span<byte> destination)
+        {
+            SP800108HmacCounterKdfImplementationManaged.DeriveBytesOneShot(key, hashAlgorithm, label, context, destination);
+        }
+    }
+}
diff --git a/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/SP800108HmacCounterKdf.Windows.cs b/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/SP800108HmacCounterKdf.Windows.cs
new file mode 100644 (file)
index 0000000..9ad1afc
--- /dev/null
@@ -0,0 +1,84 @@
+// 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;
+
+namespace System.Security.Cryptography
+{
+    public sealed partial class SP800108HmacCounterKdf : IDisposable
+    {
+        private static readonly bool s_isWindows8OrGreater = OperatingSystem.IsWindowsVersionAtLeast(6, 2);
+
+        private static partial SP800108HmacCounterKdfImplementationBase CreateImplementation(
+            ReadOnlySpan<byte> key,
+            HashAlgorithmName hashAlgorithm)
+        {
+            if (s_isWindows8OrGreater)
+            {
+                return new SP800108HmacCounterKdfImplementationCng(key, hashAlgorithm);
+            }
+            else
+            {
+                return new SP800108HmacCounterKdfImplementationManaged(key, hashAlgorithm);
+            }
+        }
+
+        private static partial byte[] DeriveBytesCore(
+            byte[] key,
+            HashAlgorithmName hashAlgorithm,
+            byte[] label,
+            byte[] context,
+            int derivedKeyLengthInBytes)
+        {
+            byte[] result = new byte[derivedKeyLengthInBytes];
+
+            if (s_isWindows8OrGreater)
+            {
+                SP800108HmacCounterKdfImplementationCng.DeriveBytesOneShot(key, hashAlgorithm, label, context, result);
+            }
+            else
+            {
+                SP800108HmacCounterKdfImplementationManaged.DeriveBytesOneShot(key, hashAlgorithm, label, context, result);
+            }
+
+            return result;
+        }
+
+        private static partial void DeriveBytesCore(
+            ReadOnlySpan<byte> key,
+            HashAlgorithmName hashAlgorithm,
+            ReadOnlySpan<byte> label,
+            ReadOnlySpan<byte> context,
+            Span<byte> destination)
+        {
+            if (s_isWindows8OrGreater)
+            {
+                SP800108HmacCounterKdfImplementationCng.DeriveBytesOneShot(key, hashAlgorithm, label, context, destination);
+            }
+            else
+            {
+                SP800108HmacCounterKdfImplementationManaged.DeriveBytesOneShot(key, hashAlgorithm, label, context, destination);
+            }
+        }
+
+        private static partial void DeriveBytesCore(
+            ReadOnlySpan<byte> key,
+            HashAlgorithmName hashAlgorithm,
+            ReadOnlySpan<char> label,
+            ReadOnlySpan<char> context,
+            Span<byte> destination)
+        {
+            if (s_isWindows8OrGreater)
+            {
+                SP800108HmacCounterKdfImplementationCng.DeriveBytesOneShot(key, hashAlgorithm, label, context, destination);
+            }
+            else
+            {
+                SP800108HmacCounterKdfImplementationManaged.DeriveBytesOneShot(key, hashAlgorithm, label, context, destination);
+            }
+        }
+    }
+}
diff --git a/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/SP800108HmacCounterKdfImplementationCng.cs b/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/SP800108HmacCounterKdfImplementationCng.cs
new file mode 100644 (file)
index 0000000..75025e5
--- /dev/null
@@ -0,0 +1,74 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System.Diagnostics;
+
+namespace System.Security.Cryptography
+{
+    internal sealed partial class SP800108HmacCounterKdfImplementationCng
+    {
+        internal unsafe SP800108HmacCounterKdfImplementationCng(ReadOnlySpan<byte> key, HashAlgorithmName hashAlgorithm)
+        {
+            Debug.Assert(hashAlgorithm.Name is not null);
+
+            scoped ReadOnlySpan<byte> symmetricKeyMaterial;
+            scoped Span<byte> clearSpan = default;
+            int symmetricKeyMaterialLength;
+            int hashAlgorithmBlockSize = GetHashBlockSize(hashAlgorithm.Name);
+
+            if (key.Length > hashAlgorithmBlockSize)
+            {
+                Span<byte> buffer = stackalloc byte[512 / 8]; // Largest supported digest is SHA512.
+                symmetricKeyMaterialLength = HashOneShot(hashAlgorithm, key, buffer);
+                clearSpan = buffer.Slice(0, symmetricKeyMaterialLength);
+                symmetricKeyMaterial = clearSpan;
+            }
+            else if (!key.IsEmpty)
+            {
+                symmetricKeyMaterial = key;
+                symmetricKeyMaterialLength = key.Length;
+            }
+            else
+            {
+                // CNG requires a non-null pointer even when the length is zero.
+                symmetricKeyMaterial = stackalloc byte[] { 0 };
+                symmetricKeyMaterialLength = 0;
+            }
+
+            try
+            {
+                fixed (byte* pSymmetricKeyMaterial = symmetricKeyMaterial)
+                {
+                    _keyHandle = CreateSymmetricKey(pSymmetricKeyMaterial, symmetricKeyMaterialLength);
+                }
+            }
+            finally
+            {
+                CryptographicOperations.ZeroMemory(clearSpan);
+            }
+
+            _hashAlgorithm = hashAlgorithm;
+        }
+
+        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Security", "CA5350", Justification = "Weak algorithms are used as instructed by the caller")]
+        private static int HashOneShot(HashAlgorithmName hashAlgorithm, ReadOnlySpan<byte> data, Span<byte> destination)
+        {
+            Debug.Assert(hashAlgorithm.Name is not null);
+
+            switch (hashAlgorithm.Name)
+            {
+                case HashAlgorithmNames.SHA1:
+                    return SHA1.HashData(data, destination);
+                case HashAlgorithmNames.SHA256:
+                    return SHA256.HashData(data, destination);
+                case HashAlgorithmNames.SHA384:
+                    return SHA384.HashData(data, destination);
+                case HashAlgorithmNames.SHA512:
+                    return SHA512.HashData(data, destination);
+                default:
+                    Debug.Fail($"Unexpected hash algorithm '{hashAlgorithm.Name}'");
+                    throw new CryptographicException();
+            }
+        }
+    }
+}
diff --git a/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/SP800108HmacCounterKdfImplementationManaged.cs b/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/SP800108HmacCounterKdfImplementationManaged.cs
new file mode 100644 (file)
index 0000000..017bcda
--- /dev/null
@@ -0,0 +1,99 @@
+// 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;
+
+namespace System.Security.Cryptography
+{
+    internal sealed partial class SP800108HmacCounterKdfImplementationManaged
+    {
+        private const int CharToBytesStackBufferSize = 256;
+
+        public SP800108HmacCounterKdfImplementationManaged(ReadOnlySpan<byte> key, HashAlgorithmName hashAlgorithm)
+        {
+            // Use the POH if we can so the key doesn't get moved around by the GC.
+            _key = GC.AllocateArray<byte>(key.Length, pinned: true);
+            key.CopyTo(_key);
+            _hashAlgorithm = hashAlgorithm;
+        }
+
+        internal static void DeriveBytesOneShot(
+            ReadOnlySpan<byte> key,
+            HashAlgorithmName hashAlgorithm,
+            ReadOnlySpan<byte> label,
+            ReadOnlySpan<byte> context,
+            Span<byte> destination)
+        {
+            if (destination.Length == 0)
+            {
+                return;
+            }
+
+            Debug.Assert(destination.Length <= 0x1FFFFFFF);
+
+            // Do everything as checked. Over/underflows are never expected.
+            checked
+            {
+                // The KDF is defined as K(i) := PRF (KI, [i]2 || Label || 0x00 || Context || [L]2)
+                // We know L is already less than 0x1FFFFFFF. h = ceil(L / h) where H is the hash length in bits.
+                // So we don't expect i to overflow.
+                using (IncrementalHash hash = IncrementalHash.CreateHMAC(hashAlgorithm, key))
+                {
+                    Span<byte> iBuffer = stackalloc byte[sizeof(uint)];
+                    Span<byte> lBuffer = stackalloc byte[sizeof(uint)];
+                    ReadOnlySpan<byte> zero = stackalloc byte[] { 0 };
+                    Span<byte> hmacBuffer = stackalloc byte[512 / 8]; // Largest HMAC supported is SHA512
+                    int hmacBufferWritten = 0;
+
+                    BinaryPrimitives.WriteUInt32BigEndian(lBuffer, (uint)destination.Length * 8U);
+
+                    for (uint i = 1; !destination.IsEmpty; i++)
+                    {
+                        BinaryPrimitives.WriteUInt32BigEndian(iBuffer, i);
+                        hash.AppendData(iBuffer);
+                        hash.AppendData(label);
+                        hash.AppendData(zero);
+                        hash.AppendData(context);
+                        hash.AppendData(lBuffer);
+
+                        if (destination.Length >= hash.HashLengthInBytes)
+                        {
+                            int written = hash.GetHashAndReset(destination);
+                            destination = destination.Slice(written);
+                        }
+                        else
+                        {
+                            hmacBufferWritten = hash.GetHashAndReset(hmacBuffer);
+                            Debug.Assert(hmacBufferWritten > destination.Length);
+                            hmacBuffer.Slice(0, destination.Length).CopyTo(destination);
+                            destination = default;
+                        }
+                    }
+
+                    // Get derived key material off the stack, if any.
+                    CryptographicOperations.ZeroMemory(hmacBuffer.Slice(0, hmacBufferWritten));
+                }
+            }
+        }
+
+         internal static void DeriveBytesOneShot(
+            ReadOnlySpan<byte> key,
+            HashAlgorithmName hashAlgorithm,
+            ReadOnlySpan<char> label,
+            ReadOnlySpan<char> context,
+            Span<byte> 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);
+            }
+        }
+    }
+}
index 3165257..bfbb590 100644 (file)
              Link="ProductionCode\Common\System\Net\MultiArrayBuffer.cs" />
     <Compile Include="$(CommonPath)System\Net\StreamBuffer.cs"
              Link="ProductionCode\Common\System\Net\StreamBuffer.cs" />
+    <Compile Include="$(CommonTestPath)System\Security\Cryptography\SP800108HmacCounterKdfTests.ArgValidation.cs"
+             Link="CommonTest\System\Security\Cryptography\SP800108HmacCounterKdfTests.ArgValidation.cs" />
+    <Compile Include="$(CommonTestPath)System\Security\Cryptography\SP800108HmacCounterKdfTests.Functional.cs"
+             Link="CommonTest\System\Security\Cryptography\SP800108HmacCounterKdfTests.Functional.cs" />
+    <Compile Include="$(CommonTestPath)System\Security\Cryptography\SP800108HmacCounterKdfTests.Helpers.cs"
+             Link="CommonTest\System\Security\Cryptography\SP800108HmacCounterKdfTests.Helpers.cs" />
+    <Compile Include="$(CommonTestPath)System\Security\Cryptography\SP800108HmacCounterKdfTests.ThreadSafety.cs"
+             Link="CommonTest\System\Security\Cryptography\SP800108HmacCounterKdfTests.ThreadSafety.cs" />
     <Compile Include="$(CommonTestPath)System\Security\Cryptography\SignatureSupport.cs"
              Link="CommonTest\System\Security\Cryptography\SignatureSupport.cs" />
     <Compile Include="$(CommonTestPath)System\Security\Cryptography\AlgorithmImplementations\AES\AesCipherTests.Data.cs"