Подтвердить что ты не робот

"Указан недопустимый тип провайдера" CryptographicException при попытке загрузить закрытый ключ сертификата

Я пытаюсь прочитать закрытый ключ сертификата, который был предоставлен мне сторонним поставщиком услуг, поэтому я могу использовать его для шифрования некоторых XML, прежде чем отправлять их им по проводу. Я делаю это программно в С#, но я думаю, что это проблема с разрешениями или неправильной конфигурацией, поэтому я сосредоточу внимание на фактах, которые кажутся наиболее важными:

  • Я не думаю, что эта проблема связана с кодом; мой код работает на других компьютерах, и проблема влияет на образец кода из Microsoft.
  • Сертификат был предоставлен как файл PFX и предназначен только для тестирования, поэтому он также включает фиктивный центр сертификации.
  • Используя MMC.exe, я могу импортировать сертификат в личное хранилище для локального компьютера, прежде чем предоставлять разрешения на закрытый ключ всем соответствующим учетным записям и перетащить центр сертификации в доверенные корневые центры сертификации.
  • Используя С#, я могу загрузить сертификат (идентифицированный его отпечатком) и убедиться, что он имеет закрытый ключ, используя X509Certificate2.HasPrivateKey. Однако попытка прочитать ключ вызывает ошибку. В .NET a CryptographicException вызывается с сообщением "Недопустимый тип провайдера" при попытке получить доступ к свойству X509Certificate2.PrivateKey. В Win32 вызов метода CryptAcquireCertificatePrivateKey возвращает эквивалентный HRESULT, NTE_BAD_PROV_TYPE.
  • Это то же самое исключение, которое также возникает при использовании двух собственных образцов кода Microsoft для чтения закрытого ключа сертификата.
  • Установка того же сертификата в эквивалентном хранилище для текущего пользователя вместо локального компьютера позволяет успешно загружать закрытый ключ.
  • Я нахожусь в Windows 8.1 с правами локального администратора, и я попытался запустить свой код как в нормальном, так и в повышенном режиме. Коллегам Windows 7 и Windows 8 удалось загрузить ключ из локального хранилища компьютеров для того же сертификата.
  • Я могу успешно прочитать закрытый ключ самозаверяющего сертификата IIS, который находится в том же хранилище.
  • Я уже ориентируюсь на .NET 4.5 (эта ошибка сообщалась с некоторыми более старыми версиями фреймворка).
  • Я не думаю, что это проблема с шаблонами сертификатов, потому что я ожидаю, что это повлияет как на локальную машину, так и на магазины текущего пользователя одинаково?

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

C:\ProgramData\Microsoft\Crypto\RSA\MachineKeys

c:\Users\\AppData\Роуминг\Microsoft\Crypto\RSA\S-1-5-21- [остальные идентификаторы пользователя]

Итак, кто-нибудь может посоветовать:

  • Рекомендуется удалить сертификат с помощью MMC, удалить все те файлы, которые выглядят как потерянные частные ключи, а затем повторно установить сертификат и повторить попытку?
  • Есть ли другие файлы, которые я должен удалить вручную?
  • Что-нибудь еще я должен попробовать?

UPDATE - добавлен образец кода, показывающий попытку прочитать закрытый ключ:

static void Main()
{
    // Exception occurs when trying to read the private key after loading certificate from here:
    X509Store store = new X509Store("MY", StoreLocation.LocalMachine);
    // Exception does not occur if certificate was installed to, and loaded from, here:
    //X509Store store = new X509Store("MY", StoreLocation.CurrentUser);

    store.Open(OpenFlags.ReadOnly | OpenFlags.OpenExistingOnly);

    X509Certificate2Collection collection = (X509Certificate2Collection)store.Certificates;
    X509Certificate2Collection fcollection = (X509Certificate2Collection)collection.Find(X509FindType.FindByTimeValid, DateTime.Now, false);
    X509Certificate2Collection scollection = X509Certificate2UI.SelectFromCollection(fcollection, "Test Certificate Select", "Select a certificate from the following list to get information on that certificate", X509SelectionFlag.MultiSelection);
    Console.WriteLine("Number of certificates: {0}{1}", scollection.Count, Environment.NewLine);

    foreach (X509Certificate2 x509 in scollection)
    {
        try
        {
            Console.WriteLine("Private Key: {0}", x509.HasPrivateKey ? x509.PrivateKey.ToXmlString(false) : "[N/A]");
            x509.Reset();
        }
        catch (CryptographicException ex)
        {
            Console.WriteLine(ex.Message);
        }
    }
    store.Close();

    Console.ReadLine();
}
4b9b3361

Ответ 1

У меня была такая же проблема на Windows 8 и Server 2012/2012 R2 с двумя новыми сертификатами, которые я недавно получил. В Windows 10 проблема больше не возникает (но это не помогает мне, поскольку код, управляющий сертификатом, используется на сервере). Хотя решение Джо Строммена в принципе работает, другая модель закрытого ключа потребует значительных изменений в коде с использованием сертификатов. Я считаю, что лучшим решением является преобразование закрытого ключа из CNG в RSA, как объяснил Реми Блок здесь.

Реми использует OpenSSL и два более старых инструмента для выполнения преобразования закрытого ключа, мы хотели автоматизировать его и разработали решение только для OpenSSL. Учитывая MYCERT.pfx с паролем закрытого ключа MYPWD в формате CNG, это шаги для получения нового CONVERTED.pfx с закрытым ключом в формате RSA и тем же паролем:

  1. Извлечение открытых ключей, полная цепочка сертификатов:

OpenSSL pkcs12 -in "MYCERT.pfx" -nokeys -out "MYCERT.cer" -passin "pass:MYPWD"

  1. Извлечь закрытый ключ:

OpenSSL pkcs12 -in "MYCERT.pfx" -nocerts –out "MYCERT.pem" -passin "pass:MYPWD" -passout "pass:MYPWD"

  1. Преобразовать закрытый ключ в формат RSA:

OpenSSL rsa -inform PEM -in "MYCERT.pem" -out "MYCERT.rsa" -passin "pass:MYPWD" -passout "pass:MYPWD"

  1. Объединить открытые ключи с закрытым ключом RSA в новый PFX:

OpenSSL pkcs12 -export -in "MYCERT.cer" -inkey "MYCERT.rsa" -out "CONVERTED.pfx" -passin "pass:MYPWD" -passout "pass:MYPWD"

Если вы загрузите преобразованный файл pfx или импортируете его в хранилище сертификатов Windows вместо формата pgx CNG, проблема исчезнет, и код С# не нужно менять.

Одна дополнительная ошибка, с которой я столкнулся при автоматизации: мы используем длинные сгенерированные пароли для закрытого ключа, и пароль может содержать ". Для командной строки OpenSSL " символы внутри пароля должны быть экранированы как "".

Ответ 2

Ссылка на блог Алехандро является ключевой.

Я считаю, что это связано с тем, что сертификат хранится на вашем компьютере с помощью API CNG ( "Crypto Next-Generation" ). Старый .NET API не совместим с ним, поэтому он не работает.

Вы можете использовать оболочку Security.Cryptography для этого API (доступную на Codeplex). Это добавляет методы расширения к X509Certificate/X509Certificate2, поэтому ваш код будет выглядеть примерно так:

using Security.Cryptography.X509Certificates; // Get extension methods

X509Certificate cert; // Populate from somewhere else...
if (cert.HasCngKey())
{
    var privateKey = cert.GetCngPrivateKey();
}
else
{
    var privateKey = cert.PrivateKey;
}

К сожалению, объектная модель для частных ключей CNG совсем немного отличается. Я не уверен, что вы можете экспортировать их в XML, как в исходном коде кода... в моем случае мне просто нужно было подписать некоторые данные с помощью закрытого ключа.

Ответ 3

Вот еще одна причина, по которой это может произойти, это была странная проблема, и после одного дня борьбы я решил ее. В качестве эксперимента я изменил разрешение для папки "C:\ProgramData\Microsoft\Crypto\RSA\MachineKeys", в которой хранятся данные закрытого ключа для сертификатов с использованием хранилища ключей компьютеров. При изменении разрешения для этой папки все частные ключи отображаются как "Поставщик Microsoft Software KSP", который не является поставщиком (в моем случае они должны быть "Поставщиком криптографии Schannel Microsoft RSA").

Решение: сбросьте разрешения для папки Machinekeys

Оригинальное разрешение для этой папки можно найти здесь. В моем случае я изменил разрешение для "Все", дал разрешения на чтение, где он снял галочку "Специальные разрешения". Поэтому я проверил с одним из членов моей команды (щелкните правой кнопкой мыши по папке> Свойства> Безопасность> Дополнительно> выберите "Все"> Изменить> Нажмите "Дополнительные параметры" в списке разрешений флажок

Special permissions

Надеюсь, это спасет кого-то день!

Здесь, где я нашел источник ответа, ему за это отдают должное.

Ответ 4

Как указывалось во многих других ответах, эта проблема возникает, когда закрытым ключом является ключ Windows Cryptography: Next Generation (CNG) вместо "классического" ключа Windows Cryptographic API (CAPI).

Начиная с .NET Framework 4.6, к закрытому ключу (предполагается, что это ключ RSA) можно получить доступ через метод расширения X509Certificate2: cert.GetRSAPrivateKey().

Когда CNG GetRSAPrivateKey закрытый ключ, метод расширения RSACng объект RSACng (новый для платформы в 4.6). Поскольку CNG имеет проход для чтения старых программных ключей CAPI, GetRSAPrivateKey обычно возвращает RSACng даже для ключа CAPI; но если CNG не может загрузить его (например, это ключ HSM без драйвера CNG), то GetRSAPrivateKey вернет RSACryptoServiceProvider.

Обратите внимание, что тип возвращаемого значения для GetRSAPrivateKey - RSA. Начиная с .NET Framework v4.6 вам не нужно приводить за пределы RSA для стандартных операций; единственная причина использовать RSACng или RSACryptoServiceProvider - это необходимость взаимодействовать с программами или библиотеками, которые используют NCRYPT_KEY_HANDLE или идентификатор ключа (или открывают постоянный ключ по имени). (В .NET Framework v4.6 было много мест, которые все еще RSACryptoServiceProvider входной объект к RSACryptoServiceProvider, но все они были устранены с помощью 4.6.2 (конечно, на тот момент более 2 лет назад)).

Поддержка сертификата ECDSA была добавлена в 4.6.1 с помощью GetECDsaPrivateKey расширения GetECDsaPrivateKey, а DSA была обновлена в 4.6.2 с помощью GetDSAPrivateKey.

В .NET Core возвращаемое значение из Get[Algorithm]PrivateKey изменяется в зависимости от ОС. Для RSA это RSACng/RSACryptoServiceProvider в Windows, RSAOpenSsl в Linux (или в любой UNIX-подобной ОС, кроме macOS), а также непубличный тип в macOS (то есть вы не можете привести его за RSA).

Ответ 5

У меня также была эта проблема и после успешной попытки предложения в этом сообщении. Я смог решить проблему, перезагрузив сертификат утилитой сертификата Digicert https://www.digicert.com/util/. Это позволяет выбрать поставщика для загрузки сертификата. В моем случае загрузка сертификата в поставщик Microsoft RSA Schannel Cryptographic Provider, где я ожидал, что он в первую очередь разрешит проблему.

Ответ 6

В моем случае следующий код работал локально (как NET 3.5, так и NET 4.7):

 var certificate = new X509Certificate2(certificateBytes, password);

 string xml = "....";
 XmlDocument xmlDocument = new XmlDocument();
 xmlDocument.PreserveWhitespace = true;
 xmlDocument.LoadXml(xml);

 SignedXml signedXml = new SignedXml(xmlDocument);
 signedXml.SigningKey = certificate.PrivateKey;

 //etc...

Но это не удалось при развертывании в веб-приложении Azure по адресу certificate.PrivateKey

Это сработало, изменив код следующим образом:

 var certificate = new X509Certificate2(certificateBytes, password, X509KeyStorageFlags.MachineKeySet | X509KeyStorageFlags.PersistKeySet | X509KeyStorageFlags.Exportable);
                                                                   //^ Here
 string xml = "....";
 XmlDocument xmlDocument = new XmlDocument();
 xmlDocument.PreserveWhitespace = true;
 xmlDocument.LoadXml(xml);

 SignedXml signedXml = new SignedXml(xmlDocument);
 signedXml.SigningKey = certificate.GetRSAPrivateKey();
                                      // ^ Here too

 //etc...

Целый день работы, потерянной благодаря Microsoft Azure, снова в моей жизни.

Ответ 7

В моем случае я пытался использовать самозаверяющий сертификат с командой PowerShell New-SelfSignedCertificate. По умолчанию он генерирует сертификат с использованием API CNG (Crypto-Next Generation) вместо старого/классического крипто-CAPI. Некоторые старые части кода будут иметь проблемы с этим; в моем случае это была более старая версия поставщика IdentityServer STS.

Добавив это в конце моей команды New-SelfSignedCertificate, я решил эту проблему:

-KeySpec KeyExchange

Ссылка на переключатель для команды powershell:

https://docs.microsoft.com/en-us/powershell/module/pkiclient/new-selfsignedcertificate?view=win10-ps

Ответ 8

Версия ответа Powershell от @berend-engelbrecht, предполагая, что openssl установлен через chocolatey

function Fix-Certificates($certPasswordPlain)
{
    $certs = Get-ChildItem -path "*.pfx" -Exclude "*.converted.pfx"
    $certs | ForEach-Object{
        $certFile = $_

        $shortName = [io.path]::GetFileNameWithoutExtension($certFile.Name)
        Write-Host "Importing $shortName"
        $finalPfx = "$shortName.converted.pfx"


        Set-Alias openssl "C:\Program Files\OpenSSL\bin\openssl.exe"

        # Extract public key
        OpenSSL pkcs12 -in $certFile.Fullname -nokeys -out "$shortName.cer" -passin "pass:$certPasswordPlain"

        # Extract private key
        OpenSSL pkcs12 -in $certFile.Fullname -nocerts -out "$shortName.pem" -passin "pass:$certPasswordPlain" -passout "pass:$certPasswordPlain"

        # Convert private key to RSA format
        OpenSSL rsa -inform PEM -in "$shortName.pem" -out "$shortName.rsa" -passin "pass:$certPasswordPlain" -passout "pass:$certPasswordPlain" 2>$null

        # Merge public keys with RSA private key to new PFX
        OpenSSL pkcs12 -export -in "$shortName.cer" -inkey "$shortName.rsa" -out $finalPfx -passin "pass:$certPasswordPlain" -passout "pass:$certPasswordPlain"

        # Clean up
        Remove-Item "$shortName.pem"
        Remove-Item "$shortName.cer"
        Remove-Item "$shortName.rsa"

        Write-Host "$finalPfx created"
    }
}

# Execute in cert folder
Fix-Certificates password

Ответ 9

Я столкнулся с той же проблемой в нашем приложении IIS:

 System.Security.Cryptography.Pkcs.PkcsUtils.CreateSignerEncodeInfo(CmsSigner signer, Boolean silent)
    System.Security.Cryptography.Pkcs.SignedCms.Sign(CmsSigner signer, Boolean silent)
    System.Security.Cryptography.Pkcs.SignedCms.ComputeSignature(CmsSigner signer, Boolean silent)

Регенерирование сертификатов, как упомянуто здесь, не помогло. Я также заметил, что тестовое консольное приложение отлично работает под пользователем пула.

Проблема исчезла после очистки параметра "Включить 32-разрядные приложения" для пула приложений IIS.