“Invalid provider type specified” CryptographicException when trying to load private key of certificate

◇◆丶佛笑我妖孽 提交于 2019-12-17 07:16:50

问题


I'm trying to read the private key of a certificate which has been shared with me by a third-party service provider, so I can use it to encrypt some XML before sending it to them over the wire. I'm doing so programmatically in C#, but I think this is a permissions or misconfiguration issue, so I'll focus on the facts which seem to be most relevant:

  • I don't think this issue is code-related; my code works on other computers, and the issue affects sample code from Microsoft.
  • The certificate was provided as a PFX file and is just for test purposes, so it also includes a dummy certification authority.
  • Using MMC.exe, I can import the certificate into the personal store for the local machine, before granting permissions on the private key to all relevant accounts, and dragging and dropping the certification authority into the Trusted Root Certification Authorities.
  • Using C#, I can load the certificate (identified by its thumbprint) and verify that it has a private key using X509Certificate2.HasPrivateKey. However, trying to read the key causes an error. In .NET a CryptographicException is thrown with the message "Invalid provider type specified" upon trying to access the property X509Certificate2.PrivateKey. In Win32, calling the method CryptAcquireCertificatePrivateKey returns the equivalent HRESULT, NTE_BAD_PROV_TYPE.
  • This is the same exception which also occurs when using two of Microsoft's own code samples to read the private key of the certificate.
  • Installing the same certificate in the equivalent store for the current user, instead of the local machine, allows the private key to be successfully loaded.
  • I'm on Windows 8.1 with local administrator rights, and I've tried running my code in both normal and elevated modes. Colleagues on Windows 7 and Windows 8 have been able to load the key from the local machine store for the same certificate.
  • I can successfully read the private key of the self-signed IIS test certificate, which is in the same store location.
  • I am already targeting .NET 4.5 (this error has been reported with some older versions of the framework).
  • I don't think this is a problem with certificate templates, because I would expect that to affect both the local machine and current-user stores equally?

Unlike my colleagues, I have made multiple previous attempts to uninstall and re-install the certificate in various ways, including via IIS Manager and also including an older certificate from the same issuer. I can't see any traces of old or duplicate certificates in MMC. However, I do have many private key files of identical size which, based on the last-write time, must have been left behind after my various installation attempts. These are found at the following locations, for the local machine and current user stores respectively:

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

c:\Users\\AppData\Roaming\Microsoft\Crypto\RSA\S-1-5-21-[rest of user ID]

So, can anyone please advise whether:

  • It's a good idea to uninstall the certificate using MMC, delete all those files which look like orphaned private keys, and then re-install the certificate and try again?
  • There any other files which I should try to manually delete?
  • There's anything else I should try?

UPDATE - Added a code sample showing an attempt to read a private key:

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

回答1:


I had the same problem on Windows 8 and Server 2012/2012 R2 with two new certificates I recently received. On Windows 10, the problem no longer occurs (but that does not help me, as the code manipulating the certificate is used on a server). While the solution of Joe Strommen in principle works, the different private key model would require massive change to the code using the certificates. I find that a better solution is to convert the private key from CNG to RSA, as explained by Remy Blok here.

Remy uses OpenSSL and two older tools to accomplish the private key conversion, we wanted to automate it and developed an OpenSSL-only solution. Given MYCERT.pfx with private key password MYPWD in CNG format, these are the steps to get a new CONVERTED.pfx with private key in RSA format and same password:

  1. Extract public keys, full certificate chain:

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

  1. Extract private key:

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

  1. Convert private key to RSA format:

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

  1. Merge public keys with RSA private key to new PFX:

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

If you load the converted pfx or import it in the Windows certificate store instead of the CNG format pfx, the problem goes away and the C# code does not need to change.

One additional gotcha that I encountered when automating this: we use long generated passwords for the private key and the password may contain ". For the OpenSSL command line, " characters inside the password must be escaped as "".




回答2:


The link to Alejandro's blog is key.

I believe this is because the certificate is stored on your machine with the CNG ("Crypto Next-Generation") API. The old .NET API is not compatible with it, so it doesn't work.

You can use the Security.Cryptography wrapper for this API (available on Codeplex). This adds extension methods to X509Certificate/X509Certificate2, so your code will look something like:

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;
}

Unfortunately the object model for CNG private keys is quite a bit different. I'm not sure if you can export them to XML like in your original code sample...in my case I just needed to sign some data with the private key.




回答3:


Here is another reason that this can happen, this was a weird issue and after struggling for a day I solved the issue. As an experiment I changed permission for "C:\ProgramData\Microsoft\Crypto\RSA\MachineKeys" folder which holds private key data for certificates using Machine key store. When you change permission for this folder all privatekeys shows up as "Microsoft Software KSP provider" which is not the provider (in my case they are supposed to be " Microsoft RSA Schannel Cryptographic Provider").

Solution: Reset permissions to Machinekeys folder

Original permission for this folder can be found in here. In my case I have changed permission for "Everyone", gave read permissions where it removed "Special permissions" tick. So I checked with one of my team member (Right click folder> Properties > Security > Advanced > select "Everyone" > Edit > Click "Advanced settings" in permission check box list

Hope this will save someone's day!

Here is where I found the answer source, credit goes to him for documenting this.




回答4:


In my case, I was trying to use a self-signed certificate with PowerShell's New-SelfSignedCertificate command. By default, it will generate a certificate using the CNG (Crypto-Next Generation) API instead of the older/classic crypto CAPI. Some older pieces of code will have trouble with this; in my case it was an older version of the IdentityServer STS provider.

By adding this at the end of my New-SelfSignedCertificate command, I got past the issue:

-KeySpec KeyExchange

Reference on the switch for the powershell command:

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




回答5:


In my case, the following code worked fine in localhost (both NET 3.5 and 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...

But it failed when deployed to an Azure Web App, at certificate.PrivateKey

It worked by changing the code as follows:

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

A whole day of work lost thanks to Microsoft Azure, once again in my life.




回答6:


I also had this issue and after attempting the suggestions in this post without success. I was able to resolve my issue by reloading the certificate with the Digicert certificate utility https://www.digicert.com/util/. This allows one to select the provider to load the certificate into. In my case loading the certificate into the Microsoft RSA Schannel Cryptographic Provider provider where I had expected it to be in the first place resolved the issue.




回答7:


As many other answers have pointed out, this issue arises when the private key is a Windows Cryptography: Next Generation (CNG) key instead of a "classic" Windows Cryptographic API (CAPI) key.

Beginning with .NET Framework 4.6 the private key (assuming it's an RSA key) can be accessed via an extension method on X509Certificate2: cert.GetRSAPrivateKey().

When the private key is held by CNG the GetRSAPrivateKey extension method will return an RSACng object (new to the framework in 4.6). Because CNG has a pass-through to read older CAPI software keys, GetRSAPrivateKey will usually return an RSACng even for a CAPI key; but if CNG can't load it (e.g. it's an HSM key with no CNG driver) then GetRSAPrivateKey will return an RSACryptoServiceProvider.

Note that the return type for GetRSAPrivateKey is RSA. Beginning with .NET Framework v4.6 you shouldn't need to cast beyond RSA for standard operations; the only reason to use RSACng or RSACryptoServiceProvider is when you need to interop with programs or libraries that use the NCRYPT_KEY_HANDLE or the key identifier (or opening a persisted key by name). (.NET Framework v4.6 had a lot of places that still cast the input object to RSACryptoServiceProvider, but those were all eliminated by 4.6.2 (of course, that's more than 2 years ago at this point)).

ECDSA certificate support was added in 4.6.1 via a GetECDsaPrivateKey extension method, and DSA was upgraded in 4.6.2 via GetDSAPrivateKey.

On .NET Core the return value from Get[Algorithm]PrivateKey changes depending on the OS. For RSA it's RSACng/RSACryptoServiceProvider on Windows, RSAOpenSsl on Linux (or any UNIX-like OS except macOS), and a non-public type on macOS (meaning you can't cast it beyond RSA).




回答8:


I faced the same problem in our IIS app:

 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)

Regenerating certs as mentioned here didnt help. I also noticed that test console app works fine under pool user.

Problem disappeared after clearing "Enable 32-bit Applications" setting for IIS app pool.




回答9:


Powershell version of the answer from @berend-engelbrecht, assuming openssl installed 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



回答10:


Using Visual Studio 2019 and IISExpress, I was able to correct this problem by removing the following flag when loading the .pfx|.p12 file:

X509KeyStorageFlags.MachineKeySet

Before:

X509KeyStorageFlags.MachineKeySet | X509KeyStorageFlags.PersistKeySet | X509KeyStorageFlags.Exportable

After:

X509KeyStorageFlags.PersistKeySet | X509KeyStorageFlags.Exportable

Normally, I'll load the cert this way:

var myCert = new X509Certificate2("mykey.pfx", "mypassword", X509KeyStorageFlags.MachineKeySet | X509KeyStorageFlags.PersistKeySet | X509KeyStorageFlags.Exportable);

This does NOT raise an Exception, instead the Exception is raised when attempting to use the certificate (or in my case, when trying to obtain the .PrivateKey). I've found this issue can be caused when the invoking user has insufficient permissions.

Since I rely on the MachineKeySet flag for some environments, my current solution is to swallow the Exception, change the flags, try again. :/

A more organic solution would be to test the permissions level and set this flag dynamically, however I'm not aware of a simple way to do this, hence the fallback.

NOTE: The .pfx file I'm using was created from a website that uses a JavaScript library (digitalbazaar/forge) which does NOT use CNG (Cryptography Next Generation) Key Storage Providers. here are many common causes for this same error (the most common fixes related to CNG extensions, which have -- unfortunately even changed namespaces in .NET versions), which throw the same error. Microsoft should ultimately be more verbose when throwing these types of errors.




回答11:


What worked for me: IIS / Application pool / Load User Profile = true



来源:https://stackoverflow.com/questions/22581811/invalid-provider-type-specified-cryptographicexception-when-trying-to-load-pri

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!