web-dev-qa-db-fra.com

"Type de fournisseur non valide spécifié" CryptographicException lors d'une tentative de chargement de la clé privée du certificat

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:

  • Je ne pense pas que cette question est liée au code; mon code fonctionne sur d'autres ordinateurs et le problème affecte l'exemple de code fourni par Microsoft.
  • Le certificat a été fourni sous forme de fichier PFX et sert uniquement à des fins de test. Il inclut donc également une autorité de certification factice.
  • À l’aide de MMC.exe, je peux importer le certificat dans le magasin personnel de la machine locale avant d’accorder des autorisations sur la clé privée à tous les comptes pertinents et de faire glisser l’autorité de certification dans les autorités de certification racines de confiance.
  • En utilisant C #, je peux charger le certificat (identifié par son empreinte numérique) et vérifier qu’il possède une clé privée en utilisant 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.
  • Il s'agit de la même exception qui se produit également lors de l'utilisation de deux exemples de code propres à Microsoft pour lire la clé privée du certificat.
  • L'installation du même certificat dans le magasin équivalent pour l'utilisateur actuel, au lieu de la machine locale, permet le chargement de la clé privée.
  • Je suis sous Windows 8.1 avec des droits d'administrateur local et j'ai essayé d'exécuter mon code dans les modes normal et élevé. Les collègues de Windows 7 et Windows 8 ont été en mesure de charger la clé à partir du magasin de l'ordinateur local pour le même certificat.
  • Je peux lire avec succès la clé privée du certificat de test auto-signé IIS, qui se trouve dans le même emplacement de magasin.
  • Je cible déjà .NET 4.5 (cette erreur a été signalée avec certaines versions plus anciennes du framework).
  • Je ne pense pas que ce soit un problème avec les modèles de certificat, car je pense que cela affectera également les magasins de l'ordinateur local et ceux des utilisateurs actuels.

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:

  • C'est une bonne idée de désinstaller le certificat à l'aide de MMC, de supprimer tous les fichiers qui ressemblent à des clés privées orphelines, puis de réinstaller le certificat et de réessayer.
  • Y at-il d’autres fichiers que je devrais essayer de supprimer manuellement?
  • Il y a autre chose que je devrais essayer?

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

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:

  1. Extraire les clés publiques, chaîne de certificat complète:

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

  1. Extraire la clé privée:

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

  1. Convertir une clé privée au format RSA:

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

  1. Fusionner les clés publiques avec la clé privée RSA vers le nouveau PFX:

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 "".

26
Berend Engelbrecht

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.

9
Joe Strommen

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. 

 Special permissions

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. 

5
Dhanuka777

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

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.

0
Hugh

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).

0
bartonjs