Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

SP800-108 CTR HMAC #79120

Merged
merged 8 commits into from
Feb 7, 2023
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,15 @@ public enum BCryptAlgPseudoHandle : uint
BCRYPT_PBKDF2_ALG_HANDLE = 0x00000331,
}

internal static bool PseudoHandlesSupported { get; } = OperatingSystem.IsWindowsVersionAtLeast(10, 0, 0);
internal static bool PseudoHandlesSupported { get; } =
#if NET
OperatingSystem.IsWindowsVersionAtLeast(10, 0, 0);
#elif NETSTANDARD2_0_OR_GREATER
RuntimeInformation.IsOSPlatform(OSPlatform.Windows) && Environment.OSVersion.Version.Major >= 10;
#elif NETFRAMEWORK
Environment.OSVersion.Version.Major >= 10;
#else
#error Unhandled platform targets
#endif
}
}

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -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();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,213 @@
// 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;
}

/// <summary>
/// Returns null if the platform ins Windows 10+ and psuedo handles should be used.
vcsjones marked this conversation as resolved.
Show resolved Hide resolved
/// </summary>
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();
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
// 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();
}

vcsjones marked this conversation as resolved.
Show resolved Hide resolved
}

private void ZeroKey()
{
CryptographicOperations.ZeroMemory(_key);
_key = null!;
}
}
}
Original file line number Diff line number Diff line change
@@ -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);
}
}
}

}
Loading