サードパーティのサービスプロバイダーによって共有されている証明書の秘密キーを読み取ろうとしているため、それを使用してXMLを暗号化してからネットワークに送信できます。私はC#でプログラム的にそうしていますが、これは許可または設定ミスの問題だと思うので、最も関連があると思われる事実に焦点を当てます。
X509Certificate2.HasPrivateKey
を使用して秘密キーがあることを確認できます。ただし、キーを読み取ろうとするとエラーが発生します。 .NETでは、プロパティX509Certificate2.PrivateKey
にアクセスしようとすると、メッセージ「Invalid provider type specified」とともにCryptographicException
がスローされます。 Win32では、メソッドCryptAcquireCertificatePrivateKey
を呼び出すと、同等のHRESULT、NTE_BAD_PROV_TYPE
が返されます。同僚とは異なり、IIS Managerや、同じ発行者からの古い証明書を含めるなど、さまざまな方法で証明書をアンインストールおよび再インストールする以前に複数の試行を行いました。 MMCで古い証明書または重複した証明書の痕跡を確認しますが、同じサイズの秘密キーファイルが多数あり、最終書き込み時間に基づいて、さまざまなインストールの試行後に残されている必要があります。それぞれ、ローカルマシンおよび現在のユーザーストアの場所:
c:\ ProgramData\Microsoft\Crypto\RSA\MachineKeys
c:\ Users \\ AppData\Roaming\Microsoft\Crypto\RSA\S-1-5-21- [ユーザーIDの残り]
だから、誰でも次のことをアドバイスできますか?
更新-秘密鍵の読み取りの試みを示すコードサンプルを追加しました。
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();
}
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
を取得する手順は次のとおりです。
OpenSSL pkcs12 -in "MYCERT.pfx" -nokeys -out "MYCERT.cer" -passin "pass:MYPWD"
OpenSSL pkcs12 -in "MYCERT.pfx" -nocerts –out “MYCERT.pem" -passin "pass:MYPWD" -passout "pass:MYPWD"
OpenSSL rsa -inform PEM -in "MYCERT.pem" -out "MYCERT.rsa" -passin "pass:MYPWD" -passout "pass:MYPWD"
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コマンドラインの場合、パスワード内の"
文字は""
としてエスケープする必要があります。
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にエクスポートできるかどうかはわかりません...私の場合は、プライベートキーでデータに署名する必要がありました。
これが起こる別の理由は次のとおりです。これは奇妙な問題であり、1日苦労して問題を解決しました。実験として、 "C:\ ProgramData\Microsoft\Crypto\RSA\MachineKeys"フォルダーのアクセス許可を変更しました。格納。このフォルダーのアクセス許可を変更すると、すべての秘密キーがプロバイダーではない「Microsoft Software KSPプロバイダー」として表示されます(私の場合、「Microsoft RSA Schannel Cryptographic Provider」と想定されます)。
解決策:Machinekeysフォルダーへのアクセス許可をリセットします
このフォルダの元の許可は here にあります。私の場合、「Everyone」の許可を変更し、「Special permissions」チェックマークを削除した場所に読み取り許可を与えました。そこで、チームメンバーの1人に確認しました(フォルダーを右クリックし、[プロパティ]> [セキュリティ]> [詳細]> [全員]を選択します]> [編集]> [アクセス許可チェックボックスリストで[詳細設定]をクリックします
これが誰かの一日を救うことを願っています!
私が答えを見つけたのはここです source 、これを文書化するために彼に信用が行きます。
私の場合、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
私の場合、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日分の仕事が失われました。
私もこの問題を抱えており、この投稿の提案を試みたが成功しなかった。 Digicert証明書ユーティリティ https://www.digicert.com/util/ を使用して証明書をリロードすることで、問題を解決できました。これにより、証明書をロードするプロバイダーを選択できます。私の場合、証明書をMicrosoft RSA Schannel Cryptographic Providerプロバイダーにロードすると、そもそも問題が解決すると予想されていました。
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ビットアプリケーションを有効にする]設定をクリアすると、問題はなくなりました。
他の多くの回答が指摘しているように、この問題は秘密鍵が「古典的な」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キー)、GetRSAPrivateKey
はRSACryptoServiceProvider
を返します。
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
を超えてキャストしないでください)。
openssl
が chocolatey を介してインストールされていると仮定して、@ 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