Use BCrypt for ephemeral RSA on Windows
authorJeremy Barton <jbarton@microsoft.com>
Fri, 30 Sep 2022 23:30:51 +0000 (16:30 -0700)
committerGitHub <noreply@github.com>
Fri, 30 Sep 2022 23:30:51 +0000 (16:30 -0700)
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.

22 files changed:
src/libraries/Common/src/Interop/Windows/BCrypt/BCryptAlgorithmCache.cs
src/libraries/Common/src/Interop/Windows/BCrypt/Cng.cs
src/libraries/Common/src/Interop/Windows/BCrypt/Interop.BCryptEncryptDecrypt.RSA.cs [new file with mode: 0644]
src/libraries/Common/src/Interop/Windows/BCrypt/Interop.BCryptExportKey.cs
src/libraries/Common/src/Interop/Windows/BCrypt/Interop.BCryptFinalizeKey.cs [new file with mode: 0644]
src/libraries/Common/src/Interop/Windows/BCrypt/Interop.BCryptGenerateKeyPair.cs [new file with mode: 0644]
src/libraries/Common/src/Interop/Windows/BCrypt/Interop.BCryptGetProperty.cs
src/libraries/Common/src/Interop/Windows/BCrypt/Interop.BCryptImportKeyPair.cs [new file with mode: 0644]
src/libraries/Common/src/Interop/Windows/BCrypt/Interop.BCryptOpenAlgorithmProvider.cs
src/libraries/Common/src/Interop/Windows/BCrypt/Interop.BCryptPropertyStrings.cs
src/libraries/Common/src/Interop/Windows/BCrypt/Interop.BCryptSignHash.cs [new file with mode: 0644]
src/libraries/Common/src/Interop/Windows/BCrypt/Interop.BCryptVerifySignature.cs [new file with mode: 0644]
src/libraries/Common/src/Interop/Windows/BCrypt/Interop.Blobs.cs
src/libraries/Common/src/Interop/Windows/BCrypt/Interop.NTSTATUS.cs
src/libraries/Common/src/System/Security/Cryptography/RSACng.ImportExport.cs
src/libraries/Common/src/System/Security/Cryptography/RSACng.SignVerify.cs
src/libraries/System.Security.Cryptography/src/System.Security.Cryptography.csproj
src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/CngHelpers.cs
src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/RSA.Create.Windows.cs
src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/RSABCrypt.cs [new file with mode: 0644]
src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/RSACng.ImportExport.cs
src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/X509Certificates/X509Pal.Windows.PublicKey.cs

index 5de32d7..aec34ae 100644 (file)
@@ -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 @@ internal static partial class Interop
                         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)))
index a545075..cc43b06 100644 (file)
@@ -70,7 +70,10 @@ namespace Internal.NativeCrypto
         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 (file)
index 0000000..0d28bc9
--- /dev/null
@@ -0,0 +1,205 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System;
+using System.Runtime.InteropServices;
+
+using Microsoft.Win32.SafeHandles;
+
+internal static partial class Interop
+{
+    internal static partial class BCrypt
+    {
+        [Flags]
+        private enum BCryptEncryptFlags : uint
+        {
+            BCRYPT_PAD_PKCS1 = 2,
+            BCRYPT_PAD_OAEP = 4,
+        }
+
+        [LibraryImport(Libraries.BCrypt)]
+        private static unsafe partial NTSTATUS BCryptEncrypt(
+            SafeBCryptKeyHandle hKey,
+            byte* pbInput,
+            int cbInput,
+            void* paddingInfo,
+            byte* pbIV,
+            int cbIV,
+            byte* pbOutput,
+            int cbOutput,
+            out int cbResult,
+            BCryptEncryptFlags dwFlags);
+
+        [LibraryImport(Libraries.BCrypt)]
+        private static unsafe partial NTSTATUS BCryptDecrypt(
+            SafeBCryptKeyHandle hKey,
+            byte* pbInput,
+            int cbInput,
+            void* paddingInfo,
+            byte* pbIV,
+            int cbIV,
+            byte* pbOutput,
+            int cbOutput,
+            out int cbResult,
+            BCryptEncryptFlags dwFlags);
+
+        private static unsafe int BCryptEncryptRsa(
+            SafeBCryptKeyHandle key,
+            ReadOnlySpan<byte> source,
+            Span<byte> destination,
+            void* pPaddingInfo,
+            BCryptEncryptFlags dwFlags)
+        {
+            // BCryptEncrypt does not accept null/0, only non-null/0.
+            Span<byte> notNull = stackalloc byte[1];
+            scoped ReadOnlySpan<byte> effectiveSource;
+
+            if (source.IsEmpty)
+            {
+                effectiveSource = notNull.Slice(1);
+            }
+            else
+            {
+                effectiveSource = source;
+            }
+
+            NTSTATUS status;
+            int written;
+
+            fixed (byte* pSource = &MemoryMarshal.GetReference(effectiveSource))
+            fixed (byte* pDest = &MemoryMarshal.GetReference(destination))
+            {
+                status = BCryptEncrypt(
+                    key,
+                    pSource,
+                    source.Length,
+                    pPaddingInfo,
+                    null,
+                    0,
+                    pDest,
+                    destination.Length,
+                    out written,
+                    dwFlags);
+            }
+
+            if (status != NTSTATUS.STATUS_SUCCESS)
+            {
+                throw CreateCryptographicException(status);
+            }
+
+            return written;
+        }
+
+        private static unsafe bool BCryptDecryptRsa(
+            SafeBCryptKeyHandle key,
+            ReadOnlySpan<byte> source,
+            Span<byte> destination,
+            void* pPaddingInfo,
+            BCryptEncryptFlags dwFlags,
+            out int bytesWritten)
+        {
+            NTSTATUS status;
+            int written;
+
+            fixed (byte* pSource = &MemoryMarshal.GetReference(source))
+            fixed (byte* pDest = &MemoryMarshal.GetReference(destination))
+            {
+                status = BCryptDecrypt(
+                    key,
+                    pSource,
+                    source.Length,
+                    pPaddingInfo,
+                    null,
+                    0,
+                    pDest,
+                    destination.Length,
+                    out written,
+                    dwFlags);
+            }
+
+            if (status == NTSTATUS.STATUS_SUCCESS)
+            {
+                bytesWritten = written;
+                return true;
+            }
+
+            if (status == NTSTATUS.STATUS_BUFFER_TOO_SMALL)
+            {
+                bytesWritten = 0;
+                return false;
+            }
+
+            throw CreateCryptographicException(status);
+        }
+
+        internal static unsafe int BCryptEncryptPkcs1(
+            SafeBCryptKeyHandle key,
+            ReadOnlySpan<byte> source,
+            Span<byte> destination)
+        {
+            return BCryptEncryptRsa(
+                key,
+                source,
+                destination,
+                null,
+                BCryptEncryptFlags.BCRYPT_PAD_PKCS1);
+        }
+
+        internal static unsafe int BCryptEncryptOaep(
+            SafeBCryptKeyHandle key,
+            ReadOnlySpan<byte> source,
+            Span<byte> destination,
+            string? hashAlgorithmName)
+        {
+            fixed (char* pHashAlg = hashAlgorithmName)
+            {
+                BCRYPT_OAEP_PADDING_INFO paddingInfo = default;
+                paddingInfo.pszAlgId = (IntPtr)pHashAlg;
+
+                return BCryptEncryptRsa(
+                    key,
+                    source,
+                    destination,
+                    &paddingInfo,
+                    BCryptEncryptFlags.BCRYPT_PAD_OAEP);
+            }
+        }
+
+        internal static unsafe bool BCryptDecryptPkcs1(
+            SafeBCryptKeyHandle key,
+            ReadOnlySpan<byte> source,
+            Span<byte> destination,
+            out int bytesWritten)
+        {
+            return BCryptDecryptRsa(
+                key,
+                source,
+                destination,
+                null,
+                BCryptEncryptFlags.BCRYPT_PAD_PKCS1,
+                out bytesWritten);
+        }
+
+        internal static unsafe bool BCryptDecryptOaep(
+            SafeBCryptKeyHandle key,
+            ReadOnlySpan<byte> source,
+            Span<byte> destination,
+            string? hashAlgorithmName,
+            out int bytesWritten)
+        {
+            fixed (char* pHashAlg = hashAlgorithmName)
+            {
+                BCRYPT_OAEP_PADDING_INFO paddingInfo = default;
+                paddingInfo.pszAlgId = (IntPtr)pHashAlg;
+
+                return BCryptDecryptRsa(
+                    key,
+                    source,
+                    destination,
+                    &paddingInfo,
+                    BCryptEncryptFlags.BCRYPT_PAD_OAEP,
+                    out bytesWritten);
+            }
+        }
+    }
+}
index 6570c7a..3be63fd 100644 (file)
@@ -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<byte> BCryptExportKey(SafeBCryptKeyHandle key, string blobType)
+        {
+            int numBytesNeeded;
+            NTSTATUS ntStatus = BCryptExportKey(key, IntPtr.Zero, blobType, null, 0, out numBytesNeeded, 0);
+
+            if (ntStatus != NTSTATUS.STATUS_SUCCESS)
+            {
+                throw CreateCryptographicException(ntStatus);
+            }
+
+            byte[] rented = CryptoPool.Rent(numBytesNeeded);
+            ntStatus = BCryptExportKey(key, IntPtr.Zero, blobType, rented, numBytesNeeded, out numBytesNeeded, 0);
+
+            if (ntStatus != NTSTATUS.STATUS_SUCCESS)
+            {
+                throw CreateCryptographicException(ntStatus);
+            }
+
+            return new ArraySegment<byte>(rented, 0, numBytesNeeded);
+        }
     }
 }
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 (file)
index 0000000..75c382f
--- /dev/null
@@ -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 (file)
index 0000000..98a00b6
--- /dev/null
@@ -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;
+        }
+    }
+}
index 2e56b42..0dfd951 100644 (file)
@@ -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 (file)
index 0000000..dd877b5
--- /dev/null
@@ -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<byte> 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;
+        }
+    }
+}
index 8b73a51..af8875c 100644 (file)
@@ -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
index 3636abd..8c7b674 100644 (file)
@@ -11,6 +11,7 @@ internal static partial class Interop
             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 (file)
index 0000000..4bacf46
--- /dev/null
@@ -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<byte> hash,
+            Span<byte> 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<byte> hash,
+            Span<byte> 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 (file)
index 0000000..02e65df
--- /dev/null
@@ -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<byte> hash,
+            ReadOnlySpan<byte> 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<byte> hash,
+            ReadOnlySpan<byte> 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;
+        }
+    }
+}
index 302e2af..24fc43f 100644 (file)
@@ -63,8 +63,17 @@ internal static partial class Interop
         /// </summary>
         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<byte>(blob, offset, count).ToArray();
+            offset += count;
+            return value;
+        }
+
+        /// <summary>
+        ///     Peel off the next "count" bytes in blob and return them in a byte array.
+        /// </summary>
+        internal static byte[] Consume(ReadOnlySpan<byte> blob, ref int offset, int count)
+        {
+            byte[] value = blob.Slice(offset, count).ToArray();
             offset += count;
             return value;
         }
index 051f219..060aa3e 100644 (file)
@@ -8,9 +8,11 @@ internal static partial class Interop
         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,
         }
     }
index 928bf65..2d01294 100644 (file)
@@ -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
     {
         /// <summary>
-        ///     <para>
-        ///         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.
-        ///     </para>
+        ///   <para>
+        ///     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.
+        ///   </para>
         /// </summary>
         /// <exception cref="ArgumentException">
         ///     if <paramref name="parameters" /> contains neither an exponent nor a modulus.
@@ -30,336 +24,166 @@ namespace System.Security.Cryptography
         /// </exception>
         public override unsafe void ImportParameters(RSAParameters parameters)
         {
-            if (parameters.Exponent == null || parameters.Modulus == null)
-                throw new CryptographicException(SR.Cryptography_InvalidRsaParameters);
+            ArraySegment<byte> 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<byte> 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<byte> passwordBytes,
+            ReadOnlySpan<byte> 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<byte> source, out int bytesRead)
-            {
-                ThrowIfDisposed();
+        public override void ImportEncryptedPkcs8PrivateKey(
+            ReadOnlySpan<char> password,
+            ReadOnlySpan<byte> 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<byte> passwordBytes,
-                ReadOnlySpan<byte> 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<char> password,
-                ReadOnlySpan<byte> source,
-                out int bytesRead)
-            {
-                ThrowIfDisposed();
+            AcceptImport(response);
+        }
 
-                CngPkcs8.Pkcs8Response response = CngPkcs8.ImportEncryptedPkcs8PrivateKey(
-                    password,
-                    source,
-                    out int localRead);
+        public override byte[] ExportEncryptedPkcs8PrivateKey(
+            ReadOnlySpan<byte> 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<char> password,
+            PbeParameters pbeParameters)
+        {
+            ArgumentNullException.ThrowIfNull(pbeParameters);
 
-                AcceptImport(response);
-            }
+            PasswordBasedEncryption.ValidatePbeParameters(
+                pbeParameters,
+                password,
+                ReadOnlySpan<byte>.Empty);
 
-            public override byte[] ExportEncryptedPkcs8PrivateKey(
-                ReadOnlySpan<byte> 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<char> password,
-                PbeParameters pbeParameters)
-            {
-                ArgumentNullException.ThrowIfNull(pbeParameters);
-
-                PasswordBasedEncryption.ValidatePbeParameters(
-                    pbeParameters,
-                    password,
-                    ReadOnlySpan<byte>.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<byte> passwordBytes,
+            PbeParameters pbeParameters,
+            Span<byte> destination,
+            out int bytesWritten)
+        {
+            ArgumentNullException.ThrowIfNull(pbeParameters);
+
+            PasswordBasedEncryption.ValidatePbeParameters(
+                pbeParameters,
+                ReadOnlySpan<char>.Empty,
+                passwordBytes);
+
+            return CngPkcs8.TryExportEncryptedPkcs8PrivateKey(
+                this,
+                passwordBytes,
+                pbeParameters,
+                destination,
+                out bytesWritten);
+        }
 
-            public override bool TryExportEncryptedPkcs8PrivateKey(
-                ReadOnlySpan<byte> passwordBytes,
-                PbeParameters pbeParameters,
-                Span<byte> destination,
-                out int bytesWritten)
-            {
-                ArgumentNullException.ThrowIfNull(pbeParameters);
+        public override bool TryExportEncryptedPkcs8PrivateKey(
+            ReadOnlySpan<char> password,
+            PbeParameters pbeParameters,
+            Span<byte> destination,
+            out int bytesWritten)
+        {
+            ArgumentNullException.ThrowIfNull(pbeParameters);
 
-                PasswordBasedEncryption.ValidatePbeParameters(
-                    pbeParameters,
-                    ReadOnlySpan<char>.Empty,
-                    passwordBytes);
+            PasswordBasedEncryption.ValidatePbeParameters(
+                pbeParameters,
+                password,
+                ReadOnlySpan<byte>.Empty);
 
-                return CngPkcs8.TryExportEncryptedPkcs8PrivateKey(
-                    this,
-                    passwordBytes,
-                    pbeParameters,
-                    destination,
-                    out bytesWritten);
-            }
-
-            public override bool TryExportEncryptedPkcs8PrivateKey(
-                ReadOnlySpan<char> password,
-                PbeParameters pbeParameters,
-                Span<byte> destination,
-                out int bytesWritten)
+            if (CngPkcs8.IsPlatformScheme(pbeParameters))
             {
-                ArgumentNullException.ThrowIfNull(pbeParameters);
-
-                PasswordBasedEncryption.ValidatePbeParameters(
-                    pbeParameters,
-                    password,
-                    ReadOnlySpan<byte>.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);
             }
 
-            /// <summary>
-            ///     Exports the key used by the RSA object into an RSAParameters object.
-            /// </summary>
-            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);
         }
 
         /// <summary>
-        ///     This function checks the magic value in the key blob header
+        ///     Exports the key used by the RSA object into an RSAParameters object.
         /// </summary>
-        /// <param name="magic">The expected magic number.</param>
-        /// <param name="includePrivateParameters">Private blob if true else public key blob</param>
-        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;
         }
     }
 }
index c8b5e63..2be5277 100644 (file)
@@ -24,7 +24,7 @@ namespace System.Security.Cryptography
                     KeyValuePair.Create(HashAlgorithmName.SHA512, 512 / 8),
                 });
 
-        private static int GetHashSizeInBytes(HashAlgorithmName hashAlgorithm)
+        internal static int GetHashSizeInBytes(HashAlgorithmName hashAlgorithm)
         {
             return s_hashSizes.GetOrAdd(
                 hashAlgorithm,
index ed68d33..0840f45 100644 (file)
              Link="Common\Interop\Windows\BCrypt\Interop.BCryptDuplicateHash.cs" />
     <Compile Include="$(CommonPath)\Interop\Windows\BCrypt\Interop.BCryptEncryptDecrypt.cs"
              Link="Common\Interop\Windows\BCrypt\Interop.BCryptEncryptDecrypt.cs" />
+    <Compile Include="$(CommonPath)\Interop\Windows\BCrypt\Interop.BCryptEncryptDecrypt.RSA.cs"
+             Link="Common\Interop\Windows\BCrypt\Interop.BCryptEncryptDecrypt.RSA.cs" />
     <Compile Include="$(CommonPath)Interop\Windows\BCrypt\Interop.BCryptExportKey.cs"
              Link="Common\Interop\Windows\BCrypt\Interop.BCryptExportKey.cs" />
+    <Compile Include="$(CommonPath)Interop\Windows\BCrypt\Interop.BCryptFinalizeKey.cs"
+             Link="Common\Interop\Windows\BCrypt\Interop.BCryptFinalizeKey.cs" />
     <Compile Include="$(CommonPath)Interop\Windows\BCrypt\Interop.BCryptFinishHash.cs"
              Link="Common\Interop\Windows\BCrypt\Interop.BCryptFinishHash.cs" />
     <Compile Include="$(CommonPath)Interop\Windows\BCrypt\Interop.BCryptGenRandom.cs"
              Link="Common\Interop\Windows\BCrypt\Interop.BCryptGenRandom.cs" />
+    <Compile Include="$(CommonPath)Interop\Windows\BCrypt\Interop.BCryptGenerateKeyPair.cs"
+             Link="Common\Interop\Windows\BCrypt\Interop.BCryptGenerateKeyPair.cs" />
     <Compile Include="$(CommonPath)Interop\Windows\BCrypt\Interop.BCryptGenerateSymmetricKey.cs"
              Link="Common\Interop\Windows\BCrypt\Interop.BCryptGenerateSymmetricKey.cs" />
     <Compile Include="$(CommonPath)Interop\Windows\BCrypt\Interop.BCryptGetProperty.cs"
              Link="Common\Interop\Windows\BCrypt\Interop.BCryptHashData.cs" />
     <Compile Include="$(CommonPath)\Interop\Windows\BCrypt\Interop.BCryptImportKey.cs"
              Link="Common\Interop\Windows\BCrypt\Interop.BCryptImportKey.cs" />
+    <Compile Include="$(CommonPath)\Interop\Windows\BCrypt\Interop.BCryptImportKeyPair.cs"
+             Link="Common\Interop\Windows\BCrypt\Interop.BCryptImportKeyPair.cs" />
     <Compile Include="$(CommonPath)Interop\Windows\BCrypt\Interop.BCryptKeyDerivation.cs"
              Link="Common\Interop\Windows\BCrypt\Interop.BCryptKeyDerivation.cs" />
     <Compile Include="$(CommonPath)Interop\Windows\BCrypt\Interop.BCryptOpenAlgorithmProvider.cs"
              Link="Common\Interop\Windows\BCrypt\Interop.BCryptOpenAlgorithmProvider.cs" />
     <Compile Include="$(CommonPath)Interop\Windows\BCrypt\Interop.BCryptPropertyStrings.cs"
              Link="Common\Interop\Windows\BCrypt\Interop.BCryptPropertyStrings.cs" />
+    <Compile Include="$(CommonPath)Interop\Windows\BCrypt\Interop.BCryptSignHash.cs"
+             Link="Common\Interop\Windows\BCrypt\Interop.BCryptSignHash.cs" />
+    <Compile Include="$(CommonPath)Interop\Windows\BCrypt\Interop.BCryptVerifySignature.cs"
+             Link="Common\Interop\Windows\BCrypt\Interop.BCryptVerifySignature.cs" />
     <Compile Include="$(CommonPath)Interop\Windows\BCrypt\Interop.Blobs.cs"
              Link="Common\Interop\Windows\BCrypt\Interop.Blobs.cs" />
     <Compile Include="$(CommonPath)Interop\Windows\BCrypt\Interop.CreateCryptographicException.cs"
     <Compile Include="System\Security\Cryptography\RC2CryptoServiceProvider.Windows.cs" />
     <Compile Include="System\Security\Cryptography\RC2Implementation.Windows.cs" />
     <Compile Include="System\Security\Cryptography\RSA.Create.Windows.cs" />
+    <Compile Include="System\Security\Cryptography\RSABCrypt.cs" />
     <Compile Include="System\Security\Cryptography\RSACryptoServiceProvider.Windows.cs" />
     <Compile Include="System\Security\Cryptography\RSACng.cs" />
     <Compile Include="System\Security\Cryptography\RSACng.ImportExport.cs" />
     <Compile Include="System\Security\Cryptography\RSACng.Key.cs" />
-    <Compile Include="System\Security\Cryptography\RSAWrapper.cs" />
     <Compile Include="System\Security\Cryptography\TripleDESCng.Windows.cs" />
     <Compile Include="System\Security\Cryptography\TripleDESCryptoServiceProvider.Wrap.cs" />
     <Compile Include="System\Security\Cryptography\TripleDesImplementation.Windows.cs" />
index 59db73d..80ef8bd 100644 (file)
@@ -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 @@ namespace System.Security.Cryptography
                 }
             }
         }
+
+        internal static unsafe ArraySegment<byte> 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<byte>(rsaBlob, 0, blobSize);
+        }
+
+        internal static void FromBCryptBlob(
+            this ref RSAParameters rsaParams,
+            ReadOnlySpan<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
+            //
+
+            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<int>(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);
+                        }
+                    }
+                }
+            }
+        }
     }
 }
index 1871e87..33fc8f1 100644 (file)
@@ -7,7 +7,7 @@ namespace System.Security.Cryptography
     {
         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 (file)
index 0000000..b3297e9
--- /dev/null
@@ -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<byte> 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<byte> 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<byte>(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<byte>(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<byte>(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<byte>(hash),
+                new ReadOnlySpan<byte>(signature),
+                hashAlgorithm,
+                padding);
+        }
+
+        public override bool TryDecrypt(
+            ReadOnlySpan<byte> data,
+            Span<byte> 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<byte> data,
+            Span<byte> 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<byte> hash,
+            Span<byte> 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<byte> hash,
+            ReadOnlySpan<byte> 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<byte> passwordBytes,
+            ReadOnlySpan<byte> source,
+            out int bytesRead)
+        {
+            ThrowIfDisposed();
+            base.ImportEncryptedPkcs8PrivateKey(passwordBytes, source, out bytesRead);
+        }
+
+        public override unsafe void ImportEncryptedPkcs8PrivateKey(
+            ReadOnlySpan<char> password,
+            ReadOnlySpan<byte> source,
+            out int bytesRead)
+        {
+            ThrowIfDisposed();
+            base.ImportEncryptedPkcs8PrivateKey(password, source, out bytesRead);
+        }
+
+        public override unsafe void ImportPkcs8PrivateKey(ReadOnlySpan<byte> source, out int bytesRead)
+        {
+            ThrowIfDisposed();
+            base.ImportPkcs8PrivateKey(source, out bytesRead);
+        }
+
+        public override void ImportRSAPrivateKey(ReadOnlySpan<byte> source, out int bytesRead)
+        {
+            ThrowIfDisposed();
+            base.ImportRSAPrivateKey(source, out bytesRead);
+        }
+
+        public override void ImportRSAPublicKey(ReadOnlySpan<byte> source, out int bytesRead)
+        {
+            ThrowIfDisposed();
+            base.ImportRSAPublicKey(source, out bytesRead);
+        }
+
+        public override void ImportSubjectPublicKeyInfo(ReadOnlySpan<byte> 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));
+            }
+        }
+    }
+}
index b656c41..dcc2f97 100644 (file)
@@ -15,7 +15,7 @@ namespace System.Security.Cryptography
         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<byte> rsaBlob, bool includePrivate)
         {
             CngKeyBlobFormat blobFormat = includePrivate ? s_rsaPrivateBlob : s_rsaPublicBlob;
 
index 4e7c58e..525b31b 100644 (file)
@@ -56,9 +56,9 @@ namespace System.Security.Cryptography.X509Certificates
                 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 @@ namespace System.Security.Cryptography.X509Certificates
             using (SafeBCryptKeyHandle bCryptKeyHandle = ImportPublicKeyInfo(certContext, importFlags))
             {
                 CngKeyBlobFormat blobFormat;
-                byte[] keyBlob;
                 string? curveName = GetCurveName(bCryptKeyHandle);
 
                 if (curveName == null)
@@ -98,15 +97,24 @@ namespace System.Security.Cryptography.X509Certificates
                         blobFormat = CngKeyBlobFormat.EccPublicBlob;
                     }
 
-                    keyBlob = ExportKeyBlob(bCryptKeyHandle, blobFormat);
-                    key = factory(CngKey.Import(keyBlob, blobFormat));
+                    ArraySegment<byte> keyBlob = ExportKeyBlob(bCryptKeyHandle, blobFormat);
+
+                    try
+                    {
+                        key = factory(CngKey.Import(keyBlob, blobFormat));
+                    }
+                    finally
+                    {
+                        CryptoPool.Return(keyBlob);
+                    }
                 }
                 else
                 {
                     blobFormat = CngKeyBlobFormat.EccPublicBlob;
-                    keyBlob = ExportKeyBlob(bCryptKeyHandle, blobFormat);
+                    ArraySegment<byte> 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 @@ namespace System.Security.Cryptography.X509Certificates
             }
         }
 
-        private static byte[] ExportKeyBlob(SafeBCryptKeyHandle bCryptKeyHandle, CngKeyBlobFormat blobFormat)
+        private static ArraySegment<byte> 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<byte> ecBlob, bool includePrivateParameters)
         {
             // We now have a buffer laid out as follows:
             //     BCRYPT_ECCKEY_BLOB   header