Skip to content

Commit fbc2232

Browse files
authored
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.
1 parent 90d9034 commit fbc2232

22 files changed

+1381
-360
lines changed

src/libraries/Common/src/Interop/Windows/BCrypt/BCryptAlgorithmCache.cs

Lines changed: 5 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,8 @@
11
// Licensed to the .NET Foundation under one or more agreements.
22
// The .NET Foundation licenses this file to you under the MIT license.
33

4-
using System;
54
using System.Diagnostics;
65
using System.Collections.Concurrent;
7-
using System.Security.Cryptography;
86

97
using Microsoft.Win32.SafeHandles;
108

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

34-
NTSTATUS ntStatus = Interop.BCrypt.BCryptOpenAlgorithmProvider(out SafeBCryptAlgorithmHandle handle, key.hashAlgorithmId, null, key.flags);
35-
if (ntStatus != NTSTATUS.STATUS_SUCCESS)
36-
{
37-
Exception e = Interop.BCrypt.CreateCryptographicException(ntStatus);
38-
handle.Dispose();
39-
throw e;
40-
}
41-
42-
int hashSize;
43-
ntStatus = Interop.BCrypt.BCryptGetProperty(
44-
handle,
45-
Interop.BCrypt.BCryptPropertyStrings.BCRYPT_HASH_LENGTH,
46-
&hashSize,
47-
sizeof(int),
48-
out int cbHashSize,
49-
0);
50-
if (ntStatus != NTSTATUS.STATUS_SUCCESS)
51-
{
52-
Exception e = Interop.BCrypt.CreateCryptographicException(ntStatus);
53-
handle.Dispose();
54-
throw e;
55-
}
32+
SafeBCryptAlgorithmHandle handle = BCryptOpenAlgorithmProvider(
33+
key.hashAlgorithmId,
34+
null,
35+
key.flags);
5636

57-
Debug.Assert(cbHashSize == sizeof(int));
37+
int hashSize = BCryptGetDWordProperty(handle, BCryptPropertyStrings.BCRYPT_HASH_LENGTH);
5838
Debug.Assert(hashSize > 0);
5939

6040
if (!s_handles.TryAdd(key, (handle, hashSize)))

src/libraries/Common/src/Interop/Windows/BCrypt/Cng.cs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,10 @@ public enum OpenAlgorithmProviderFlags : int
7070
public const string BCRYPT_CHAIN_MODE_CFB = "ChainingModeCFB";
7171
public const string BCRYPT_CHAIN_MODE_CCM = "ChainingModeCCM";
7272

73-
public static SafeAlgorithmHandle BCryptOpenAlgorithmProvider(string pszAlgId, string? pszImplementation, OpenAlgorithmProviderFlags dwFlags)
73+
public static SafeAlgorithmHandle BCryptOpenAlgorithmProvider(
74+
string pszAlgId,
75+
string? pszImplementation = null,
76+
OpenAlgorithmProviderFlags dwFlags = 0)
7477
{
7578
SafeAlgorithmHandle hAlgorithm;
7679
NTSTATUS ntStatus = Interop.BCryptOpenAlgorithmProvider(out hAlgorithm, pszAlgId, pszImplementation, (int)dwFlags);
Lines changed: 205 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,205 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
using System;
5+
using System.Runtime.InteropServices;
6+
7+
using Microsoft.Win32.SafeHandles;
8+
9+
internal static partial class Interop
10+
{
11+
internal static partial class BCrypt
12+
{
13+
[Flags]
14+
private enum BCryptEncryptFlags : uint
15+
{
16+
BCRYPT_PAD_PKCS1 = 2,
17+
BCRYPT_PAD_OAEP = 4,
18+
}
19+
20+
[LibraryImport(Libraries.BCrypt)]
21+
private static unsafe partial NTSTATUS BCryptEncrypt(
22+
SafeBCryptKeyHandle hKey,
23+
byte* pbInput,
24+
int cbInput,
25+
void* paddingInfo,
26+
byte* pbIV,
27+
int cbIV,
28+
byte* pbOutput,
29+
int cbOutput,
30+
out int cbResult,
31+
BCryptEncryptFlags dwFlags);
32+
33+
[LibraryImport(Libraries.BCrypt)]
34+
private static unsafe partial NTSTATUS BCryptDecrypt(
35+
SafeBCryptKeyHandle hKey,
36+
byte* pbInput,
37+
int cbInput,
38+
void* paddingInfo,
39+
byte* pbIV,
40+
int cbIV,
41+
byte* pbOutput,
42+
int cbOutput,
43+
out int cbResult,
44+
BCryptEncryptFlags dwFlags);
45+
46+
private static unsafe int BCryptEncryptRsa(
47+
SafeBCryptKeyHandle key,
48+
ReadOnlySpan<byte> source,
49+
Span<byte> destination,
50+
void* pPaddingInfo,
51+
BCryptEncryptFlags dwFlags)
52+
{
53+
// BCryptEncrypt does not accept null/0, only non-null/0.
54+
Span<byte> notNull = stackalloc byte[1];
55+
scoped ReadOnlySpan<byte> effectiveSource;
56+
57+
if (source.IsEmpty)
58+
{
59+
effectiveSource = notNull.Slice(1);
60+
}
61+
else
62+
{
63+
effectiveSource = source;
64+
}
65+
66+
NTSTATUS status;
67+
int written;
68+
69+
fixed (byte* pSource = &MemoryMarshal.GetReference(effectiveSource))
70+
fixed (byte* pDest = &MemoryMarshal.GetReference(destination))
71+
{
72+
status = BCryptEncrypt(
73+
key,
74+
pSource,
75+
source.Length,
76+
pPaddingInfo,
77+
null,
78+
0,
79+
pDest,
80+
destination.Length,
81+
out written,
82+
dwFlags);
83+
}
84+
85+
if (status != NTSTATUS.STATUS_SUCCESS)
86+
{
87+
throw CreateCryptographicException(status);
88+
}
89+
90+
return written;
91+
}
92+
93+
private static unsafe bool BCryptDecryptRsa(
94+
SafeBCryptKeyHandle key,
95+
ReadOnlySpan<byte> source,
96+
Span<byte> destination,
97+
void* pPaddingInfo,
98+
BCryptEncryptFlags dwFlags,
99+
out int bytesWritten)
100+
{
101+
NTSTATUS status;
102+
int written;
103+
104+
fixed (byte* pSource = &MemoryMarshal.GetReference(source))
105+
fixed (byte* pDest = &MemoryMarshal.GetReference(destination))
106+
{
107+
status = BCryptDecrypt(
108+
key,
109+
pSource,
110+
source.Length,
111+
pPaddingInfo,
112+
null,
113+
0,
114+
pDest,
115+
destination.Length,
116+
out written,
117+
dwFlags);
118+
}
119+
120+
if (status == NTSTATUS.STATUS_SUCCESS)
121+
{
122+
bytesWritten = written;
123+
return true;
124+
}
125+
126+
if (status == NTSTATUS.STATUS_BUFFER_TOO_SMALL)
127+
{
128+
bytesWritten = 0;
129+
return false;
130+
}
131+
132+
throw CreateCryptographicException(status);
133+
}
134+
135+
internal static unsafe int BCryptEncryptPkcs1(
136+
SafeBCryptKeyHandle key,
137+
ReadOnlySpan<byte> source,
138+
Span<byte> destination)
139+
{
140+
return BCryptEncryptRsa(
141+
key,
142+
source,
143+
destination,
144+
null,
145+
BCryptEncryptFlags.BCRYPT_PAD_PKCS1);
146+
}
147+
148+
internal static unsafe int BCryptEncryptOaep(
149+
SafeBCryptKeyHandle key,
150+
ReadOnlySpan<byte> source,
151+
Span<byte> destination,
152+
string? hashAlgorithmName)
153+
{
154+
fixed (char* pHashAlg = hashAlgorithmName)
155+
{
156+
BCRYPT_OAEP_PADDING_INFO paddingInfo = default;
157+
paddingInfo.pszAlgId = (IntPtr)pHashAlg;
158+
159+
return BCryptEncryptRsa(
160+
key,
161+
source,
162+
destination,
163+
&paddingInfo,
164+
BCryptEncryptFlags.BCRYPT_PAD_OAEP);
165+
}
166+
}
167+
168+
internal static unsafe bool BCryptDecryptPkcs1(
169+
SafeBCryptKeyHandle key,
170+
ReadOnlySpan<byte> source,
171+
Span<byte> destination,
172+
out int bytesWritten)
173+
{
174+
return BCryptDecryptRsa(
175+
key,
176+
source,
177+
destination,
178+
null,
179+
BCryptEncryptFlags.BCRYPT_PAD_PKCS1,
180+
out bytesWritten);
181+
}
182+
183+
internal static unsafe bool BCryptDecryptOaep(
184+
SafeBCryptKeyHandle key,
185+
ReadOnlySpan<byte> source,
186+
Span<byte> destination,
187+
string? hashAlgorithmName,
188+
out int bytesWritten)
189+
{
190+
fixed (char* pHashAlg = hashAlgorithmName)
191+
{
192+
BCRYPT_OAEP_PADDING_INFO paddingInfo = default;
193+
paddingInfo.pszAlgId = (IntPtr)pHashAlg;
194+
195+
return BCryptDecryptRsa(
196+
key,
197+
source,
198+
destination,
199+
&paddingInfo,
200+
BCryptEncryptFlags.BCRYPT_PAD_OAEP,
201+
out bytesWritten);
202+
}
203+
}
204+
}
205+
}

src/libraries/Common/src/Interop/Windows/BCrypt/Interop.BCryptExportKey.cs

Lines changed: 30 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,8 @@
22
// The .NET Foundation licenses this file to you under the MIT license.
33

44
using System;
5-
using System.Diagnostics;
65
using System.Runtime.InteropServices;
6+
using System.Security.Cryptography;
77

88
using Microsoft.Win32.SafeHandles;
99

@@ -12,6 +12,34 @@ internal static partial class Interop
1212
internal static partial class BCrypt
1313
{
1414
[LibraryImport(Libraries.BCrypt, StringMarshalling = StringMarshalling.Utf16)]
15-
internal static partial NTSTATUS BCryptExportKey(SafeBCryptKeyHandle hKey, IntPtr hExportKey, string pszBlobType, byte[]? pbOutput, int cbOutput, out int pcbResult, int dwFlags);
15+
private static partial NTSTATUS BCryptExportKey(
16+
SafeBCryptKeyHandle hKey,
17+
IntPtr hExportKey,
18+
string pszBlobType,
19+
byte[]? pbOutput,
20+
int cbOutput,
21+
out int pcbResult,
22+
int dwFlags);
23+
24+
internal static ArraySegment<byte> BCryptExportKey(SafeBCryptKeyHandle key, string blobType)
25+
{
26+
int numBytesNeeded;
27+
NTSTATUS ntStatus = BCryptExportKey(key, IntPtr.Zero, blobType, null, 0, out numBytesNeeded, 0);
28+
29+
if (ntStatus != NTSTATUS.STATUS_SUCCESS)
30+
{
31+
throw CreateCryptographicException(ntStatus);
32+
}
33+
34+
byte[] rented = CryptoPool.Rent(numBytesNeeded);
35+
ntStatus = BCryptExportKey(key, IntPtr.Zero, blobType, rented, numBytesNeeded, out numBytesNeeded, 0);
36+
37+
if (ntStatus != NTSTATUS.STATUS_SUCCESS)
38+
{
39+
throw CreateCryptographicException(ntStatus);
40+
}
41+
42+
return new ArraySegment<byte>(rented, 0, numBytesNeeded);
43+
}
1644
}
1745
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
using System;
5+
using System.Runtime.InteropServices;
6+
7+
using Microsoft.Win32.SafeHandles;
8+
9+
internal static partial class Interop
10+
{
11+
internal static partial class BCrypt
12+
{
13+
[LibraryImport(Libraries.BCrypt)]
14+
private static unsafe partial NTSTATUS BCryptFinalizeKeyPair(
15+
SafeBCryptKeyHandle hKey,
16+
uint dwFlags);
17+
18+
internal static void BCryptFinalizeKeyPair(SafeBCryptKeyHandle key)
19+
{
20+
NTSTATUS status = BCryptFinalizeKeyPair(key, 0);
21+
22+
if (status != NTSTATUS.STATUS_SUCCESS)
23+
{
24+
throw CreateCryptographicException(status);
25+
}
26+
}
27+
}
28+
}
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
using System.Runtime.InteropServices;
5+
6+
using Microsoft.Win32.SafeHandles;
7+
8+
internal static partial class Interop
9+
{
10+
internal static partial class BCrypt
11+
{
12+
[LibraryImport(Libraries.BCrypt)]
13+
private static unsafe partial NTSTATUS BCryptGenerateKeyPair(
14+
SafeBCryptAlgorithmHandle hAlgorithm,
15+
out SafeBCryptKeyHandle phKey,
16+
int dwLength,
17+
uint dwFlags);
18+
19+
internal static SafeBCryptKeyHandle BCryptGenerateKeyPair(
20+
SafeBCryptAlgorithmHandle hAlgorithm,
21+
int keyLength)
22+
{
23+
NTSTATUS status = BCryptGenerateKeyPair(
24+
hAlgorithm,
25+
out SafeBCryptKeyHandle hKey,
26+
keyLength,
27+
0);
28+
29+
if (status != NTSTATUS.STATUS_SUCCESS)
30+
{
31+
hKey.Dispose();
32+
throw CreateCryptographicException(status);
33+
}
34+
35+
return hKey;
36+
}
37+
}
38+
}

0 commit comments

Comments
 (0)