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

Use BCrypt for ephemeral RSA on Windows #76277

Merged
merged 4 commits into from
Sep 30, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,10 +1,8 @@
// 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.Diagnostics;
using System.Collections.Concurrent;
using System.Security.Cryptography;

using Microsoft.Win32.SafeHandles;

Expand All @@ -31,30 +29,12 @@ public static unsafe SafeBCryptAlgorithmHandle GetCachedBCryptAlgorithmHandle(st
return result.Handle;
}

NTSTATUS ntStatus = Interop.BCrypt.BCryptOpenAlgorithmProvider(out SafeBCryptAlgorithmHandle handle, key.hashAlgorithmId, null, key.flags);
if (ntStatus != NTSTATUS.STATUS_SUCCESS)
{
Exception e = Interop.BCrypt.CreateCryptographicException(ntStatus);
handle.Dispose();
throw e;
}

int hashSize;
ntStatus = Interop.BCrypt.BCryptGetProperty(
handle,
Interop.BCrypt.BCryptPropertyStrings.BCRYPT_HASH_LENGTH,
&hashSize,
sizeof(int),
out int cbHashSize,
0);
if (ntStatus != NTSTATUS.STATUS_SUCCESS)
{
Exception e = Interop.BCrypt.CreateCryptographicException(ntStatus);
handle.Dispose();
throw e;
}
SafeBCryptAlgorithmHandle handle = BCryptOpenAlgorithmProvider(
key.hashAlgorithmId,
null,
key.flags);

Debug.Assert(cbHashSize == sizeof(int));
int hashSize = BCryptGetDWordProperty(handle, BCryptPropertyStrings.BCRYPT_HASH_LENGTH);
Debug.Assert(hashSize > 0);

if (!s_handles.TryAdd(key, (handle, hashSize)))
Expand Down
5 changes: 4 additions & 1 deletion src/libraries/Common/src/Interop/Windows/BCrypt/Cng.cs
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,10 @@ public enum OpenAlgorithmProviderFlags : int
public const string BCRYPT_CHAIN_MODE_CFB = "ChainingModeCFB";
public const string BCRYPT_CHAIN_MODE_CCM = "ChainingModeCCM";

public static SafeAlgorithmHandle BCryptOpenAlgorithmProvider(string pszAlgId, string? pszImplementation, OpenAlgorithmProviderFlags dwFlags)
public static SafeAlgorithmHandle BCryptOpenAlgorithmProvider(
string pszAlgId,
string? pszImplementation = null,
OpenAlgorithmProviderFlags dwFlags = 0)
{
SafeAlgorithmHandle hAlgorithm;
NTSTATUS ntStatus = Interop.BCryptOpenAlgorithmProvider(out hAlgorithm, pszAlgId, pszImplementation, (int)dwFlags);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,205 @@
// 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 Microsoft.Win32.SafeHandles;

internal static partial class Interop
{
internal static partial class BCrypt
{
[Flags]
private enum BCryptEncryptFlags : uint
{
BCRYPT_PAD_PKCS1 = 2,
BCRYPT_PAD_OAEP = 4,
}

[LibraryImport(Libraries.BCrypt)]
private static unsafe partial NTSTATUS BCryptEncrypt(
SafeBCryptKeyHandle hKey,
byte* pbInput,
int cbInput,
void* paddingInfo,
byte* pbIV,
int cbIV,
byte* pbOutput,
int cbOutput,
out int cbResult,
BCryptEncryptFlags dwFlags);

[LibraryImport(Libraries.BCrypt)]
private static unsafe partial NTSTATUS BCryptDecrypt(
SafeBCryptKeyHandle hKey,
byte* pbInput,
int cbInput,
void* paddingInfo,
byte* pbIV,
int cbIV,
byte* pbOutput,
int cbOutput,
out int cbResult,
BCryptEncryptFlags dwFlags);

private static unsafe int BCryptEncryptRsa(
SafeBCryptKeyHandle key,
ReadOnlySpan<byte> source,
Span<byte> destination,
void* pPaddingInfo,
BCryptEncryptFlags dwFlags)
{
// BCryptEncrypt does not accept null/0, only non-null/0.
Span<byte> notNull = stackalloc byte[1];
bartonjs marked this conversation as resolved.
Show resolved Hide resolved
scoped ReadOnlySpan<byte> effectiveSource;

if (source.IsEmpty)
{
effectiveSource = notNull.Slice(1);
}
else
{
effectiveSource = source;
}

NTSTATUS status;
int written;

fixed (byte* pSource = &MemoryMarshal.GetReference(effectiveSource))
fixed (byte* pDest = &MemoryMarshal.GetReference(destination))
{
status = BCryptEncrypt(
key,
pSource,
source.Length,
pPaddingInfo,
null,
0,
pDest,
destination.Length,
out written,
dwFlags);
}

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

return written;
}

private static unsafe bool BCryptDecryptRsa(
SafeBCryptKeyHandle key,
ReadOnlySpan<byte> source,
Span<byte> destination,
void* pPaddingInfo,
BCryptEncryptFlags dwFlags,
out int bytesWritten)
{
NTSTATUS status;
int written;

fixed (byte* pSource = &MemoryMarshal.GetReference(source))
fixed (byte* pDest = &MemoryMarshal.GetReference(destination))
{
status = BCryptDecrypt(
key,
pSource,
source.Length,
pPaddingInfo,
null,
0,
pDest,
destination.Length,
out written,
dwFlags);
}

if (status == NTSTATUS.STATUS_SUCCESS)
{
bytesWritten = written;
return true;
}

if (status == NTSTATUS.STATUS_BUFFER_TOO_SMALL)
{
bytesWritten = 0;
return false;
}

throw CreateCryptographicException(status);
}

internal static unsafe int BCryptEncryptPkcs1(
SafeBCryptKeyHandle key,
ReadOnlySpan<byte> source,
Span<byte> destination)
{
return BCryptEncryptRsa(
key,
source,
destination,
null,
BCryptEncryptFlags.BCRYPT_PAD_PKCS1);
}

internal static unsafe int BCryptEncryptOaep(
SafeBCryptKeyHandle key,
ReadOnlySpan<byte> source,
Span<byte> destination,
string? hashAlgorithmName)
{
fixed (char* pHashAlg = hashAlgorithmName)
{
BCRYPT_OAEP_PADDING_INFO paddingInfo = default;
paddingInfo.pszAlgId = (IntPtr)pHashAlg;

return BCryptEncryptRsa(
key,
source,
destination,
&paddingInfo,
BCryptEncryptFlags.BCRYPT_PAD_OAEP);
}
}

internal static unsafe bool BCryptDecryptPkcs1(
SafeBCryptKeyHandle key,
ReadOnlySpan<byte> source,
Span<byte> destination,
out int bytesWritten)
{
return BCryptDecryptRsa(
key,
source,
destination,
null,
BCryptEncryptFlags.BCRYPT_PAD_PKCS1,
out bytesWritten);
}

internal static unsafe bool BCryptDecryptOaep(
SafeBCryptKeyHandle key,
ReadOnlySpan<byte> source,
Span<byte> destination,
string? hashAlgorithmName,
out int bytesWritten)
{
fixed (char* pHashAlg = hashAlgorithmName)
{
BCRYPT_OAEP_PADDING_INFO paddingInfo = default;
paddingInfo.pszAlgId = (IntPtr)pHashAlg;

return BCryptDecryptRsa(
key,
source,
destination,
&paddingInfo,
BCryptEncryptFlags.BCRYPT_PAD_OAEP,
out bytesWritten);
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@
// The .NET Foundation licenses this file to you under the MIT license.

using System;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Security.Cryptography;

using Microsoft.Win32.SafeHandles;

Expand All @@ -12,6 +12,34 @@ internal static partial class Interop
internal static partial class BCrypt
{
[LibraryImport(Libraries.BCrypt, StringMarshalling = StringMarshalling.Utf16)]
internal static partial NTSTATUS BCryptExportKey(SafeBCryptKeyHandle hKey, IntPtr hExportKey, string pszBlobType, byte[]? pbOutput, int cbOutput, out int pcbResult, int dwFlags);
private static partial NTSTATUS BCryptExportKey(
SafeBCryptKeyHandle hKey,
IntPtr hExportKey,
string pszBlobType,
byte[]? pbOutput,
int cbOutput,
out int pcbResult,
int dwFlags);

internal static ArraySegment<byte> BCryptExportKey(SafeBCryptKeyHandle key, string blobType)
{
int numBytesNeeded;
NTSTATUS ntStatus = BCryptExportKey(key, IntPtr.Zero, blobType, null, 0, out numBytesNeeded, 0);

if (ntStatus != NTSTATUS.STATUS_SUCCESS)
{
throw CreateCryptographicException(ntStatus);
}

byte[] rented = CryptoPool.Rent(numBytesNeeded);
ntStatus = BCryptExportKey(key, IntPtr.Zero, blobType, rented, numBytesNeeded, out numBytesNeeded, 0);

if (ntStatus != NTSTATUS.STATUS_SUCCESS)
{
throw CreateCryptographicException(ntStatus);
}

return new ArraySegment<byte>(rented, 0, numBytesNeeded);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
// 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 Microsoft.Win32.SafeHandles;

internal static partial class Interop
{
internal static partial class BCrypt
{
[LibraryImport(Libraries.BCrypt)]
private static unsafe partial NTSTATUS BCryptFinalizeKeyPair(
SafeBCryptKeyHandle hKey,
uint dwFlags);

internal static void BCryptFinalizeKeyPair(SafeBCryptKeyHandle key)
{
NTSTATUS status = BCryptFinalizeKeyPair(key, 0);

if (status != NTSTATUS.STATUS_SUCCESS)
{
throw CreateCryptographicException(status);
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.Runtime.InteropServices;

using Microsoft.Win32.SafeHandles;

internal static partial class Interop
{
internal static partial class BCrypt
{
[LibraryImport(Libraries.BCrypt)]
private static unsafe partial NTSTATUS BCryptGenerateKeyPair(
SafeBCryptAlgorithmHandle hAlgorithm,
out SafeBCryptKeyHandle phKey,
int dwLength,
vcsjones marked this conversation as resolved.
Show resolved Hide resolved
uint dwFlags);

internal static SafeBCryptKeyHandle BCryptGenerateKeyPair(
SafeBCryptAlgorithmHandle hAlgorithm,
int keyLength)
{
NTSTATUS status = BCryptGenerateKeyPair(
hAlgorithm,
out SafeBCryptKeyHandle hKey,
keyLength,
0);

if (status != NTSTATUS.STATUS_SUCCESS)
{
hKey.Dispose();
throw CreateCryptographicException(status);
}

return hKey;
}
}
}
Loading