From fbc22320322b6be09d22b89a0e7fb56f3542758b Mon Sep 17 00:00:00 2001 From: Jeremy Barton Date: Fri, 30 Sep 2022 16:30:51 -0700 Subject: [PATCH] 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. --- .../Windows/BCrypt/BCryptAlgorithmCache.cs | 30 +- .../Common/src/Interop/Windows/BCrypt/Cng.cs | 5 +- .../Interop.BCryptEncryptDecrypt.RSA.cs | 205 +++++++++ .../Windows/BCrypt/Interop.BCryptExportKey.cs | 32 +- .../BCrypt/Interop.BCryptFinalizeKey.cs | 28 ++ .../BCrypt/Interop.BCryptGenerateKeyPair.cs | 38 ++ .../BCrypt/Interop.BCryptGetProperty.cs | 36 +- .../BCrypt/Interop.BCryptImportKeyPair.cs | 52 +++ .../Interop.BCryptOpenAlgorithmProvider.cs | 27 +- .../BCrypt/Interop.BCryptPropertyStrings.cs | 1 + .../Windows/BCrypt/Interop.BCryptSignHash.cs | 77 ++++ .../BCrypt/Interop.BCryptVerifySignature.cs | 88 ++++ .../Interop/Windows/BCrypt/Interop.Blobs.cs | 13 +- .../Windows/BCrypt/Interop.NTSTATUS.cs | 2 + .../Cryptography/RSACng.ImportExport.cs | 424 +++++------------ .../Cryptography/RSACng.SignVerify.cs | 2 +- .../src/System.Security.Cryptography.csproj | 14 +- .../Security/Cryptography/CngHelpers.cs | 191 ++++++++ .../Cryptography/RSA.Create.Windows.cs | 2 +- .../System/Security/Cryptography/RSABCrypt.cs | 433 ++++++++++++++++++ .../Cryptography/RSACng.ImportExport.cs | 2 +- .../X509Pal.Windows.PublicKey.cs | 39 +- 22 files changed, 1381 insertions(+), 360 deletions(-) create mode 100644 src/libraries/Common/src/Interop/Windows/BCrypt/Interop.BCryptEncryptDecrypt.RSA.cs create mode 100644 src/libraries/Common/src/Interop/Windows/BCrypt/Interop.BCryptFinalizeKey.cs create mode 100644 src/libraries/Common/src/Interop/Windows/BCrypt/Interop.BCryptGenerateKeyPair.cs create mode 100644 src/libraries/Common/src/Interop/Windows/BCrypt/Interop.BCryptImportKeyPair.cs create mode 100644 src/libraries/Common/src/Interop/Windows/BCrypt/Interop.BCryptSignHash.cs create mode 100644 src/libraries/Common/src/Interop/Windows/BCrypt/Interop.BCryptVerifySignature.cs create mode 100644 src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/RSABCrypt.cs diff --git a/src/libraries/Common/src/Interop/Windows/BCrypt/BCryptAlgorithmCache.cs b/src/libraries/Common/src/Interop/Windows/BCrypt/BCryptAlgorithmCache.cs index 5de32d75706dc..aec34aeb52347 100644 --- a/src/libraries/Common/src/Interop/Windows/BCrypt/BCryptAlgorithmCache.cs +++ b/src/libraries/Common/src/Interop/Windows/BCrypt/BCryptAlgorithmCache.cs @@ -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; @@ -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))) diff --git a/src/libraries/Common/src/Interop/Windows/BCrypt/Cng.cs b/src/libraries/Common/src/Interop/Windows/BCrypt/Cng.cs index a54507556dd93..cc43b0623369b 100644 --- a/src/libraries/Common/src/Interop/Windows/BCrypt/Cng.cs +++ b/src/libraries/Common/src/Interop/Windows/BCrypt/Cng.cs @@ -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); diff --git a/src/libraries/Common/src/Interop/Windows/BCrypt/Interop.BCryptEncryptDecrypt.RSA.cs b/src/libraries/Common/src/Interop/Windows/BCrypt/Interop.BCryptEncryptDecrypt.RSA.cs new file mode 100644 index 0000000000000..0d28bc9dc2b31 --- /dev/null +++ b/src/libraries/Common/src/Interop/Windows/BCrypt/Interop.BCryptEncryptDecrypt.RSA.cs @@ -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 source, + Span destination, + void* pPaddingInfo, + BCryptEncryptFlags dwFlags) + { + // BCryptEncrypt does not accept null/0, only non-null/0. + Span notNull = stackalloc byte[1]; + scoped ReadOnlySpan 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 source, + Span 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 source, + Span destination) + { + return BCryptEncryptRsa( + key, + source, + destination, + null, + BCryptEncryptFlags.BCRYPT_PAD_PKCS1); + } + + internal static unsafe int BCryptEncryptOaep( + SafeBCryptKeyHandle key, + ReadOnlySpan source, + Span 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 source, + Span destination, + out int bytesWritten) + { + return BCryptDecryptRsa( + key, + source, + destination, + null, + BCryptEncryptFlags.BCRYPT_PAD_PKCS1, + out bytesWritten); + } + + internal static unsafe bool BCryptDecryptOaep( + SafeBCryptKeyHandle key, + ReadOnlySpan source, + Span 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); + } + } + } +} diff --git a/src/libraries/Common/src/Interop/Windows/BCrypt/Interop.BCryptExportKey.cs b/src/libraries/Common/src/Interop/Windows/BCrypt/Interop.BCryptExportKey.cs index 6570c7a79b4ea..3be63fdfe175a 100644 --- a/src/libraries/Common/src/Interop/Windows/BCrypt/Interop.BCryptExportKey.cs +++ b/src/libraries/Common/src/Interop/Windows/BCrypt/Interop.BCryptExportKey.cs @@ -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; @@ -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 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(rented, 0, numBytesNeeded); + } } } diff --git a/src/libraries/Common/src/Interop/Windows/BCrypt/Interop.BCryptFinalizeKey.cs b/src/libraries/Common/src/Interop/Windows/BCrypt/Interop.BCryptFinalizeKey.cs new file mode 100644 index 0000000000000..75c382ff13fdb --- /dev/null +++ b/src/libraries/Common/src/Interop/Windows/BCrypt/Interop.BCryptFinalizeKey.cs @@ -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); + } + } + } +} diff --git a/src/libraries/Common/src/Interop/Windows/BCrypt/Interop.BCryptGenerateKeyPair.cs b/src/libraries/Common/src/Interop/Windows/BCrypt/Interop.BCryptGenerateKeyPair.cs new file mode 100644 index 0000000000000..98a00b6300999 --- /dev/null +++ b/src/libraries/Common/src/Interop/Windows/BCrypt/Interop.BCryptGenerateKeyPair.cs @@ -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; + } + } +} diff --git a/src/libraries/Common/src/Interop/Windows/BCrypt/Interop.BCryptGetProperty.cs b/src/libraries/Common/src/Interop/Windows/BCrypt/Interop.BCryptGetProperty.cs index 2e56b42e381af..0dfd951a5aac8 100644 --- a/src/libraries/Common/src/Interop/Windows/BCrypt/Interop.BCryptGetProperty.cs +++ b/src/libraries/Common/src/Interop/Windows/BCrypt/Interop.BCryptGetProperty.cs @@ -1,9 +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.Runtime.InteropServices; +using System.Security.Cryptography; using Microsoft.Win32.SafeHandles; @@ -12,6 +11,37 @@ internal static partial class Interop internal static partial class BCrypt { [LibraryImport(Libraries.BCrypt, StringMarshalling = StringMarshalling.Utf16)] - internal static unsafe partial NTSTATUS BCryptGetProperty(SafeBCryptHandle hObject, string pszProperty, void* pbOutput, int cbOutput, out int pcbResult, int dwFlags); + internal static unsafe partial NTSTATUS BCryptGetProperty( + SafeBCryptHandle hObject, + string pszProperty, + void* pbOutput, + int cbOutput, + out int pcbResult, + int dwFlags); + + internal static unsafe int BCryptGetDWordProperty(SafeBCryptHandle hObject, string pszProperty) + { + int ret = 0; + + NTSTATUS status = BCryptGetProperty( + hObject, + pszProperty, + &ret, + sizeof(int), + out int written, + 0); + + if (status != NTSTATUS.STATUS_SUCCESS) + { + throw CreateCryptographicException(status); + } + + if (written != sizeof(int)) + { + throw new CryptographicException(); + } + + return ret; + } } } diff --git a/src/libraries/Common/src/Interop/Windows/BCrypt/Interop.BCryptImportKeyPair.cs b/src/libraries/Common/src/Interop/Windows/BCrypt/Interop.BCryptImportKeyPair.cs new file mode 100644 index 0000000000000..dd877b544d6c8 --- /dev/null +++ b/src/libraries/Common/src/Interop/Windows/BCrypt/Interop.BCryptImportKeyPair.cs @@ -0,0 +1,52 @@ +// 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, StringMarshalling = StringMarshalling.Utf16)] + private static unsafe partial NTSTATUS BCryptImportKeyPair( + SafeBCryptAlgorithmHandle hAlgorithm, + IntPtr hImportKey, + string pszBlobType, + out SafeBCryptKeyHandle phKey, + byte* pbInput, + int cbInput, + uint dwFlags); + + internal static unsafe SafeBCryptKeyHandle BCryptImportKeyPair( + SafeBCryptAlgorithmHandle algorithm, + string blobType, + ReadOnlySpan keyBlob) + { + NTSTATUS status; + SafeBCryptKeyHandle key; + + fixed (byte* pBlob = keyBlob) + { + status = BCryptImportKeyPair( + algorithm, + IntPtr.Zero, + blobType, + out key, + pBlob, + keyBlob.Length, + 0); + } + + if (status != NTSTATUS.STATUS_SUCCESS) + { + key.Dispose(); + throw CreateCryptographicException(status); + } + + return key; + } + } +} diff --git a/src/libraries/Common/src/Interop/Windows/BCrypt/Interop.BCryptOpenAlgorithmProvider.cs b/src/libraries/Common/src/Interop/Windows/BCrypt/Interop.BCryptOpenAlgorithmProvider.cs index 8b73a51291854..af8875c039773 100644 --- a/src/libraries/Common/src/Interop/Windows/BCrypt/Interop.BCryptOpenAlgorithmProvider.cs +++ b/src/libraries/Common/src/Interop/Windows/BCrypt/Interop.BCryptOpenAlgorithmProvider.cs @@ -2,7 +2,6 @@ // The .NET Foundation licenses this file to you under the MIT license. using System; -using System.Diagnostics; using System.Runtime.InteropServices; using Microsoft.Win32.SafeHandles; @@ -12,7 +11,31 @@ internal static partial class Interop internal static partial class BCrypt { [LibraryImport(Libraries.BCrypt, StringMarshalling = StringMarshalling.Utf16)] - internal static partial NTSTATUS BCryptOpenAlgorithmProvider(out SafeBCryptAlgorithmHandle phAlgorithm, string pszAlgId, string? pszImplementation, BCryptOpenAlgorithmProviderFlags dwFlags); + internal static partial NTSTATUS BCryptOpenAlgorithmProvider( + out SafeBCryptAlgorithmHandle phAlgorithm, + string pszAlgId, + string? pszImplementation, + BCryptOpenAlgorithmProviderFlags dwFlags); + + internal static SafeBCryptAlgorithmHandle BCryptOpenAlgorithmProvider( + string pszAlgId, + string? pszImplementation = null, + BCryptOpenAlgorithmProviderFlags dwFlags = 0) + { + NTSTATUS status = BCryptOpenAlgorithmProvider( + out SafeBCryptAlgorithmHandle hAlgorithm, + pszAlgId, + pszImplementation, + dwFlags); + + if (status != NTSTATUS.STATUS_SUCCESS) + { + hAlgorithm.Dispose(); + throw CreateCryptographicException(status); + } + + return hAlgorithm; + } [Flags] internal enum BCryptOpenAlgorithmProviderFlags : int diff --git a/src/libraries/Common/src/Interop/Windows/BCrypt/Interop.BCryptPropertyStrings.cs b/src/libraries/Common/src/Interop/Windows/BCrypt/Interop.BCryptPropertyStrings.cs index 3636abdea7efd..8c7b67476a699 100644 --- a/src/libraries/Common/src/Interop/Windows/BCrypt/Interop.BCryptPropertyStrings.cs +++ b/src/libraries/Common/src/Interop/Windows/BCrypt/Interop.BCryptPropertyStrings.cs @@ -11,6 +11,7 @@ internal static class BCryptPropertyStrings internal const string BCRYPT_ECC_PARAMETERS = "ECCParameters"; internal const string BCRYPT_EFFECTIVE_KEY_LENGTH = "EffectiveKeyLength"; internal const string BCRYPT_HASH_LENGTH = "HashDigestLength"; + internal const string BCRYPT_KEY_STRENGTH = "KeyStrength"; internal const string BCRYPT_MESSAGE_BLOCK_LENGTH = "MessageBlockLength"; } } diff --git a/src/libraries/Common/src/Interop/Windows/BCrypt/Interop.BCryptSignHash.cs b/src/libraries/Common/src/Interop/Windows/BCrypt/Interop.BCryptSignHash.cs new file mode 100644 index 0000000000000..4bacf465c5a1d --- /dev/null +++ b/src/libraries/Common/src/Interop/Windows/BCrypt/Interop.BCryptSignHash.cs @@ -0,0 +1,77 @@ +// 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 BCryptSignHash( + SafeBCryptKeyHandle hKey, + void* pPaddingInfo, + byte* pbInput, + int cbInput, + byte* pbOutput, + int cbOutput, + out int pcbResult, + BCryptSignVerifyFlags dwFlags); + + internal static unsafe NTSTATUS BCryptSignHashPkcs1( + SafeBCryptKeyHandle key, + ReadOnlySpan hash, + Span destination, + string hashAlgorithmName, + out int bytesWritten) + { + fixed (char* pHashAlgorithmName = hashAlgorithmName) + fixed (byte* pHash = &MemoryMarshal.GetReference(hash)) + fixed (byte* pDest = &MemoryMarshal.GetReference(destination)) + { + BCRYPT_PKCS1_PADDING_INFO paddingInfo = default; + paddingInfo.pszAlgId = (IntPtr)pHashAlgorithmName; + + return BCryptSignHash( + key, + &paddingInfo, + pHash, + hash.Length, + pDest, + destination.Length, + out bytesWritten, + BCryptSignVerifyFlags.BCRYPT_PAD_PKCS1); + } + } + + internal static unsafe NTSTATUS BCryptSignHashPss( + SafeBCryptKeyHandle key, + ReadOnlySpan hash, + Span destination, + string hashAlgorithmName, + out int bytesWritten) + { + fixed (char* pHashAlgorithmName = hashAlgorithmName) + fixed (byte* pHash = &MemoryMarshal.GetReference(hash)) + fixed (byte* pDest = &MemoryMarshal.GetReference(destination)) + { + BCRYPT_PSS_PADDING_INFO paddingInfo = default; + paddingInfo.pszAlgId = (IntPtr)pHashAlgorithmName; + paddingInfo.cbSalt = hash.Length; + + return BCryptSignHash( + key, + &paddingInfo, + pHash, + hash.Length, + pDest, + destination.Length, + out bytesWritten, + BCryptSignVerifyFlags.BCRYPT_PAD_PSS); + } + } + } +} diff --git a/src/libraries/Common/src/Interop/Windows/BCrypt/Interop.BCryptVerifySignature.cs b/src/libraries/Common/src/Interop/Windows/BCrypt/Interop.BCryptVerifySignature.cs new file mode 100644 index 0000000000000..02e65df958096 --- /dev/null +++ b/src/libraries/Common/src/Interop/Windows/BCrypt/Interop.BCryptVerifySignature.cs @@ -0,0 +1,88 @@ +// 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 BCryptSignVerifyFlags : uint + { + BCRYPT_PAD_PKCS1 = 2, + BCRYPT_PAD_PSS = 8, + } + + [LibraryImport(Libraries.BCrypt)] + private static unsafe partial NTSTATUS BCryptVerifySignature( + SafeBCryptKeyHandle hKey, + void* pPaddingInfo, + byte* pbHash, + int cbHash, + byte* pbSignature, + int cbSignature, + BCryptSignVerifyFlags dwFlags); + + internal static unsafe bool BCryptVerifySignaturePkcs1( + SafeBCryptKeyHandle key, + ReadOnlySpan hash, + ReadOnlySpan signature, + string hashAlgorithmName) + { + NTSTATUS status; + + fixed (char* pHashAlgorithmName = hashAlgorithmName) + fixed (byte* pHash = &MemoryMarshal.GetReference(hash)) + fixed (byte* pSignature = &MemoryMarshal.GetReference(signature)) + { + BCRYPT_PKCS1_PADDING_INFO paddingInfo = default; + paddingInfo.pszAlgId = (IntPtr)pHashAlgorithmName; + + status = BCryptVerifySignature( + key, + &paddingInfo, + pHash, + hash.Length, + pSignature, + signature.Length, + BCryptSignVerifyFlags.BCRYPT_PAD_PKCS1); + } + + return status == NTSTATUS.STATUS_SUCCESS; + } + + internal static unsafe bool BCryptVerifySignaturePss( + SafeBCryptKeyHandle key, + ReadOnlySpan hash, + ReadOnlySpan signature, + string hashAlgorithmName) + { + + NTSTATUS status; + + fixed (char* pHashAlgorithmName = hashAlgorithmName) + fixed (byte* pHash = &MemoryMarshal.GetReference(hash)) + fixed (byte* pSignature = &MemoryMarshal.GetReference(signature)) + { + BCRYPT_PSS_PADDING_INFO paddingInfo = default; + paddingInfo.pszAlgId = (IntPtr)pHashAlgorithmName; + paddingInfo.cbSalt = hash.Length; + + status = BCryptVerifySignature( + key, + &paddingInfo, + pHash, + hash.Length, + pSignature, + signature.Length, + BCryptSignVerifyFlags.BCRYPT_PAD_PSS); + } + + return status == NTSTATUS.STATUS_SUCCESS; + } + } +} diff --git a/src/libraries/Common/src/Interop/Windows/BCrypt/Interop.Blobs.cs b/src/libraries/Common/src/Interop/Windows/BCrypt/Interop.Blobs.cs index 302e2aff8641c..24fc43f96f997 100644 --- a/src/libraries/Common/src/Interop/Windows/BCrypt/Interop.Blobs.cs +++ b/src/libraries/Common/src/Interop/Windows/BCrypt/Interop.Blobs.cs @@ -63,8 +63,17 @@ internal static void EmitBigEndian(byte[] blob, ref int offset, int value) /// internal static byte[] Consume(byte[] blob, ref int offset, int count) { - byte[] value = new byte[count]; - Buffer.BlockCopy(blob, offset, value, 0, count); + byte[] value = new ReadOnlySpan(blob, offset, count).ToArray(); + offset += count; + return value; + } + + /// + /// Peel off the next "count" bytes in blob and return them in a byte array. + /// + internal static byte[] Consume(ReadOnlySpan blob, ref int offset, int count) + { + byte[] value = blob.Slice(offset, count).ToArray(); offset += count; return value; } diff --git a/src/libraries/Common/src/Interop/Windows/BCrypt/Interop.NTSTATUS.cs b/src/libraries/Common/src/Interop/Windows/BCrypt/Interop.NTSTATUS.cs index 051f21938984b..060aa3e517f08 100644 --- a/src/libraries/Common/src/Interop/Windows/BCrypt/Interop.NTSTATUS.cs +++ b/src/libraries/Common/src/Interop/Windows/BCrypt/Interop.NTSTATUS.cs @@ -8,9 +8,11 @@ internal static partial class BCrypt internal enum NTSTATUS : uint { STATUS_SUCCESS = 0x0, + STATUS_UNSUCCESSFUL = 0xC0000001, STATUS_NOT_FOUND = 0xc0000225, STATUS_INVALID_PARAMETER = 0xc000000d, STATUS_NO_MEMORY = 0xc0000017, + STATUS_BUFFER_TOO_SMALL = 0xC0000023, STATUS_AUTH_TAG_MISMATCH = 0xc000a002, } } diff --git a/src/libraries/Common/src/System/Security/Cryptography/RSACng.ImportExport.cs b/src/libraries/Common/src/System/Security/Cryptography/RSACng.ImportExport.cs index 928bf654c9336..2d0129412d9fb 100644 --- a/src/libraries/Common/src/System/Security/Cryptography/RSACng.ImportExport.cs +++ b/src/libraries/Common/src/System/Security/Cryptography/RSACng.ImportExport.cs @@ -1,25 +1,19 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -using System.Diagnostics; -using Internal.Cryptography; using Internal.NativeCrypto; -using ErrorCode = Interop.NCrypt.ErrorCode; -using KeyBlobMagicNumber = Interop.BCrypt.KeyBlobMagicNumber; -using BCRYPT_RSAKEY_BLOB = Interop.BCrypt.BCRYPT_RSAKEY_BLOB; - namespace System.Security.Cryptography { public sealed partial class RSACng : RSA { /// - /// - /// ImportParameters will replace the existing key that RSACng is working with by creating a - /// new CngKey for the parameters structure. If the parameters structure contains only an - /// exponent and modulus, then only a public key will be imported. If the parameters also - /// contain P and Q values, then a full key pair will be imported. - /// + /// + /// ImportParameters will replace the existing key that RSACng is working with by creating a + /// new CngKey for the parameters structure. If the parameters structure contains only an + /// exponent and modulus, then only a public key will be imported. If the parameters also + /// contain P and Q values, then a full key pair will be imported. + /// /// /// /// if contains neither an exponent nor a modulus. @@ -30,336 +24,166 @@ public sealed partial class RSACng : RSA /// public override unsafe void ImportParameters(RSAParameters parameters) { - if (parameters.Exponent == null || parameters.Modulus == null) - throw new CryptographicException(SR.Cryptography_InvalidRsaParameters); + ArraySegment keyBlob = parameters.ToBCryptBlob(); - bool includePrivate; - if (parameters.D == null) + try { - includePrivate = false; - - if (parameters.P != null || - parameters.DP != null || - parameters.Q != null || - parameters.DQ != null || - parameters.InverseQ != null) - { - throw new CryptographicException(SR.Cryptography_InvalidRsaParameters); - } - } - else - { - includePrivate = true; - - if (parameters.P == null || - parameters.DP == null || - parameters.Q == null || - parameters.DQ == null || - parameters.InverseQ == null) - { - throw new CryptographicException(SR.Cryptography_InvalidRsaParameters); - } - - // Half, rounded up. - int halfModulusLength = (parameters.Modulus.Length + 1) / 2; - - // The same checks are done by RSACryptoServiceProvider on import (when building the key blob) - // Historically RSACng let CNG handle this (reporting NTE_NOT_SUPPORTED), but on RS1 CNG let the - // import succeed, then on private key use (e.g. signing) it would report NTE_INVALID_PARAMETER. - // - // Doing the check here prevents the state in RS1 where the Import succeeds, but corrupts the key, - // and makes for a friendlier exception message. - if (parameters.D.Length != parameters.Modulus.Length || - parameters.P.Length != halfModulusLength || - parameters.Q.Length != halfModulusLength || - parameters.DP.Length != halfModulusLength || - parameters.DQ.Length != halfModulusLength || - parameters.InverseQ.Length != halfModulusLength) - { - throw new CryptographicException(SR.Cryptography_InvalidRsaParameters); - } + ImportKeyBlob(keyBlob, parameters.D != null); } - - // - // We need to build a key blob structured as follows: - // - // BCRYPT_RSAKEY_BLOB header - // byte[cbPublicExp] publicExponent - Exponent - // byte[cbModulus] modulus - Modulus - // -- Only if "includePrivate" is true -- - // byte[cbPrime1] prime1 - P - // byte[cbPrime2] prime2 - Q - // ------------------ - // - - int blobSize = sizeof(BCRYPT_RSAKEY_BLOB) + - parameters.Exponent.Length + - parameters.Modulus.Length; - if (includePrivate) + finally { - blobSize += parameters.P!.Length + - parameters.Q!.Length; + CryptoPool.Return(keyBlob); } + } - byte[] rsaBlob = new byte[blobSize]; - fixed (byte* pRsaBlob = &rsaBlob[0]) - { - // Build the header - BCRYPT_RSAKEY_BLOB* pBcryptBlob = (BCRYPT_RSAKEY_BLOB*)pRsaBlob; - pBcryptBlob->Magic = includePrivate ? KeyBlobMagicNumber.BCRYPT_RSAPRIVATE_MAGIC : KeyBlobMagicNumber.BCRYPT_RSAPUBLIC_MAGIC; - pBcryptBlob->BitLength = parameters.Modulus.Length * 8; - pBcryptBlob->cbPublicExp = parameters.Exponent.Length; - pBcryptBlob->cbModulus = parameters.Modulus.Length; - - if (includePrivate) - { - pBcryptBlob->cbPrime1 = parameters.P!.Length; - pBcryptBlob->cbPrime2 = parameters.Q!.Length; - } + public override void ImportPkcs8PrivateKey(ReadOnlySpan source, out int bytesRead) + { + ThrowIfDisposed(); - int offset = sizeof(BCRYPT_RSAKEY_BLOB); + CngPkcs8.Pkcs8Response response = CngPkcs8.ImportPkcs8PrivateKey(source, out int localRead); - Interop.BCrypt.Emit(rsaBlob, ref offset, parameters.Exponent); - Interop.BCrypt.Emit(rsaBlob, ref offset, parameters.Modulus); + ProcessPkcs8Response(response); + bytesRead = localRead; + } - if (includePrivate) - { - Interop.BCrypt.Emit(rsaBlob, ref offset, parameters.P!); - Interop.BCrypt.Emit(rsaBlob, ref offset, parameters.Q!); - } + public override void ImportEncryptedPkcs8PrivateKey( + ReadOnlySpan passwordBytes, + ReadOnlySpan source, + out int bytesRead) + { + ThrowIfDisposed(); - // We better have computed the right allocation size above! - Debug.Assert(offset == blobSize, "offset == blobSize"); - } + CngPkcs8.Pkcs8Response response = CngPkcs8.ImportEncryptedPkcs8PrivateKey( + passwordBytes, + source, + out int localRead); - ImportKeyBlob(rsaBlob, includePrivate); + ProcessPkcs8Response(response); + bytesRead = localRead; } - public override void ImportPkcs8PrivateKey(ReadOnlySpan source, out int bytesRead) - { - ThrowIfDisposed(); + public override void ImportEncryptedPkcs8PrivateKey( + ReadOnlySpan password, + ReadOnlySpan source, + out int bytesRead) + { + ThrowIfDisposed(); - CngPkcs8.Pkcs8Response response = CngPkcs8.ImportPkcs8PrivateKey(source, out int localRead); + CngPkcs8.Pkcs8Response response = CngPkcs8.ImportEncryptedPkcs8PrivateKey( + password, + source, + out int localRead); - ProcessPkcs8Response(response); - bytesRead = localRead; - } + ProcessPkcs8Response(response); + bytesRead = localRead; + } - public override void ImportEncryptedPkcs8PrivateKey( - ReadOnlySpan passwordBytes, - ReadOnlySpan source, - out int bytesRead) + private void ProcessPkcs8Response(CngPkcs8.Pkcs8Response response) + { + // Wrong algorithm? + if (response.GetAlgorithmGroup() != BCryptNative.AlgorithmName.RSA) { - ThrowIfDisposed(); - - CngPkcs8.Pkcs8Response response = CngPkcs8.ImportEncryptedPkcs8PrivateKey( - passwordBytes, - source, - out int localRead); - - ProcessPkcs8Response(response); - bytesRead = localRead; + response.FreeKey(); + throw new CryptographicException(SR.Cryptography_NotValidPublicOrPrivateKey); } - public override void ImportEncryptedPkcs8PrivateKey( - ReadOnlySpan password, - ReadOnlySpan source, - out int bytesRead) - { - ThrowIfDisposed(); + AcceptImport(response); + } - CngPkcs8.Pkcs8Response response = CngPkcs8.ImportEncryptedPkcs8PrivateKey( - password, - source, - out int localRead); + public override byte[] ExportEncryptedPkcs8PrivateKey( + ReadOnlySpan passwordBytes, + PbeParameters pbeParameters) + { + ArgumentNullException.ThrowIfNull(pbeParameters); - ProcessPkcs8Response(response); - bytesRead = localRead; - } + return CngPkcs8.ExportEncryptedPkcs8PrivateKey( + this, + passwordBytes, + pbeParameters); + } - private void ProcessPkcs8Response(CngPkcs8.Pkcs8Response response) - { - // Wrong algorithm? - if (response.GetAlgorithmGroup() != BCryptNative.AlgorithmName.RSA) - { - response.FreeKey(); - throw new CryptographicException(SR.Cryptography_NotValidPublicOrPrivateKey); - } + public override byte[] ExportEncryptedPkcs8PrivateKey( + ReadOnlySpan password, + PbeParameters pbeParameters) + { + ArgumentNullException.ThrowIfNull(pbeParameters); - AcceptImport(response); - } + PasswordBasedEncryption.ValidatePbeParameters( + pbeParameters, + password, + ReadOnlySpan.Empty); - public override byte[] ExportEncryptedPkcs8PrivateKey( - ReadOnlySpan passwordBytes, - PbeParameters pbeParameters) + if (CngPkcs8.IsPlatformScheme(pbeParameters)) { - ArgumentNullException.ThrowIfNull(pbeParameters); - - return CngPkcs8.ExportEncryptedPkcs8PrivateKey( - this, - passwordBytes, - pbeParameters); + return ExportEncryptedPkcs8(password, pbeParameters.IterationCount); } - public override byte[] ExportEncryptedPkcs8PrivateKey( - ReadOnlySpan password, - PbeParameters pbeParameters) - { - ArgumentNullException.ThrowIfNull(pbeParameters); - - PasswordBasedEncryption.ValidatePbeParameters( - pbeParameters, - password, - ReadOnlySpan.Empty); - - if (CngPkcs8.IsPlatformScheme(pbeParameters)) - { - return ExportEncryptedPkcs8(password, pbeParameters.IterationCount); - } + return CngPkcs8.ExportEncryptedPkcs8PrivateKey( + this, + password, + pbeParameters); + } - return CngPkcs8.ExportEncryptedPkcs8PrivateKey( - this, - password, - pbeParameters); - } + public override bool TryExportEncryptedPkcs8PrivateKey( + ReadOnlySpan passwordBytes, + PbeParameters pbeParameters, + Span destination, + out int bytesWritten) + { + ArgumentNullException.ThrowIfNull(pbeParameters); + + PasswordBasedEncryption.ValidatePbeParameters( + pbeParameters, + ReadOnlySpan.Empty, + passwordBytes); + + return CngPkcs8.TryExportEncryptedPkcs8PrivateKey( + this, + passwordBytes, + pbeParameters, + destination, + out bytesWritten); + } - public override bool TryExportEncryptedPkcs8PrivateKey( - ReadOnlySpan passwordBytes, - PbeParameters pbeParameters, - Span destination, - out int bytesWritten) - { - ArgumentNullException.ThrowIfNull(pbeParameters); + public override bool TryExportEncryptedPkcs8PrivateKey( + ReadOnlySpan password, + PbeParameters pbeParameters, + Span destination, + out int bytesWritten) + { + ArgumentNullException.ThrowIfNull(pbeParameters); - PasswordBasedEncryption.ValidatePbeParameters( - pbeParameters, - ReadOnlySpan.Empty, - passwordBytes); + PasswordBasedEncryption.ValidatePbeParameters( + pbeParameters, + password, + ReadOnlySpan.Empty); - return CngPkcs8.TryExportEncryptedPkcs8PrivateKey( - this, - passwordBytes, - pbeParameters, - destination, - out bytesWritten); - } - - public override bool TryExportEncryptedPkcs8PrivateKey( - ReadOnlySpan password, - PbeParameters pbeParameters, - Span destination, - out int bytesWritten) + if (CngPkcs8.IsPlatformScheme(pbeParameters)) { - ArgumentNullException.ThrowIfNull(pbeParameters); - - PasswordBasedEncryption.ValidatePbeParameters( - pbeParameters, - password, - ReadOnlySpan.Empty); - - if (CngPkcs8.IsPlatformScheme(pbeParameters)) - { - return TryExportEncryptedPkcs8( - password, - pbeParameters.IterationCount, - destination, - out bytesWritten); - } - - return CngPkcs8.TryExportEncryptedPkcs8PrivateKey( - this, + return TryExportEncryptedPkcs8( password, - pbeParameters, + pbeParameters.IterationCount, destination, out bytesWritten); } - /// - /// Exports the key used by the RSA object into an RSAParameters object. - /// - public override RSAParameters ExportParameters(bool includePrivateParameters) - { - byte[] rsaBlob = ExportKeyBlob(includePrivateParameters); - RSAParameters rsaParams = default; - ExportParameters(ref rsaParams, rsaBlob, includePrivateParameters); - return rsaParams; - } - - private static void ExportParameters(ref RSAParameters rsaParams, byte[] rsaBlob, bool includePrivateParameters) - { - // - // We now have a buffer laid out as follows: - // BCRYPT_RSAKEY_BLOB header - // byte[cbPublicExp] publicExponent - Exponent - // byte[cbModulus] modulus - Modulus - // -- Private only -- - // byte[cbPrime1] prime1 - P - // byte[cbPrime2] prime2 - Q - // byte[cbPrime1] exponent1 - DP - // byte[cbPrime2] exponent2 - DQ - // byte[cbPrime1] coefficient - InverseQ - // byte[cbModulus] privateExponent - D - // - KeyBlobMagicNumber magic = (KeyBlobMagicNumber)BitConverter.ToInt32(rsaBlob, 0); - - // Check the magic value in the key blob header. If the blob does not have the required magic, - // then throw a CryptographicException. - CheckMagicValueOfKey(magic, includePrivateParameters); - - unsafe - { - // Fail-fast if a rogue provider gave us a blob that isn't even the size of the blob header. - if (rsaBlob.Length < sizeof(BCRYPT_RSAKEY_BLOB)) - throw ErrorCode.E_FAIL.ToCryptographicException(); - - fixed (byte* pRsaBlob = &rsaBlob[0]) - { - BCRYPT_RSAKEY_BLOB* pBcryptBlob = (BCRYPT_RSAKEY_BLOB*)pRsaBlob; - - int offset = sizeof(BCRYPT_RSAKEY_BLOB); - - // Read out the exponent - rsaParams.Exponent = Interop.BCrypt.Consume(rsaBlob, ref offset, pBcryptBlob->cbPublicExp); - rsaParams.Modulus = Interop.BCrypt.Consume(rsaBlob, ref offset, pBcryptBlob->cbModulus); - - if (includePrivateParameters) - { - rsaParams.P = Interop.BCrypt.Consume(rsaBlob, ref offset, pBcryptBlob->cbPrime1); - rsaParams.Q = Interop.BCrypt.Consume(rsaBlob, ref offset, pBcryptBlob->cbPrime2); - rsaParams.DP = Interop.BCrypt.Consume(rsaBlob, ref offset, pBcryptBlob->cbPrime1); - rsaParams.DQ = Interop.BCrypt.Consume(rsaBlob, ref offset, pBcryptBlob->cbPrime2); - rsaParams.InverseQ = Interop.BCrypt.Consume(rsaBlob, ref offset, pBcryptBlob->cbPrime1); - rsaParams.D = Interop.BCrypt.Consume(rsaBlob, ref offset, pBcryptBlob->cbModulus); - } - } - } + return CngPkcs8.TryExportEncryptedPkcs8PrivateKey( + this, + password, + pbeParameters, + destination, + out bytesWritten); } /// - /// This function checks the magic value in the key blob header + /// Exports the key used by the RSA object into an RSAParameters object. /// - /// The expected magic number. - /// Private blob if true else public key blob - private static void CheckMagicValueOfKey(KeyBlobMagicNumber magic, bool includePrivateParameters) + public override RSAParameters ExportParameters(bool includePrivateParameters) { - if (includePrivateParameters) - { - if (magic != KeyBlobMagicNumber.BCRYPT_RSAPRIVATE_MAGIC && magic != KeyBlobMagicNumber.BCRYPT_RSAFULLPRIVATE_MAGIC) - { - throw new CryptographicException(SR.Cryptography_NotValidPrivateKey); - } - } - else - { - if (magic != KeyBlobMagicNumber.BCRYPT_RSAPUBLIC_MAGIC) - { - // Private key magic is permissible too since the public key can be derived from the private key blob. - if (magic != KeyBlobMagicNumber.BCRYPT_RSAPRIVATE_MAGIC && magic != KeyBlobMagicNumber.BCRYPT_RSAFULLPRIVATE_MAGIC) - { - throw new CryptographicException(SR.Cryptography_NotValidPublicOrPrivateKey); - } - } - } + byte[] rsaBlob = ExportKeyBlob(includePrivateParameters); + RSAParameters rsaParams = default; + rsaParams.FromBCryptBlob(rsaBlob, includePrivateParameters); + return rsaParams; } } } diff --git a/src/libraries/Common/src/System/Security/Cryptography/RSACng.SignVerify.cs b/src/libraries/Common/src/System/Security/Cryptography/RSACng.SignVerify.cs index c8b5e63d26c5d..2be5277088176 100644 --- a/src/libraries/Common/src/System/Security/Cryptography/RSACng.SignVerify.cs +++ b/src/libraries/Common/src/System/Security/Cryptography/RSACng.SignVerify.cs @@ -24,7 +24,7 @@ public sealed partial class RSACng : RSA KeyValuePair.Create(HashAlgorithmName.SHA512, 512 / 8), }); - private static int GetHashSizeInBytes(HashAlgorithmName hashAlgorithm) + internal static int GetHashSizeInBytes(HashAlgorithmName hashAlgorithm) { return s_hashSizes.GetOrAdd( hashAlgorithm, diff --git a/src/libraries/System.Security.Cryptography/src/System.Security.Cryptography.csproj b/src/libraries/System.Security.Cryptography/src/System.Security.Cryptography.csproj index ed68d33a835db..0840f45a3ae61 100644 --- a/src/libraries/System.Security.Cryptography/src/System.Security.Cryptography.csproj +++ b/src/libraries/System.Security.Cryptography/src/System.Security.Cryptography.csproj @@ -1412,12 +1412,18 @@ Link="Common\Interop\Windows\BCrypt\Interop.BCryptDuplicateHash.cs" /> + + + + + + + - diff --git a/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/CngHelpers.cs b/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/CngHelpers.cs index 59db73d98f073..80ef8bd134196 100644 --- a/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/CngHelpers.cs +++ b/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/CngHelpers.cs @@ -3,12 +3,15 @@ using System.Diagnostics; using System.IO; +using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Text; using Internal.Cryptography; using Microsoft.Win32.SafeHandles; +using BCRYPT_RSAKEY_BLOB = Interop.BCrypt.BCRYPT_RSAKEY_BLOB; using ErrorCode = Interop.NCrypt.ErrorCode; +using KeyBlobMagicNumber = Interop.BCrypt.KeyBlobMagicNumber; namespace System.Security.Cryptography { @@ -244,5 +247,193 @@ internal static byte[] GetSymmetricKeyDataIfExportable(this CngKey cngKey, strin } } } + + internal static unsafe ArraySegment ToBCryptBlob(this in RSAParameters parameters) + { + if (parameters.Exponent == null || parameters.Modulus == null) + throw new CryptographicException(SR.Cryptography_InvalidRsaParameters); + + bool includePrivate; + if (parameters.D == null) + { + includePrivate = false; + + if (parameters.P != null || + parameters.DP != null || + parameters.Q != null || + parameters.DQ != null || + parameters.InverseQ != null) + { + throw new CryptographicException(SR.Cryptography_InvalidRsaParameters); + } + } + else + { + includePrivate = true; + + if (parameters.P == null || + parameters.DP == null || + parameters.Q == null || + parameters.DQ == null || + parameters.InverseQ == null) + { + throw new CryptographicException(SR.Cryptography_InvalidRsaParameters); + } + + // Half, rounded up. + int halfModulusLength = (parameters.Modulus.Length + 1) / 2; + + // The same checks are done by RSACryptoServiceProvider on import (when building the key blob) + // Historically RSACng let CNG handle this (reporting NTE_NOT_SUPPORTED), but on RS1 CNG let the + // import succeed, then on private key use (e.g. signing) it would report NTE_INVALID_PARAMETER. + // + // Doing the check here prevents the state in RS1 where the Import succeeds, but corrupts the key, + // and makes for a friendlier exception message. + if (parameters.D.Length != parameters.Modulus.Length || + parameters.P.Length != halfModulusLength || + parameters.Q.Length != halfModulusLength || + parameters.DP.Length != halfModulusLength || + parameters.DQ.Length != halfModulusLength || + parameters.InverseQ.Length != halfModulusLength) + { + throw new CryptographicException(SR.Cryptography_InvalidRsaParameters); + } + } + + // + // We need to build a key blob structured as follows: + // + // BCRYPT_RSAKEY_BLOB header + // byte[cbPublicExp] publicExponent - Exponent + // byte[cbModulus] modulus - Modulus + // -- Only if "includePrivate" is true -- + // byte[cbPrime1] prime1 - P + // byte[cbPrime2] prime2 - Q + // ------------------ + // + + int blobSize = sizeof(BCRYPT_RSAKEY_BLOB) + + parameters.Exponent.Length + + parameters.Modulus.Length; + if (includePrivate) + { + blobSize += parameters.P!.Length + + parameters.Q!.Length; + } + + byte[] rsaBlob = CryptoPool.Rent(blobSize); + + fixed (byte* pRsaBlob = &rsaBlob[0]) + { + // Build the header + BCRYPT_RSAKEY_BLOB* pBcryptBlob = (BCRYPT_RSAKEY_BLOB*)pRsaBlob; + pBcryptBlob->Magic = includePrivate ? KeyBlobMagicNumber.BCRYPT_RSAPRIVATE_MAGIC : KeyBlobMagicNumber.BCRYPT_RSAPUBLIC_MAGIC; + pBcryptBlob->BitLength = parameters.Modulus.Length * 8; + pBcryptBlob->cbPublicExp = parameters.Exponent.Length; + pBcryptBlob->cbModulus = parameters.Modulus.Length; + + if (includePrivate) + { + pBcryptBlob->cbPrime1 = parameters.P!.Length; + pBcryptBlob->cbPrime2 = parameters.Q!.Length; + } + else + { + pBcryptBlob->cbPrime1 = pBcryptBlob->cbPrime2 = 0; + } + + int offset = sizeof(BCRYPT_RSAKEY_BLOB); + + Interop.BCrypt.Emit(rsaBlob, ref offset, parameters.Exponent); + Interop.BCrypt.Emit(rsaBlob, ref offset, parameters.Modulus); + + if (includePrivate) + { + Interop.BCrypt.Emit(rsaBlob, ref offset, parameters.P!); + Interop.BCrypt.Emit(rsaBlob, ref offset, parameters.Q!); + } + + // We better have computed the right allocation size above! + Debug.Assert(offset == blobSize, "offset == blobSize"); + } + + return new ArraySegment(rsaBlob, 0, blobSize); + } + + internal static void FromBCryptBlob( + this ref RSAParameters rsaParams, + ReadOnlySpan rsaBlob, + bool includePrivateParameters) + { + // + // We now have a buffer laid out as follows: + // BCRYPT_RSAKEY_BLOB header + // byte[cbPublicExp] publicExponent - Exponent + // byte[cbModulus] modulus - Modulus + // -- Private only -- + // byte[cbPrime1] prime1 - P + // byte[cbPrime2] prime2 - Q + // byte[cbPrime1] exponent1 - DP + // byte[cbPrime2] exponent2 - DQ + // byte[cbPrime1] coefficient - InverseQ + // byte[cbModulus] privateExponent - D + // + + unsafe + { + // Fail-fast if a rogue provider gave us a blob that isn't even the size of the blob header. + if (rsaBlob.Length < sizeof(BCRYPT_RSAKEY_BLOB)) + throw ErrorCode.E_FAIL.ToCryptographicException(); + + fixed (byte* pRsaBlob = &rsaBlob[0]) + { + KeyBlobMagicNumber magic = (KeyBlobMagicNumber)Unsafe.ReadUnaligned(pRsaBlob); + + // Check the magic value in the key blob header. If the blob does not have the required magic, + // then throw a CryptographicException. + CheckMagicValueOfKey(magic, includePrivateParameters); + + BCRYPT_RSAKEY_BLOB* pBcryptBlob = (BCRYPT_RSAKEY_BLOB*)pRsaBlob; + + int offset = sizeof(BCRYPT_RSAKEY_BLOB); + + // Read out the exponent + rsaParams.Exponent = Interop.BCrypt.Consume(rsaBlob, ref offset, pBcryptBlob->cbPublicExp); + rsaParams.Modulus = Interop.BCrypt.Consume(rsaBlob, ref offset, pBcryptBlob->cbModulus); + + if (includePrivateParameters) + { + rsaParams.P = Interop.BCrypt.Consume(rsaBlob, ref offset, pBcryptBlob->cbPrime1); + rsaParams.Q = Interop.BCrypt.Consume(rsaBlob, ref offset, pBcryptBlob->cbPrime2); + rsaParams.DP = Interop.BCrypt.Consume(rsaBlob, ref offset, pBcryptBlob->cbPrime1); + rsaParams.DQ = Interop.BCrypt.Consume(rsaBlob, ref offset, pBcryptBlob->cbPrime2); + rsaParams.InverseQ = Interop.BCrypt.Consume(rsaBlob, ref offset, pBcryptBlob->cbPrime1); + rsaParams.D = Interop.BCrypt.Consume(rsaBlob, ref offset, pBcryptBlob->cbModulus); + } + } + } + + static void CheckMagicValueOfKey(KeyBlobMagicNumber magic, bool includePrivateParameters) + { + if (includePrivateParameters) + { + if (magic != KeyBlobMagicNumber.BCRYPT_RSAPRIVATE_MAGIC && magic != KeyBlobMagicNumber.BCRYPT_RSAFULLPRIVATE_MAGIC) + { + throw new CryptographicException(SR.Cryptography_NotValidPrivateKey); + } + } + else + { + if (magic != KeyBlobMagicNumber.BCRYPT_RSAPUBLIC_MAGIC) + { + // Private key magic is permissible too since the public key can be derived from the private key blob. + if (magic != KeyBlobMagicNumber.BCRYPT_RSAPRIVATE_MAGIC && magic != KeyBlobMagicNumber.BCRYPT_RSAFULLPRIVATE_MAGIC) + { + throw new CryptographicException(SR.Cryptography_NotValidPublicOrPrivateKey); + } + } + } + } + } } } diff --git a/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/RSA.Create.Windows.cs b/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/RSA.Create.Windows.cs index 1871e87f87c66..33fc8f1813d82 100644 --- a/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/RSA.Create.Windows.cs +++ b/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/RSA.Create.Windows.cs @@ -7,7 +7,7 @@ public partial class RSA : AsymmetricAlgorithm { public static new partial RSA Create() { - return new RSAWrapper(new RSACng()); + return new RSABCrypt(); } } } diff --git a/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/RSABCrypt.cs b/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/RSABCrypt.cs new file mode 100644 index 0000000000000..b3297e9e56a8d --- /dev/null +++ b/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/RSABCrypt.cs @@ -0,0 +1,433 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Diagnostics; +using System.Threading; +using Internal.NativeCrypto; +using Microsoft.Win32.SafeHandles; + +namespace System.Security.Cryptography +{ + internal sealed class RSABCrypt : RSA + { + private static readonly SafeBCryptAlgorithmHandle s_algHandle = + Interop.BCrypt.BCryptOpenAlgorithmProvider(BCryptNative.AlgorithmName.RSA); + + // See https://msdn.microsoft.com/en-us/library/windows/desktop/bb931354(v=vs.85).aspx + // All values are in bits. + private static readonly KeySizes s_keySizes = + new KeySizes(minSize: 512, maxSize: 16384, skipSize: 64); + + private SafeBCryptKeyHandle? _key; + private int _lastKeySize; + + internal RSABCrypt() + { + KeySizeValue = 2048; + } + + protected override void Dispose(bool disposing) + { + if (disposing) + { + _key?.Dispose(); + } + + _lastKeySize = -1; + } + + private SafeBCryptKeyHandle GetKey() + { + int keySize = KeySize; + + // Since _lastKeySize also tracks the disposal state, we can do the equals check first. + if (_lastKeySize == keySize) + { + Debug.Assert(_key != null); + return _key; + } + + ThrowIfDisposed(); + + SafeBCryptKeyHandle newKey = Interop.BCrypt.BCryptGenerateKeyPair(s_algHandle, keySize); + Interop.BCrypt.BCryptFinalizeKeyPair(newKey); + SetKey(newKey); + return newKey; + } + + private void SetKey(SafeBCryptKeyHandle newKey) + { + Debug.Assert(!newKey.IsInvalid); + + int keySize = Interop.BCrypt.BCryptGetDWordProperty( + newKey, + Interop.BCrypt.BCryptPropertyStrings.BCRYPT_KEY_STRENGTH); + + SafeBCryptKeyHandle? oldKey = Interlocked.Exchange(ref _key, newKey); + ForceSetKeySize(keySize); + oldKey?.Dispose(); + } + + public override RSAParameters ExportParameters(bool includePrivateParameters) + { + SafeBCryptKeyHandle key = GetKey(); + + ArraySegment keyBlob = Interop.BCrypt.BCryptExportKey( + key, + includePrivateParameters ? + Interop.BCrypt.KeyBlobType.BCRYPT_RSAFULLPRIVATE_BLOB : + Interop.BCrypt.KeyBlobType.BCRYPT_RSAPUBLIC_KEY_BLOB); + + RSAParameters ret = default; + ret.FromBCryptBlob(keyBlob, includePrivateParameters); + + // FromBCryptBlob isn't expected to have any failures since it's reading + // data directly from BCryptExportKey, so we don't need to bother with + // a try/finally. + CryptoPool.Return(keyBlob); + + return ret; + } + + public override void ImportParameters(RSAParameters parameters) + { + ThrowIfDisposed(); + + ArraySegment keyBlob = parameters.ToBCryptBlob(); + SafeBCryptKeyHandle newKey; + + try + { + newKey = Interop.BCrypt.BCryptImportKeyPair( + s_algHandle, + parameters.D != null ? + Interop.BCrypt.KeyBlobType.BCRYPT_RSAPRIVATE_BLOB : + Interop.BCrypt.KeyBlobType.BCRYPT_RSAPUBLIC_KEY_BLOB, + keyBlob); + } + finally + { + // Return (and clear) the BCryptBlob array even if the parameters + // are invalid and the import fails/throws (e.g. P*Q != Modulus). + CryptoPool.Return(keyBlob); + } + + SetKey(newKey); + } + + public override byte[] Encrypt(byte[] data, RSAEncryptionPadding padding) + { + ArgumentNullException.ThrowIfNull(data); + ArgumentNullException.ThrowIfNull(padding); + + byte[] ret = new byte[AsymmetricAlgorithmHelpers.BitsToBytes(KeySize)]; + int written = Encrypt(new ReadOnlySpan(data), ret.AsSpan(), padding); + + VerifyWritten(ret, written); + return ret; + } + + public override byte[] Decrypt(byte[] data, RSAEncryptionPadding padding) + { + ArgumentNullException.ThrowIfNull(data); + ArgumentNullException.ThrowIfNull(padding); + + return Decrypt(new ReadOnlySpan(data), padding); + } + + public override byte[] SignHash( + byte[] hash, + HashAlgorithmName hashAlgorithm, + RSASignaturePadding padding) + { + ArgumentNullException.ThrowIfNull(hash); + ArgumentException.ThrowIfNullOrEmpty(hashAlgorithm.Name, nameof(hashAlgorithm)); + ArgumentNullException.ThrowIfNull(padding); + + byte[] ret = new byte[AsymmetricAlgorithmHelpers.BitsToBytes(KeySize)]; + + int written = SignHash( + new ReadOnlySpan(hash), + ret.AsSpan(), + hashAlgorithm, + padding); + + VerifyWritten(ret, written); + return ret; + } + + public override bool VerifyHash( + byte[] hash, + byte[] signature, + HashAlgorithmName hashAlgorithm, + RSASignaturePadding padding) + { + ArgumentNullException.ThrowIfNull(hash); + ArgumentNullException.ThrowIfNull(signature); + ArgumentException.ThrowIfNullOrEmpty(hashAlgorithm.Name, nameof(hashAlgorithm)); + ArgumentNullException.ThrowIfNull(padding); + + return VerifyHash( + new ReadOnlySpan(hash), + new ReadOnlySpan(signature), + hashAlgorithm, + padding); + } + + public override bool TryDecrypt( + ReadOnlySpan data, + Span destination, + RSAEncryptionPadding padding, + out int bytesWritten) + { + ArgumentNullException.ThrowIfNull(padding); + + SafeBCryptKeyHandle key = GetKey(); + int modulusSizeInBytes = RsaPaddingProcessor.BytesRequiredForBitCount(KeySize); + + if (data.Length != modulusSizeInBytes) + { + throw new CryptographicException(SR.Cryptography_RSA_DecryptWrongSize); + } + + switch (padding.Mode) + { + case RSAEncryptionPaddingMode.Pkcs1: + return Interop.BCrypt.BCryptDecryptPkcs1(key, data, destination, out bytesWritten); + case RSAEncryptionPaddingMode.Oaep: + return Interop.BCrypt.BCryptDecryptOaep( + key, + data, + destination, + padding.OaepHashAlgorithm.Name, + out bytesWritten); + } + + throw new CryptographicException(SR.Cryptography_UnsupportedPaddingMode); + } + + public override bool TryEncrypt( + ReadOnlySpan data, + Span destination, + RSAEncryptionPadding padding, + out int bytesWritten) + { + ArgumentNullException.ThrowIfNull(padding); + + SafeBCryptKeyHandle key = GetKey(); + int modulusSizeInBytes = RsaPaddingProcessor.BytesRequiredForBitCount(KeySize); + + if (destination.Length < modulusSizeInBytes) + { + bytesWritten = 0; + return false; + } + + const int Pkcs1PaddingOverhead = 11; + + switch (padding.Mode) + { + case RSAEncryptionPaddingMode.Pkcs1: + if (modulusSizeInBytes - Pkcs1PaddingOverhead < data.Length) + { + throw new CryptographicException( + SR.Format( + SR.Cryptography_Encryption_MessageTooLong, + modulusSizeInBytes - Pkcs1PaddingOverhead)); + } + + bytesWritten = Interop.BCrypt.BCryptEncryptPkcs1(key, data, destination); + return true; + case RSAEncryptionPaddingMode.Oaep: + bytesWritten = Interop.BCrypt.BCryptEncryptOaep( + key, + data, + destination, + padding.OaepHashAlgorithm.Name); + + return true; + } + + throw new CryptographicException(SR.Cryptography_UnsupportedPaddingMode); + } + + public override bool TrySignHash( + ReadOnlySpan hash, + Span destination, + HashAlgorithmName hashAlgorithm, + RSASignaturePadding padding, + out int bytesWritten) + { + string? hashAlgorithmName = hashAlgorithm.Name; + ArgumentException.ThrowIfNullOrEmpty(hashAlgorithmName, nameof(hashAlgorithm)); + ArgumentNullException.ThrowIfNull(padding); + + SafeBCryptKeyHandle key = GetKey(); + + if (hash.Length != RSACng.GetHashSizeInBytes(hashAlgorithm)) + { + throw new CryptographicException(SR.Cryptography_SignHash_WrongSize); + } + + Interop.BCrypt.NTSTATUS status; + int written; + + switch (padding.Mode) + { + case RSASignaturePaddingMode.Pkcs1: + status = Interop.BCrypt.BCryptSignHashPkcs1( + key, + hash, + destination, + hashAlgorithmName, + out written); + + break; + case RSASignaturePaddingMode.Pss: + status = Interop.BCrypt.BCryptSignHashPss( + key, + hash, + destination, + hashAlgorithmName, + out written); + + break; + default: + throw new CryptographicException(SR.Cryptography_UnsupportedPaddingMode); + } + + if (status == Interop.BCrypt.NTSTATUS.STATUS_SUCCESS) + { + bytesWritten = written; + return true; + } + + if (status == Interop.BCrypt.NTSTATUS.STATUS_BUFFER_TOO_SMALL) + { + bytesWritten = 0; + return false; + } + + throw Interop.BCrypt.CreateCryptographicException(status); + } + + public override bool VerifyHash( + ReadOnlySpan hash, + ReadOnlySpan signature, + HashAlgorithmName hashAlgorithm, + RSASignaturePadding padding) + { + string? hashAlgorithmName = hashAlgorithm.Name; + ArgumentException.ThrowIfNullOrEmpty(hashAlgorithmName, nameof(hashAlgorithm)); + ArgumentNullException.ThrowIfNull(padding); + + SafeBCryptKeyHandle key = GetKey(); + + if (hash.Length != RSACng.GetHashSizeInBytes(hashAlgorithm)) + { + return false; + } + + switch (padding.Mode) + { + case RSASignaturePaddingMode.Pkcs1: + return Interop.BCrypt.BCryptVerifySignaturePkcs1( + key, + hash, + signature, + hashAlgorithmName); + case RSASignaturePaddingMode.Pss: + return Interop.BCrypt.BCryptVerifySignaturePss( + key, + hash, + signature, + hashAlgorithmName); + default: + throw new CryptographicException(SR.Cryptography_UnsupportedPaddingMode); + } + } + + public override KeySizes[] LegalKeySizes => new KeySizes[] { s_keySizes }; + + public override unsafe void ImportEncryptedPkcs8PrivateKey( + ReadOnlySpan passwordBytes, + ReadOnlySpan source, + out int bytesRead) + { + ThrowIfDisposed(); + base.ImportEncryptedPkcs8PrivateKey(passwordBytes, source, out bytesRead); + } + + public override unsafe void ImportEncryptedPkcs8PrivateKey( + ReadOnlySpan password, + ReadOnlySpan source, + out int bytesRead) + { + ThrowIfDisposed(); + base.ImportEncryptedPkcs8PrivateKey(password, source, out bytesRead); + } + + public override unsafe void ImportPkcs8PrivateKey(ReadOnlySpan source, out int bytesRead) + { + ThrowIfDisposed(); + base.ImportPkcs8PrivateKey(source, out bytesRead); + } + + public override void ImportRSAPrivateKey(ReadOnlySpan source, out int bytesRead) + { + ThrowIfDisposed(); + base.ImportRSAPrivateKey(source, out bytesRead); + } + + public override void ImportRSAPublicKey(ReadOnlySpan source, out int bytesRead) + { + ThrowIfDisposed(); + base.ImportRSAPublicKey(source, out bytesRead); + } + + public override void ImportSubjectPublicKeyInfo(ReadOnlySpan source, out int bytesRead) + { + ThrowIfDisposed(); + base.ImportSubjectPublicKeyInfo(source, out bytesRead); + } + + private void ForceSetKeySize(int newKeySize) + { + // Our LegalKeySizes value stores the values that we encoded as being the correct + // legal key size limitations for this algorithm, as documented on MSDN. + // + // But on a new OS version we might not question if our limit is accurate, or MSDN + // could have been inaccurate to start with. + // + // Since the key is already loaded, we know that Windows thought it to be valid; + // therefore we should set KeySizeValue directly to bypass the LegalKeySizes conformance + // check. + // + // For RSA there are known cases where this change matters. RSACryptoServiceProvider can + // create a 384-bit RSA key, which we consider too small to be legal. It can also create + // a 1032-bit RSA key, which we consider illegal because it doesn't match our 64-bit + // alignment requirement. (In both cases Windows loads it just fine) + KeySizeValue = newKeySize; + _lastKeySize = newKeySize; + } + + private static void VerifyWritten(byte[] array, int written) + { + if (array.Length != written) + { + Debug.Fail( + $"An array-filling operation wrote {written} when {array.Length} was expected."); + + throw new CryptographicException(); + } + } + + private void ThrowIfDisposed() + { + if (_lastKeySize < 0) + { + throw new ObjectDisposedException(nameof(RSA)); + } + } + } +} diff --git a/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/RSACng.ImportExport.cs b/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/RSACng.ImportExport.cs index b656c41fa82b1..dcc2f9742990c 100644 --- a/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/RSACng.ImportExport.cs +++ b/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/RSACng.ImportExport.cs @@ -15,7 +15,7 @@ public sealed partial class RSACng : RSA private static readonly CngKeyBlobFormat s_rsaPublicBlob = new CngKeyBlobFormat(Interop.BCrypt.KeyBlobType.BCRYPT_RSAPUBLIC_KEY_BLOB); - private void ImportKeyBlob(byte[] rsaBlob, bool includePrivate) + private void ImportKeyBlob(ReadOnlySpan rsaBlob, bool includePrivate) { CngKeyBlobFormat blobFormat = includePrivate ? s_rsaPrivateBlob : s_rsaPublicBlob; diff --git a/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/X509Certificates/X509Pal.Windows.PublicKey.cs b/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/X509Certificates/X509Pal.Windows.PublicKey.cs index 4e7c58e98a98c..525b31b1f1176 100644 --- a/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/X509Certificates/X509Pal.Windows.PublicKey.cs +++ b/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/X509Certificates/X509Pal.Windows.PublicKey.cs @@ -56,9 +56,9 @@ public AsymmetricAlgorithm DecodePublicKey(Oid oid, byte[] encodedKeyValue, byte case AlgId.CALG_RSA_KEYX: case AlgId.CALG_RSA_SIGN: { - byte[] keyBlob = DecodeKeyBlob(CryptDecodeObjectStructType.CNG_RSA_PUBLIC_KEY_BLOB, encodedKeyValue); - CngKey cngKey = CngKey.Import(keyBlob, CngKeyBlobFormat.GenericPublicBlob); - return new RSACng(cngKey, transferOwnership: true); + RSA rsa = new RSABCrypt(); + rsa.ImportRSAPublicKey(encodedKeyValue, out _); + return rsa; } case AlgId.CALG_DSS_SIGN: { @@ -84,7 +84,6 @@ private static TAlgorithm DecodeECPublicKey( using (SafeBCryptKeyHandle bCryptKeyHandle = ImportPublicKeyInfo(certContext, importFlags)) { CngKeyBlobFormat blobFormat; - byte[] keyBlob; string? curveName = GetCurveName(bCryptKeyHandle); if (curveName == null) @@ -98,15 +97,24 @@ private static TAlgorithm DecodeECPublicKey( blobFormat = CngKeyBlobFormat.EccPublicBlob; } - keyBlob = ExportKeyBlob(bCryptKeyHandle, blobFormat); - key = factory(CngKey.Import(keyBlob, blobFormat)); + ArraySegment keyBlob = ExportKeyBlob(bCryptKeyHandle, blobFormat); + + try + { + key = factory(CngKey.Import(keyBlob, blobFormat)); + } + finally + { + CryptoPool.Return(keyBlob); + } } else { blobFormat = CngKeyBlobFormat.EccPublicBlob; - keyBlob = ExportKeyBlob(bCryptKeyHandle, blobFormat); + ArraySegment keyBlob = ExportKeyBlob(bCryptKeyHandle, blobFormat); ECParameters ecparams = default; ExportNamedCurveParameters(ref ecparams, keyBlob, false); + CryptoPool.Return(keyBlob); ecparams.Curve = ECCurve.CreateFromFriendlyName(curveName); key = new TAlgorithm(); key.ImportParameters(ecparams); @@ -146,25 +154,14 @@ private static SafeBCryptKeyHandle ImportPublicKeyInfo(SafeCertContextHandle cer } } - private static byte[] ExportKeyBlob(SafeBCryptKeyHandle bCryptKeyHandle, CngKeyBlobFormat blobFormat) + private static ArraySegment ExportKeyBlob(SafeBCryptKeyHandle bCryptKeyHandle, CngKeyBlobFormat blobFormat) { string blobFormatString = blobFormat.Format; - int numBytesNeeded; - NTSTATUS ntStatus = Interop.BCrypt.BCryptExportKey(bCryptKeyHandle, IntPtr.Zero, blobFormatString, null, 0, out numBytesNeeded, 0); - if (ntStatus != NTSTATUS.STATUS_SUCCESS) - throw new CryptographicException(Interop.Kernel32.GetMessage((int)ntStatus)); - - byte[] keyBlob = new byte[numBytesNeeded]; - ntStatus = Interop.BCrypt.BCryptExportKey(bCryptKeyHandle, IntPtr.Zero, blobFormatString, keyBlob, keyBlob.Length, out numBytesNeeded, 0); - if (ntStatus != NTSTATUS.STATUS_SUCCESS) - throw new CryptographicException(Interop.Kernel32.GetMessage((int)ntStatus)); - - Array.Resize(ref keyBlob, numBytesNeeded); - return keyBlob; + return Interop.BCrypt.BCryptExportKey(bCryptKeyHandle, blobFormatString); } - private static void ExportNamedCurveParameters(ref ECParameters ecParams, byte[] ecBlob, bool includePrivateParameters) + private static void ExportNamedCurveParameters(ref ECParameters ecParams, ReadOnlySpan ecBlob, bool includePrivateParameters) { // We now have a buffer laid out as follows: // BCRYPT_ECCKEY_BLOB header