Невозможно экспортировать параметры закрытого ключа RSA, запрошенная операция не поддерживается - PullRequest
0 голосов
/ 01 февраля 2019

У меня есть файл сертификата, предоставленный другой стороной, который я загружаю в свое приложение и не могу экспортировать параметры его закрытого ключа.Похоже, что сертификат использует CNG, а не CryptoAPI, поэтому я не могу получить доступ к закрытому ключу напрямую, только с помощью метода GetRSAPrivateKey ().Метод возвращает RSACngKey, а не RSACryptoServiceProvider, что является другой реализацией RSA.Проблема в том, что возвращенный ключ, кажется, отсутствует CngExportPolicies.AllowPlaintextExport в его политиках экспорта, и поэтому я не могу экспортировать параметры RSA из этого сертификата.Я могу воспроизвести проблему, сгенерировав новый сертификат, который пропускает необходимые политики экспорта:

using System;
using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates;

namespace TestRsaCngConsole
{
    class Program
    {
        static void Main(string[] args)
        {
            var oldCertificate = CreateCertificate();
            var oldCertificateBytes = oldCertificate.Export(X509ContentType.Pfx, "");
            var newCertificate = new X509Certificate2(oldCertificateBytes, "",
                X509KeyStorageFlags.Exportable | 
                X509KeyStorageFlags.MachineKeySet | 
                X509KeyStorageFlags.PersistKeySet);

            LogCertificate(oldCertificate, "old certificate"); // this fails
            LogCertificate(newCertificate, "new certificate"); // works only on Win10
            Console.ReadKey();
        }

        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 static void LogCertificate(X509Certificate2 certificate, string name)
        {
            Console.WriteLine("----- Testing " + name + " ------");

            try
            {
                var rsaPrivateKey = certificate.GetRSAPrivateKey();
                var parameters = rsaPrivateKey.ExportParameters(true);
                Console.WriteLine("Certificate private key RSA parameters were successfully exported.");

                var privateKey = certificate.PrivateKey;
                Console.WriteLine("Certificate private key is accessible.");
            }
            catch (Exception e)
            {
                Console.WriteLine(e.ToString());
            }
        }
    }
}

Программа показывает следующий вывод при работе в Windows 10:

----- Testing old certificate ------
System.Security.Cryptography.CryptographicException: The requested operation is not supported.

   at System.Security.Cryptography.NCryptNative.ExportKey(SafeNCryptKeyHandle key, String format)
   at System.Security.Cryptography.CngKey.Export(CngKeyBlobFormat format)
   at System.Security.Cryptography.RSACng.ExportParameters(Boolean includePrivateParameters)
   at TestRsaCngConsole.Program.LogCertificate(X509Certificate2 certificate, String name) in D:\Projects\TestRsaCngConsole\TestRsaCngConsole\Program.cs:line 44
----- Testing new certificate ------
Certificate private key RSA parameters were successfully exported.
Certificate private key is accessible.

Итак, первый сертификатне удается экспортировать закрытый ключ, поскольку в его политиках экспорта отсутствует флаг AllowPlaintextExport.Но после перезагрузки старого сертификата с экспортируемыми флагами я могу экспортировать новые параметры сертификата просто отлично.Однако он не работает в Windows Server 2012 или Windows Server 2016 и выдает исключения для обоих сертификатов:

----- Testing old certificate ------
System.Security.Cryptography.CryptographicException: The requested operation is not supported.

   at System.Security.Cryptography.NCryptNative.ExportKey(SafeNCryptKeyHandle key, String format)
   at System.Security.Cryptography.CngKey.Export(CngKeyBlobFormat format)
   at System.Security.Cryptography.RSACng.ExportParameters(Boolean includePrivateParameters)
   at TestRsaCngConsole.Program.LogCertificate(X509Certificate2 certificate, String name) in D:\Projects\TestRsaCngConsole\TestRsaCngConsole\Program.cs:line 44
----- Testing new certificate ------
System.Security.Cryptography.CryptographicException: The requested operation is not supported.

   at System.Security.Cryptography.NCryptNative.ExportKey(SafeNCryptKeyHandle key, String format)
   at System.Security.Cryptography.CngKey.Export(CngKeyBlobFormat format)
   at System.Security.Cryptography.RSACng.ExportParameters(Boolean includePrivateParameters)
   at TestRsaCngConsole.Program.LogCertificate(X509Certificate2 certificate, String name) in D:\Projects\TestRsaCngConsole\TestRsaCngConsole\Program.cs:line 44

Мне нужно иметь возможность исправить сертификат и сделать возможным экспорт параметров RSA, дажеесли в сертификате изначально отсутствовал AllowPlaintextExport.Чем отличается Windows Server и есть ли способ исправить сертификат?

1 Ответ

0 голосов
/ 05 февраля 2019

К сожалению, единственный способ экспортировать ключ в этом состоянии - это P / Invoke в NCryptExportKey для настройки зашифрованного экспорта;затем импортируйте его в новый ключ с помощью NCryptImportKey, а затем установите для политики экспорта значение AllowPlaintextExport.

Начиная с .NET Core 3.0 это будет проще:

using (RSA exportRewriter = RSA.Create())
{
    // Only one KDF iteration is being used here since it's immediately being
    // imported again.  Use more if you're actually exporting encrypted keys.
    exportRewriter.ImportEncryptedPkcs8(
        "password",
        rsa.ExportEncryptedPkcs8(
            "password",
            new PbeParameters(
                PbeEncryptionAlgorithm.Aes128Cbc,
                HashAlgorithmName.SHA256,
                1)),
        out _);

    return exportRewriter.ExportParameters(true);
}

Код .NET Coreэкспорт зашифрован на https://github.com/dotnet/corefx/blob/64477348da1ff57a43deb65a4b12d32986ed00bd/src/System.Security.Cryptography.Cng/src/System/Security/Cryptography/CngKey.Export.cs#L126-L237, это не очень хороший API для вызова из C #.

...