web-dev-qa-db-ja.com

「無効なプロバイダータイプが指定されました」証明書の秘密鍵をロードしようとしたときにCryptographicExceptionが発生しました

サードパーティのサービスプロバイダーによって共有されている証明書の秘密キーを読み取ろうとしているため、それを使用してXMLを暗号化してからネットワークに送信できます。私はC#でプログラム的にそうしていますが、これは許可または設定ミスの問題だと思うので、最も関連があると思われる事実に焦点を当てます。

  • この問題はコードに関連するとは思わない。私のコードは他のコンピューターで動作し、問題はマイクロソフトのサンプルコードに影響します。
  • 証明書はPFXファイルとして提供されたものであり、テスト目的のためだけのものであるため、ダミーの証明機関も含まれています。
  • MMC.exeを使用して、ローカルマシンの個人ストアに証明書をインポートしてから、すべての関連アカウントに秘密キーのアクセス許可を付与し、信頼されたルート証明機関に証明機関をドラッグアンドドロップできます。
  • C#を使用して、証明書(thumb印で識別)をロードし、X509Certificate2.HasPrivateKeyを使用して秘密キーがあることを確認できます。ただし、キーを読み取ろうとするとエラーが発生します。 .NETでは、プロパティX509Certificate2.PrivateKeyにアクセスしようとすると、メッセージ「Invalid provider type specified」とともにCryptographicExceptionがスローされます。 Win32では、メソッドCryptAcquireCertificatePrivateKeyを呼び出すと、同等のHRESULT、NTE_BAD_PROV_TYPEが返されます。
  • これは、Microsoftの2つのコードサンプルを使用して証明書の秘密キーを読み取るときにも発生する例外です。
  • ローカルマシンではなく、現在のユーザーの同等のストアに同じ証明書をインストールすると、秘密鍵を正常にロードできます。
  • ローカル管理者権限でWindows 8.1を使用しており、通常モードと昇格モードの両方でコードを実行しようとしました。 Windows 7およびWindows 8の同僚は、同じ証明書のローカルマシンストアからキーをロードできました。
  • 同じストアの場所にある自己署名IISテスト証明書の秘密鍵を正常に読み取ることができます。
  • 私はすでに.NET 4.5をターゲットにしています(このエラーは、古いバージョンのフレームワークで報告されています)。
  • これが証明書テンプレートの問題だとは思いません。ローカルマシンストアと現在のユーザーストアの両方に等しく影響するのではないかと思っているからです。

同僚とは異なり、IIS Managerや、同じ発行者からの古い証明書を含めるなど、さまざまな方法で証明書をアンインストールおよび再インストールする以前に複数の試行を行いました。 MMCで古い証明書または重複した証明書の痕跡を確認しますが、同じサイズの秘密キーファイルが多数あり、最終書き込み時間に基づいて、さまざまなインストールの試行後に残されている必要があります。それぞれ、ローカルマシンおよび現在のユーザーストアの場所:

c:\ ProgramData\Microsoft\Crypto\RSA\MachineKeys

c:\ Users \\ AppData\Roaming\Microsoft\Crypto\RSA\S-1-5-21- [ユーザーIDの残り]

だから、誰でも次のことをアドバイスできますか?

  • MMCを使用して証明書をアンインストールし、孤立した秘密キーのように見えるすべてのファイルを削除してから、証明書を再インストールしてもう一度お試しください。
  • 手動で削除しようとする他のファイルはありますか?
  • 他に試してみるべきことはありますか?

更新-秘密鍵の読み取りの試みを示すコードサンプルを追加しました。

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();
}
36
Simon13

Windows 8とServer 2012/2012 R2で、最近受け取った2つの新しい証明書で同じ問題が発生しました。 Windows 10では、問題は発生しなくなりました(ただし、証明書を操作するコードがサーバーで使用されるため、これは役に立ちません)。 Joe Strommenのソリューションは原則として機能しますが、異なる秘密鍵モデルでは、証明書を使用してコードを大幅に変更する必要があります。 Remy Blok here で説明されているように、秘密鍵をCNGからRSAに変換することがより良い解決策であることがわかりました。

RemyはOpenSSLと2つの古いツールを使用して秘密キーの変換を実現しました。私たちはそれを自動化し、OpenSSLのみのソリューションを開発しました。 CNG形式の秘密鍵パスワードMYPWDを持つMYCERT.pfxが与えられた場合、RSA形式の秘密鍵と同じパスワードを持つ新しいCONVERTED.pfxを取得する手順は次のとおりです。

  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を読み込むか、CNG形式のpfxではなくWindows証明書ストアにインポートすると、問題はなくなり、C#コードを変更する必要はありません。

これを自動化するときに私が遭遇したもう1つの落とし穴:秘密キーに長い生成されたパスワードを使用し、パスワードには"が含まれる場合があります。 OpenSSLコマンドラインの場合、パスワード内の"文字は""としてエスケープする必要があります。

31

Alejandroのブログ へのリンクが重要です。

これは、証明書がCNG(「Crypto Next-Generation」)APIを使用してマシンに保存されているためだと思います。古い.NET APIは互換性がないため、機能しません。

このAPIにはSecurity.Cryptographyラッパーを使用できます( 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にエクスポートできるかどうかはわかりません...私の場合は、プライベートキーでデータに署名する必要がありました。

9
Joe Strommen

これが起こる別の理由は次のとおりです。これは奇妙な問題であり、1日苦労して問題を解決しました。実験として、 "C:\ ProgramData\Microsoft\Crypto\RSA\MachineKeys"フォルダーのアクセス許可を変更しました。格納。このフォルダーのアクセス許可を変更すると、すべての秘密キーがプロバイダーではない「Microsoft Software KSPプロバイダー」として表示されます(私の場合、「Microsoft RSA Schannel Cryptographic Provider」と想定されます)。

解決策:Machinekeysフォルダーへのアクセス許可をリセットします

このフォルダの元の許可は here にあります。私の場合、「Everyone」の許可を変更し、「Special permissions」チェックマークを削除した場所に読み取り許可を与えました。そこで、チームメンバーの1人に確認しました(フォルダーを右クリックし、[プロパティ]> [セキュリティ]> [詳細]> [全員]を選択します]> [編集]> [アクセス許可チェックボックスリストで[詳細設定]をクリックします

Special permissions

これが誰かの一日を救うことを願っています!

私が答えを見つけたのはここです source 、これを文書化するために彼に信用が行きます。

7
Dhanuka777

私の場合、PowerShellのNew-SelfSignedCertificateコマンドで自己署名証明書を使用しようとしました。デフォルトでは、古い/クラシック暗号CAPIの代わりにCNG(暗号次世代)APIを使用して証明書を生成します。古いコードの一部にはこれで問題があります。私の場合、それはIdentityServer STSプロバイダーの古いバージョンでした。

New-SelfSignedCertificateコマンドの最後にこれを追加することで、この問題を乗り越えました。

-KeySpec KeyExchange

Powershellコマンドのスイッチに関するリファレンス:

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

5
Greg Loehr

私の場合、localhost(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 Web Appにデプロイすると、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のおかげで1日分の仕事が失われました。

3
sports

私もこの問題を抱えており、この投稿の提案を試みたが成功しなかった。 Digicert証明書ユーティリティ https://www.digicert.com/util/ を使用して証明書をリロードすることで、問題を解決できました。これにより、証明書をロードするプロバイダーを選択できます。私の場合、証明書をMicrosoft RSA Schannel Cryptographic Providerプロバイダーにロードすると、そもそも問題が解決すると予想されていました。

1
Hugh

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)

ここで述べたように証明書を再生成しても役に立ちませんでした。また、プールユーザーでもテストコンソールアプリが正常に動作することに気付きました。

IISアプリプールの[32ビットアプリケーションを有効にする]設定をクリアすると、問題はなくなりました。

1
Vadim Kiselev

他の多くの回答が指摘しているように、この問題は秘密鍵が「古典的な」Windows Cryptographic API(CAPI)キーではなくWindows Cryptography:Next Generation(CNG)キーである場合に発生します。

.NET Framework 4.6以降では、秘密キー(RSAキーであると想定)には、X509Certificate2の拡張メソッドcert.GetRSAPrivateKey()を介してアクセスできます。

秘密鍵がCNGによって保持されている場合、GetRSAPrivateKey拡張メソッドはRSACngオブジェクトを返します(4.6のフレームワークの新機能)。 CNGには古いCAPIソフトウェアキーを読み取るためのパススルーがあるため、GetRSAPrivateKeyは通常、CAPIキーであってもRSACngを返します。しかし、CNGがCNGをロードできない場合(たとえば、CNGドライバーのないHSMキー)、GetRSAPrivateKeyRSACryptoServiceProviderを返します。

GetRSAPrivateKeyの戻り値の型はRSAであることに注意してください。 .NET Framework v4.6以降では、標準操作でRSAを超えてキャストする必要はありません。 RSACngまたはRSACryptoServiceProviderを使用する唯一の理由は、NCRYPT_KEY_HANDLEまたはキー識別子を使用する(または名前で永続キーを開く)プログラムまたはライブラリと相互運用する必要がある場合です。 (.NET Framework v4.6にはまだ入力オブジェクトをRSACryptoServiceProviderにキャストする場所がたくさんありましたが、それらはすべて4.6.2で削除されました(もちろん、この時点で2年以上前です)。 。

ECDSA証明書のサポートは4.6.1でGetECDsaPrivateKey拡張メソッドを介して追加され、DSAは4.6.2でGetDSAPrivateKeyを介してアップグレードされました。

.NET Coreでは、Get[Algorithm]PrivateKeyからの戻り値はOSによって異なります。 RSAの場合、WindowsではRSACng/RSACryptoServiceProvider、LinuxではRSAOpenSsl(またはmacOSを除くUNIXライクなOS)、macOSでは非パブリックタイプです(可能なことを意味します) RSAを超えてキャストしないでください)。

1
bartonjs

opensslchocolatey を介してインストールされていると仮定して、@ berend-engelbrechtからの回答のPowerShellバージョン

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
0
fiat