-
Notifications
You must be signed in to change notification settings - Fork 5.1k
SP800-108 CTR HMAC #79120
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
Merged
Merged
SP800-108 CTR HMAC #79120
Changes from 6 commits
Commits
Show all changes
8 commits
Select commit
Hold shift + click to select a range
1d8075a
SP800108: Basic API shape and Windows CNG implementation
vcsjones 09fc792
SP800108: Managed implementation provided
vcsjones 5bcc6d0
SP800108: Add tests
vcsjones e8c6257
SP800108: Add Microsoft.Bcl.Cryptography
vcsjones 7a51f3e
SP800108: Minor fixes
vcsjones 9a497c4
SP800108: Make Windows 7 work because it's easy
vcsjones cb6a781
SP800108: Code review feedback
vcsjones bd1c66b
SP800108: Remove incorrect platform check
vcsjones File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
611 changes: 611 additions & 0 deletions
611
src/libraries/Common/src/System/Security/Cryptography/SP800108HmacCounterKdf.cs
Large diffs are not rendered by default.
Oops, something went wrong.
14 changes: 14 additions & 0 deletions
14
...aries/Common/src/System/Security/Cryptography/SP800108HmacCounterKdfImplementationBase.cs
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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(); | ||
} | ||
} |
213 changes: 213 additions & 0 deletions
213
...raries/Common/src/System/Security/Cryptography/SP800108HmacCounterKdfImplementationCng.cs
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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. | ||
/// </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(); | ||
} | ||
} | ||
} | ||
} |
112 changes: 112 additions & 0 deletions
112
...es/Common/src/System/Security/Cryptography/SP800108HmacCounterKdfImplementationManaged.cs
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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!; | ||
} | ||
} | ||
} |
39 changes: 39 additions & 0 deletions
39
src/libraries/Common/src/System/Security/Cryptography/Utf8DataEncoding.cs
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | ||
} | ||
} | ||
} | ||
|
||
} |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.