J'essaie de lire la clé privée d'un certificat qui a été partagée avec moi par un fournisseur de services tiers. Je peux donc l'utiliser pour chiffrer du code XML avant de l'envoyer par câble. Je le fais par programme en C #, mais je pense que c'est un problème de permissions ou de configuration, je vais donc me concentrer sur les faits qui semblent les plus pertinents:
X509Certificate2.HasPrivateKey
. Cependant, essayer de lire la clé provoque une erreur. Dans .NET, une CryptographicException
est générée avec le message "Type de fournisseur non valide spécifié" lors de la tentative d'accès à la propriété X509Certificate2.PrivateKey
. En Win32, l'appel de la méthode CryptAcquireCertificatePrivateKey
renvoie l'équivalent HRESULT, NTE_BAD_PROV_TYPE
.Contrairement à mes collègues, j'ai déjà tenté à plusieurs reprises de désinstaller et de réinstaller le certificat de différentes manières, notamment via IIS Manager et également avec un certificat plus ancien du même émetteur. Je ne vois aucune trace d'ancien ou de duplicata de certificats dans MMC. Cependant, j'ai beaucoup de fichiers de clés privées de taille identique qui, en fonction de l'heure de la dernière écriture, doivent avoir été laissés après mes différentes tentatives d'installation. Ceux-ci se trouvent aux emplacements suivants, pour l'ordinateur local et les magasins d'utilisateurs actuels, respectivement:
c:\ProgramData\Microsoft\Crypto\RSA\MachineKeys
c:\Utilisateurs \\ AppData\Roaming\Microsoft\Crypto\RSA\S-1-5-21- [reste de l'ID utilisateur]
Alors, quelqu'un peut-il s'il vous plaît indiquer si:
UPDATE - Ajout d'un exemple de code montrant une tentative de lecture d'une clé privée:
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();
}
J'ai eu le même problème sous Windows 8 et Server 2012/2012 R2 avec deux nouveaux certificats que j'ai récemment reçus. Sous Windows 10, le problème ne se pose plus (mais cela ne m’aide pas, car le code manipulant le certificat est utilisé sur un serveur). Bien que la solution de Joe Strommen fonctionne en principe, le modèle de clé privée différent nécessiterait une modification en profondeur du code à l'aide des certificats. Je trouve qu'une meilleure solution consiste à convertir la clé privée de CNG en RSA, comme l'a expliqué Remy Blok ici .
Remy utilise OpenSSL et deux outils plus anciens pour effectuer la conversion de clé privée. Nous avons voulu l'automatiser et avons développé une solution OpenSSL uniquement. Étant donné MYCERT.pfx
avec le mot de passe de clé privée MYPWD
au format CNG, voici les étapes à suivre pour obtenir un nouveau CONVERTED.pfx
avec une clé privée au format RSA et le même mot de passe:
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"
Si vous chargez le pfx converti ou l'importez dans le magasin de certificats Windows au lieu du pfx au format CNG, le problème disparaît et le code C # n'a pas besoin d'être modifié.
Un casse-tête supplémentaire que j'ai rencontré lors de l'automatisation de ceci: nous utilisons des mots de passe longs pour la clé privée et le mot de passe peut contenir ". Pour la ligne de commande OpenSSL," les caractères du mot de passe doivent être échappés en tant que "".
Le lien vers Le blog de Alejandro est la clé.
Je pense que cela est dû au fait que le certificat est stocké sur votre ordinateur avec l’API CNG ("Crypto Next-Generation"). L'ancienne API .NET n'est pas compatible avec cette dernière et ne fonctionne donc pas.
Vous pouvez utiliser le wrapper Security.Cryptography pour cette API ( disponible sur Codeplex ). Ceci ajoute les méthodes d'extension à X509Certificate/X509Certificate2
, ainsi votre code ressemblera à ceci:
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;
}
Malheureusement, le modèle d'objet pour les clés privées CNG est assez différent. Je ne sais pas si vous pouvez les exporter au format XML comme dans votre exemple de code d'origine ... Dans mon cas, je devais simplement signer des données avec la clé privée.
Voici une autre raison pour laquelle cela peut arriver, c'est un problème étrange et, après avoir lutté pendant un jour, j'ai résolu le problème. À titre d’expérience, j’ai modifié l’autorisation pour le dossier "C:\ProgramData\Microsoft\Crypto\RSA\MachineKeys" qui contient les données de clé privée pour les certificats utilisant le magasin de clés Machine. Lorsque vous modifiez l'autorisation de ce dossier, toutes les clés privées apparaissent comme "fournisseur Microsoft Software KSP", qui n'est pas le fournisseur (dans mon cas, elles sont supposées être "fournisseur cryptographique Microsoft RSA Schannel").
Solution: Réinitialiser les autorisations dans le dossier Machinekeys
La permission originale pour ce dossier peut être trouvée dans ici . Dans mon cas, j'ai changé la permission pour "Tout le monde", j'ai donné les permissions en lecture quand il a enlevé la coche "Autorisations spéciales". J'ai donc vérifié auprès d'un membre de mon équipe (dossier avec le bouton droit de la souris sur le dossier> Propriétés> Sécurité> Avancé> sélectionnez "Tout le monde"> Modifier> Cliquez sur "Paramètres avancés" dans la liste des cases à cocher des autorisations.
J'espère que cela sauvera la journée de quelqu'un!
Voici où j'ai trouvé la réponse source , le mérite lui revient de documenter cela.
Version Powershell de la réponse de @ berend-engelbrecht, en supposant que openssl
est installé via 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
J'ai également eu ce problème et après avoir tenté les suggestions de ce post sans succès. J'ai pu résoudre mon problème en rechargeant le certificat avec l'utilitaire de certificat Digicert https://www.digicert.com/util/ . Cela permet de sélectionner le fournisseur dans lequel charger le certificat. Dans mon cas, le chargement du certificat dans le fournisseur de cryptographie Microsoft RSA Schannel où je m'attendais à le résoudre a résolu le problème.
Comme de nombreuses autres réponses l'ont souligné, ce problème se pose lorsque la clé privée est une clé CNG (Windows Cryptography: Next Generation) au lieu d'une clé CAPI (Windows Cryptographic API) "classique".
À partir de .NET Framework 4.6, la clé privée (en supposant qu'il s'agisse d'une clé RSA) est accessible via une méthode d'extension sur X509Certificate2: cert.GetRSAPrivateKey()
.
Lorsque la clé privée est détenue par CNG, la méthode d’extension GetRSAPrivateKey
renvoie un objet RSACng
(nouveauté de la structure en 4.6). Étant donné que le GNC permet de lire les anciennes clés du logiciel CAPI, GetRSAPrivateKey
renverra généralement une RSACng
même pour une clé CAPI; mais si CNG ne peut pas le charger (par exemple, il s'agit d'une clé HSM sans pilote CNG), alors GetRSAPrivateKey
renverra un RSACryptoServiceProvider
.
Notez que le type de retour pour GetRSAPrivateKey
est RSA
. À partir de .NET Framework v4.6, vous ne devriez pas avoir besoin de transtyper au-delà de RSA
pour les opérations standard. La seule raison d'utiliser RSACng
ou RSACryptoServiceProvider
est lorsque vous devez interagir avec des programmes ou des bibliothèques utilisant le NCRYPT_KEY_HANDLE
ou l'identificateur de clé (ou en ouvrant une clé persistante par son nom). (.NET Framework v4.6 contenait beaucoup d'endroits qui convertissaient toujours l'objet d'entrée en RSACryptoServiceProvider
, mais ceux-ci ont tous été éliminés par la version 4.6.2 (bien sûr, cela fait plus de 2 ans à ce stade)).
La prise en charge des certificats ECDSA a été ajoutée à la version 4.6.1 via une méthode d’extension GetECDsaPrivateKey
, et le DSA a été mis à niveau à la version 4.6.2 via GetDSAPrivateKey
.
Sur .NET Core, la valeur renvoyée par Get[Algorithm]PrivateKey
change en fonction du système d'exploitation. Pour RSA, il s'agit de RSACng
/RSACryptoServiceProvider
sous Windows, RSAOpenSsl
sous Linux (ou tout système d'exploitation de type UNIX sauf macOS), et un type non public sur macOS (ce qui signifie que vous ne pouvez pas le diffuser au-delà de RSA
).