2

I am getting one of the following Exceptions while trying to get a private key from X509Certificate2 certificate:

System.Security.Cryptography.CryptographicException: Invalid provider type specified.

OR

System.Security.Cryptography.CryptographicException: Key does not exist at the following line of code: RSACryptoServiceProvider rsaKey = (RSACryptoServiceProvider)digiSignCert.PrivateKey;

Stacktrace:

System.Security.Cryptography.CryptographicException: Key does not exist. at System.Security.Cryptography.Utils.GetKeyPairHelper(CspAlgorithmType keyType, CspParameters parameters, Boolean randomKeyContainer, Int32 dwKeySize, SafeProvHandle& safeProvHandle, SafeKeyHandle& safeKeyHandle) at System.Security.Cryptography.RSACryptoServiceProvider.GetKeyPair() at System.Security.Cryptography.RSACryptoServiceProvider..ctor(Int32 dwKeySize, CspParameters parameters, Boolean useDefaultKeySize) at System.Security.Cryptography.X509Certificates.X509Certificate2.get_PrivateKey() at Api.CertificateUtil.GetSignedXml(String xml, X509Certificate2 privateCert)

Code:

public static RSACryptoServiceProvider rsaKey = null;
public X509Certificate2 _PrivateCert;

public APISearch()
{
    byte[] privateCert = null;//We get the actual certificate file data here
    GetPrivateCerificate(privateCert, "abc@123");
    GetSignedXml(_PrivateCert);
}

public void GetPrivateCerificate(byte[] privateCert, string pwd)
{
    _PrivateCert = new X509Certificate2(privateCert, pwd, X509KeyStorageFlags.MachineKeySet | X509KeyStorageFlags.PersistKeySet | X509KeyStorageFlags.Exportable);
}

public void GetSignedXml(X509Certificate2 privateCert)
{
    rsaKey = (RSACryptoServiceProvider)privateCert.PrivateKey; //Occassional Exception
}

Expected result: (RSACryptoServiceProvider)privateCert.PrivateKey should always produce a private key.

Actual result: Sometimes the aforementioned exceptions are thrown at this line:

rsaKey = (RSACryptoServiceProvider)privateCert.PrivateKey;

and sometimes the private key is successfully being fetched from the certificate file. As of now, we have been unable to track the pattern of this problem.

Bender Bending
  • 729
  • 8
  • 25

1 Answers1

13

RSACryptoServiceProvider is a type that performs RSA via the Window Cryptographic API (CAPI) library. When .NET was first created CAPI was new and always the right answer (on Windows). Starting in Windows Vista there was a new library: Cryptography: Next Generation (CNG). CNG, for compatibility, understands how to work with CAPI. But CAPI can't "be CAPI" and "understand CNG". The exceptions you are seeing are when a PFX indicated that the private keys should be stored via CNG (or an in-store cert indicates that it's private keys are stored via CNG).

When .NET Framework was adding RSACng it was decided that too many people had already written the line (RSACryptoServiceProvider)cert.PrivateKey, so that property can't ever return an RSACng instance. Instead, in .NET 4.6 new (extension) methods were made: cert.GetRSAPublicKey() and cert.GetRSAPrivateKey(), they return RSA instead of AsymmetricAlgorithm. Also in .NET 4.6 the RSA base class was enhanced to have the Sign/Verify and Encrypt/Decrypt operations moved down (though with different signatures, since RSA has gained new options since CAPI was written).

Expected result: (RSACryptoServiceProvider)privateCert.PrivateKey should always produce a private key.

The actual truth is cert.PrivateKey (and cert.PublicKey.Key) is/are soft-deprecated. You shouldn't call it/them anymore. RSA (4.6), ECDSA (4.6.1) and DSA (4.6.2) all have Get[Algorithm]{Public|Private}Key methods.

  • (RSACryptoServiceProvider)cert.PrivateKey => cert.GetRSAPrivateKey()
  • rsaCSP.Encrypt(data, false) => rsa.Encrypt(data, RSAEncryptionPadding.Pkcs1)
  • rsaCSP.Encrypt(data, true) => rsa.Encrypt(data, RSAEncryptionPadding.OaepSHA1)
  • rsaCSP.SignData(data, "SHA256") => rsa.SignData(data, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1)

Similar for Decrypt, SignHash, VerifyData, VerifyHash; and similar for ECDsa and DSA.

Finally, please don't hard-cast the return value of those methods, it changes as it needs to... on Windows it can return either RSACng or RSACryptoServiceProvider, on Linux (.NET Core) it currently returns RSAOpenSsl, and on macOS (.NET Core) it returns an uncastable object.

bartonjs
  • 30,352
  • 2
  • 71
  • 111