-
Notifications
You must be signed in to change notification settings - Fork 4.8k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Use BCrypt for ephemeral RSA on Windows
Windows CNG has two different libraries: bcrypt.dll (`BCrypt*` functions) for in-memory/ephemeral operations, and ncrypt.dll (`NCrypt*` functions) for persisted key operations. Since the NCrypt functions can also operate on ephemeral keys our cryptographic operations have generally been done in terms of NCrypt. NCrypt's flexibility (to also work with persisted keys) comes at a cost. All key operations are done out-of-process (in lsass.exe), and that requires an (L)RPC call for every operation. It also means there are simply more moving parts, and thus more room for error. With this change we will use BCrypt for RSA operations on Windows from `RSA.Create()` and `cert.GetRSAPublicKey()`. ECDSA/ECDH/DSA can any/all be changed to follow suit later. For keys created from RSA.Create() a verification operation currently looks like * Virtual invoke to RSAWrapper.VerifyHash * Maybe-devirtualized invoke to RSACng.VerifyHash * P/Invoke to NCryptVerifySignature * "Virtual" invoke to MSKSPVerifySignature (or whatever it's really called) * LRPC call * Find key in the MRU ring * Effectively a call to BCryptVerifySignature After this change it is instead * Virtual invoke to RSABCrypt.VerifyHash * P/Invoke to BCryptVerifySignature Removing all of those other indirections removes about 40us from a 50us operation (on my primary devbox). As part of making some code be shared between RSACng and RSABCrypt, some allocating code changed to pooling code and some array code got spanified.
- Loading branch information
Showing
22 changed files
with
1,381 additions
and
360 deletions.
There are no files selected for viewing
This file contains 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
This file contains 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
205 changes: 205 additions & 0 deletions
205
src/libraries/Common/src/Interop/Windows/BCrypt/Interop.BCryptEncryptDecrypt.RSA.cs
This file contains 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,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]; | ||
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); | ||
} | ||
} | ||
} | ||
} |
This file contains 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
28 changes: 28 additions & 0 deletions
28
src/libraries/Common/src/Interop/Windows/BCrypt/Interop.BCryptFinalizeKey.cs
This file contains 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,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); | ||
} | ||
} | ||
} | ||
} |
38 changes: 38 additions & 0 deletions
38
src/libraries/Common/src/Interop/Windows/BCrypt/Interop.BCryptGenerateKeyPair.cs
This file contains 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,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, | ||
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; | ||
} | ||
} | ||
} |
Oops, something went wrong.