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 996f80f668780..ff8d6cf8a655c 100644 --- a/src/libraries/System.Security.Cryptography/src/System.Security.Cryptography.csproj +++ b/src/libraries/System.Security.Cryptography/src/System.Security.Cryptography.csproj @@ -1411,12 +1411,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