Невозможно повторно импортировать закрытый ключ с помощью NCryptExportKey и NCryptImportKey - PullRequest
0 голосов
/ 27 июня 2019

Я пытаюсь перезагрузить закрытый ключ сертификата с другими политиками экспорта, чтобы исправить эту проблему .Я повторно использовал код из этого ответа , чтобы экспортировать закрытый ключ, а затем импортировать его и установить политику экспорта в AllowPlainTextExport.После этого я смогу восстановить исходный сертификат с импортированным закрытым ключом и экспортировать его параметры, если это необходимо.Вот код, который у меня сейчас есть:

using Microsoft.Win32.SafeHandles;
using System;
using System.ComponentModel;
using System.Runtime.InteropServices;
using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates;
using System.Text;

namespace TestRsaCngExportImport
{
    class Program
    {
        internal const string NcryptPkcs8PrivateKeyBlob = "PKCS8_PRIVATEKEY";
        private const int NcryptDoNotFinalizeFlag = 0x00000400;
        public const string MicrosoftSoftwareKeyStorageProvider = "Microsoft Software Key Storage Provider";
        private static readonly byte[] pkcs12TripleDesOidBytes = Encoding.ASCII.GetBytes("1.2.840.113549.1.12.1.3\0");

        static void Main(string[] args)
        {
            var certificate = CreateCertificate();
            FixPrivateKey(certificate);
        }        

        public static void FixPrivateKey(X509Certificate2 certificate)
        {
            var cngKey = (RSACng)RSACertificateExtensions.GetRSAPrivateKey(certificate);
            var exported = ExportPkcs8KeyBlob(cngKey.Key.Handle, "", 1);
            var importedKeyName = ImportPkcs8KeyBlob(exported, "", 1);

            // Attempt #1

            CspParameters parameters = new CspParameters();
            parameters.KeyContainerName = importedKeyName;
            var rsaKey = new RSACryptoServiceProvider(parameters);
            certificate.PrivateKey = rsaKey; // public key doesn't match the private key

            // Attempt #2

            var rsaCngKey = new RSACng(CngKey.Open(importedKeyName));
            certificate.PrivateKey = rsaCngKey; // Only asymmetric keys that implement ICspAsymmetricAlgorithm are supported.

            // Attempt #3
            certificate.PrivateKey = null;
            X509Certificate2 certWithKey = certificate.CopyWithPrivateKey(rsaKey); // The provided key does not match the public key for this certificate.
        }

        private static X509Certificate2 CreateCertificate()
        {
            var keyParams = new CngKeyCreationParameters();
            keyParams.KeyUsage = CngKeyUsages.Signing;
            keyParams.Provider = CngProvider.MicrosoftSoftwareKeyStorageProvider;
            keyParams.ExportPolicy = CngExportPolicies.AllowExport; // here I don't have AllowPlaintextExport
            keyParams.Parameters.Add(new CngProperty("Length", BitConverter.GetBytes(2048), CngPropertyOptions.None));
            var cngKey = CngKey.Create(CngAlgorithm.Rsa, Guid.NewGuid().ToString(), keyParams);
            var rsaKey = new RSACng(cngKey);
            var req = new CertificateRequest("cn=mah_cert", rsaKey, HashAlgorithmName.SHA256, RSASignaturePadding.Pss); // requires .net 4.7.2
            var cert = req.CreateSelfSigned(DateTimeOffset.Now, DateTimeOffset.Now.AddYears(5));
            return cert;
        }

        private unsafe static string ImportPkcs8KeyBlob(byte[] exported, string password, int kdfCount)
        {
            var pbeParams = new NativeMethods.NCrypt.PbeParams();
            var pbeParamsPtr = &pbeParams;
            var salt = new byte[NativeMethods.NCrypt.PbeParams.RgbSaltSize];
            using (RandomNumberGenerator rng = RandomNumberGenerator.Create())
                rng.GetBytes(salt);
            pbeParams.Params.cbSalt = salt.Length;
            Marshal.Copy(salt, 0, (IntPtr)pbeParams.rgbSalt, salt.Length);
            pbeParams.Params.iIterations = kdfCount;

            var keyName = Guid.NewGuid().ToString("D");
            fixed (char* passwordPtr = password)
            fixed (char* keyNamePtr = keyName)
            fixed (byte* oidPtr = pkcs12TripleDesOidBytes)
            {
                NativeMethods.NCrypt.NCryptOpenStorageProvider(out var safeNCryptProviderHandle, MicrosoftSoftwareKeyStorageProvider, 0);
                NativeMethods.NCrypt.NCryptBuffer* buffers = stackalloc NativeMethods.NCrypt.NCryptBuffer[4];

                buffers[0] = new NativeMethods.NCrypt.NCryptBuffer
                {
                    BufferType = NativeMethods.NCrypt.BufferType.PkcsSecret,
                    cbBuffer = checked(2 * (password.Length + 1)),
                    pvBuffer = (IntPtr)passwordPtr,
                };

                if (buffers[0].pvBuffer == IntPtr.Zero)
                {
                    buffers[0].cbBuffer = 0;
                }

                buffers[1] = new NativeMethods.NCrypt.NCryptBuffer
                {
                    BufferType = NativeMethods.NCrypt.BufferType.PkcsAlgOid,
                    cbBuffer = pkcs12TripleDesOidBytes.Length,
                    pvBuffer = (IntPtr)oidPtr,
                };

                buffers[2] = new NativeMethods.NCrypt.NCryptBuffer
                {
                    BufferType = NativeMethods.NCrypt.BufferType.PkcsAlgParam,
                    cbBuffer = sizeof(NativeMethods.NCrypt.PbeParams),
                    pvBuffer = (IntPtr)pbeParamsPtr,
                };

                buffers[3] = new NativeMethods.NCrypt.NCryptBuffer
                {
                    BufferType = NativeMethods.NCrypt.BufferType.PkcsKeyName,
                    cbBuffer = checked(2 * (keyName.Length + 1)),
                    pvBuffer = (IntPtr)keyNamePtr,
                };

                var desc2 = new NativeMethods.NCrypt.NCryptBufferDesc
                {
                    cBuffers = 4,
                    pBuffers = (IntPtr)buffers,
                    ulVersion = 0,
                };

                var result = NativeMethods.NCrypt.NCryptImportKey(safeNCryptProviderHandle, IntPtr.Zero, NcryptPkcs8PrivateKeyBlob, ref desc2, out var safeNCryptKeyHandle, exported, exported.Length, NcryptDoNotFinalizeFlag);
                if (result != 0)
                    throw new Win32Exception(result);

                var exportPolicyBytes = BitConverter.GetBytes(
                  (int)(CngExportPolicies.AllowExport |
                        CngExportPolicies.AllowPlaintextExport |
                        CngExportPolicies.AllowArchiving |
                        CngExportPolicies.AllowPlaintextArchiving));

                NativeMethods.NCrypt.NCryptSetProperty(safeNCryptKeyHandle, "Export Policy", exportPolicyBytes, exportPolicyBytes.Length, CngPropertyOptions.Persist);
                NativeMethods.NCrypt.NCryptFinalizeKey(safeNCryptKeyHandle, 0);

                return keyName;
            }
        }

        private static unsafe byte[] ExportPkcs8KeyBlob(SafeNCryptKeyHandle keyHandle, string password, int kdfCount)
        {
            var pbeParams = new NativeMethods.NCrypt.PbeParams();
            var pbeParamsPtr = &pbeParams;
            var salt = new byte[NativeMethods.NCrypt.PbeParams.RgbSaltSize];
            using (RandomNumberGenerator rng = RandomNumberGenerator.Create())
                rng.GetBytes(salt);
            pbeParams.Params.cbSalt = salt.Length;
            Marshal.Copy(salt, 0, (IntPtr)pbeParams.rgbSalt, salt.Length);
            pbeParams.Params.iIterations = kdfCount;

            fixed (char* stringPtr = password)
            fixed (byte* oidPtr = pkcs12TripleDesOidBytes)
            {
                NativeMethods.NCrypt.NCryptBuffer* buffers =
                    stackalloc NativeMethods.NCrypt.NCryptBuffer[3];

                buffers[0] = new NativeMethods.NCrypt.NCryptBuffer
                {
                    BufferType = NativeMethods.NCrypt.BufferType.PkcsSecret,
                    cbBuffer = checked(2 * (password.Length + 1)),
                    pvBuffer = (IntPtr)stringPtr,
                };

                if (buffers[0].pvBuffer == IntPtr.Zero)
                {
                    buffers[0].cbBuffer = 0;
                }

                buffers[1] = new NativeMethods.NCrypt.NCryptBuffer
                {
                    BufferType = NativeMethods.NCrypt.BufferType.PkcsAlgOid,
                    cbBuffer = pkcs12TripleDesOidBytes.Length,
                    pvBuffer = (IntPtr)oidPtr,
                };

                buffers[2] = new NativeMethods.NCrypt.NCryptBuffer
                {
                    BufferType = NativeMethods.NCrypt.BufferType.PkcsAlgParam,
                    cbBuffer = sizeof(NativeMethods.NCrypt.PbeParams),
                    pvBuffer = (IntPtr)pbeParamsPtr,
                };

                var desc = new NativeMethods.NCrypt.NCryptBufferDesc
                {
                    cBuffers = 3,
                    pBuffers = (IntPtr)buffers,
                    ulVersion = 0,
                };

                int result = NativeMethods.NCrypt.NCryptExportKey(keyHandle, IntPtr.Zero, NcryptPkcs8PrivateKeyBlob, ref desc, null, 0, out int bytesNeeded, 0);
                if (result != 0)
                    throw new Win32Exception(result);

                byte[] exported = new byte[bytesNeeded];
                result = NativeMethods.NCrypt.NCryptExportKey(keyHandle, IntPtr.Zero, NcryptPkcs8PrivateKeyBlob, ref desc, exported, exported.Length, out bytesNeeded, 0);

                if (result != 0)
                    throw new Win32Exception(result);

                if (bytesNeeded != exported.Length)
                    Array.Resize(ref exported, bytesNeeded);
                return exported;
            }
        }

        private static class NativeMethods
        {
            internal static class NCrypt
            {
                public const string NCryptLibraryName = "ncrypt.dll";

                [DllImport(NCryptLibraryName, CharSet = CharSet.Unicode)]
                internal static extern int NCryptCreatePersistedKey(SafeNCryptProviderHandle hProvider, [Out] out SafeNCryptKeyHandle phKey, string pszAlgId, string pszKeyName, int dwLegacyKeySpec, CngKeyCreationOptions dwFlags);
                [DllImport(NCryptLibraryName, CharSet = CharSet.Unicode)]
                internal static extern int NCryptOpenStorageProvider([Out] out SafeNCryptProviderHandle phProvider, [MarshalAs(UnmanagedType.LPWStr)] string pszProviderName, int dwFlags);
                [DllImport(NCryptLibraryName, CharSet = CharSet.Unicode)]
                internal static extern int NCryptExportKey(SafeNCryptKeyHandle hKey, IntPtr hExportKey, string pszBlobType, ref NCryptBufferDesc pParameterList, byte[] pbOutput, int cbOutput, [Out] out int pcbResult, int dwFlags);
                [DllImport(NCryptLibraryName, CharSet = CharSet.Unicode)]
                internal static extern int NCryptImportKey(SafeNCryptProviderHandle hProvider, IntPtr hImportKey, string pszBlobType, ref NCryptBufferDesc pParameterList, [Out] out SafeNCryptKeyHandle phKey, [MarshalAs(UnmanagedType.LPArray)] byte[] pbData, int cbData, int dwFlags);
                [DllImport(NCryptLibraryName, CharSet = CharSet.Unicode)]
                internal static extern int NCryptSetProperty(SafeNCryptHandle hObject, string pszProperty, [MarshalAs(UnmanagedType.LPArray)] byte[] pbInput, int cbInput, CngPropertyOptions dwFlags);
                [DllImport(NCryptLibraryName, CharSet = CharSet.Unicode)]
                internal static extern int NCryptSetProperty(SafeNCryptHandle hObject, string pszProperty, string pbInput, int cbInput, CngPropertyOptions dwFlags);
                [DllImport(NCryptLibraryName, CharSet = CharSet.Unicode)]
                internal static extern int NCryptSetProperty(SafeNCryptHandle hObject, string pszProperty, IntPtr pbInput, int cbInput, CngPropertyOptions dwFlags);
                [DllImport(NCryptLibraryName, CharSet = CharSet.Unicode)]
                internal static extern int NCryptFinalizeKey(SafeNCryptKeyHandle hKey, int dwFlags);
                [DllImport(NCryptLibraryName, CharSet = CharSet.Unicode)]
                internal static extern int NCryptExportKey(SafeNCryptKeyHandle hKey, IntPtr hExportKey, string pszBlobType, IntPtr pParameterList, byte[] pbOutput, int cbOutput, [Out] out int pcbResult, int dwFlags);

                [StructLayout(LayoutKind.Sequential)]
                internal unsafe struct PbeParams
                {
                    internal const int RgbSaltSize = 8;
                    internal CryptPkcs12PbeParams Params;
                    internal fixed byte rgbSalt[RgbSaltSize];
                }

                [StructLayout(LayoutKind.Sequential)]
                internal struct CryptPkcs12PbeParams
                {
                    internal int iIterations;
                    internal int cbSalt;
                }

                [StructLayout(LayoutKind.Sequential)]
                internal struct NCryptBufferDesc
                {
                    public int ulVersion;
                    public int cBuffers;
                    public IntPtr pBuffers;
                }

                [StructLayout(LayoutKind.Sequential)]
                internal struct NCryptBuffer
                {
                    public int cbBuffer;
                    public BufferType BufferType;
                    public IntPtr pvBuffer;
                }

                internal enum BufferType
                {
                    PkcsAlgOid = 41,
                    PkcsAlgParam = 42,
                    PkcsAlgId = 43,
                    PkcsKeyName = 45,
                    PkcsSecret = 46,
                }
            }
        }
    }
}

Сертификат экспортируется, а затем импортируется.Однако импортированный закрытый ключ не может быть переназначен на исходный сертификат.Я получаю либо «Предоставленный ключ не соответствует открытому ключу для этого сертификата», либо «Поддерживаются только асимметричные ключи, которые реализуют алгоритм ICspAsymmetricAlgorithm».Что-то я делаю не так?

1 Ответ

1 голос
/ 27 июня 2019
// Attempt #1

CspParameters parameters = new CspParameters();
parameters.KeyContainerName = importedKeyName;
var rsaKey = new RSACryptoServiceProvider(parameters);
certificate.PrivateKey = rsaKey; // public key doesn't match the private key

CAPI (библиотека CspParameters) вообще не может понимать ключи в CNG в Windows 7 или 8.1;у него (теоретически) есть поддержка для 10, но вы определенно должны сказать ему, что ключ находится в CNG (CspParameters.ProviderName).

Код, приведенный здесь, создал новый ключ CAPI в «Microsoft RSA и AES».Расширенный поставщик служб шифрования "с ProviderType 24, который, как оказалось, имеет то же имя локального ключа, что и ваш ключ CNG.

Вы не указали флаг UseExistingOnly, и ключ не существует, поэтому он создалновый ... и именно поэтому открытый ключ не соответствует тому, что находится в сертификате.

// Attempt #2

var rsaCngKey = new RSACng(CngKey.Open(importedKeyName));
certificate.PrivateKey = rsaCngKey; // Only asymmetric keys that implement ICspAsymmetricAlgorithm are supported.

Свойство PrivateKey поддерживает только CAPI, либо в get, либо в set.Этот набор действительно опасен для использования, потому что он не изменяет объект сертификата, он изменяет состояние сертификата в системе хранилища сертификатов Windows ... что означает, что он также влияет на любые другие объекты в настоящее время или в будущем, работающие на том же самом объекте.(Windows) cert.

// Attempt #3
certificate.PrivateKey = null;
X509Certificate2 certWithKey = certificate.CopyWithPrivateKey(rsaKey); // The provided key does not match the public key for this certificate.

Это тот же новый случайный ключ, созданный из Попытки 1.


Если вы удалите Попытку 1, затем объедините 2 и 3, вы должны закончитьс

var rsaCngKey = new RSACng(CngKey.Open(importedKeyName));
X509Certificate2 certWithKey = certificate.CopyWithPrivateKey(rsaCngKey);

И это должно сработать.(Если вы уже импортировали сертификат в хранилище сертификатов, вы можете просто добавить certWithKey в хранилище сертификатов, которое будет иметь такое же «обновление, которое каждый знает об этом»), как и cert.set_PrivateKey, за исключением того, что это более очевидно.что вы попросили магазин сертификатов принять изменения)

...