C# – Invalid provider type specified one more time

authenticationciisvisual studiox509certificate

I’ve already viewed these questions and their answers:

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

How do I create client certificates for local testing of two-way authentication over SSL?

Invalid provider type specified. CryptographicException

And this blog that was referenced by the second question.

I suspect there is a problem with the certificate issuer, but I could be wrong.

Preface
I’m new at authentication (you can read that as idiot). The current project is to upgrade an existing web site and web application written in Visual Studio 2013 .Net 4.5.1 to Visual Studio 2017 2017 .Net version 4.6.1 to meet requirements for a new message broker.

The environment
Windows 10
Visual Studio 2017 (15.8.1)
IIS 10
Microsoft SQL Server 2017

Problem Description
The web server written in C# is throwing this error during authentication

    {"IDX10614: AsymmetricSecurityKey.GetSignatureFormater( 'http://www.w3.org/2001/04/xmldsig-more#rsa-sha256' ) threw an exception.
Key:
    'System.IdentityModel.Tokens.X509AsymmetricSecurityKey'\nSignatureAlgorithm: 'http://www.w3.org/2001/04/xmldsig-more#rsa-sha256', check to make sure the     SignatureAlgorithm is supported.\nException:'System.Security.Cryptography.CryptographicException: Invalid provider type specified.
       at System.Security.Cryptography.Utils.CreateProvHandle(CspParameters parameters, Boolean randomKeyContainer)
       at System.Security.Cryptography.Utils.GetKeyPairHelper(CspAlgorithmType keyType, CspParameters parameters, Boolean randomKeyContainer, Int32 dwKeySize, SafeProvHandle& safeProvHandle, SafeKeyHandle& safeKeyHandle)\r\n   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 System.IdentityModel.Tokens.X509AsymmetricSecurityKey.get_PrivateKey()\r\n   at System.IdentityModel.Tokens.X509AsymmetricSecurityKey.GetSignatureFormatter(String algorithm)
       at System.IdentityModel.Tokens.AsymmetricSignatureProvider..ctor(AsymmetricSecurityKey key, String algorithm, Boolean willCreateSignatures)'.
    If you only need to verify signatures the parameter 'willBeUseForSigning' should be false if the private key is not be available."}

Steps taken
Initially there was no certificate to check and that generated a different error.
In PowerShell running as Administrator

New-SelfSignedCertificate -Subject "CN= XxxxxxxXXCA" -DnsName "localhost" -FriendlyName "XxxxxxxXXCA" -KeyUsage DigitalSignature -KeyUsageProperty ALL -KeyAlgorithm RSA -KeyLength 2048 -CertStoreLocation "Cert:\CurrentUser\My"

Start MMC

Copy the certificate created to Local Machine Personal

Copy the certificate to Local Machine Trusted Root Certificate Authorities

Copy the certificate to Local Machine Trusted Publishers

Start website in IIS

Run Web Server in Visual Studio 2017

Use Advanced REST Client (ARC) to send a login request to the server from a client.

Walk through the authentication code in Visual Studio 2017 debugger in the code below:

The exception is thrown in the return statement.

    public string GenerateToken(string email)
    {

        X509Store store = new X509Store(StoreLocation.LocalMachine);
        store.Open(OpenFlags.ReadOnly);
        var certs = store.Certificates;

        X509Certificate2 signingCert =
        certs.Cast<X509Certificate2>().FirstOrDefault(cert => cert.FriendlyName == "XxxxxxxXXCA");
        SigningCredentials signingCredentials = new X509SigningCredentials(signingCert);

        var tokenHandler = new JwtSecurityTokenHandler();
        var now = DateTime.UtcNow;

        var customer = _customerService.GetCustomerByEmail(email); 

        var emailClaim = new Claim(ClaimTypes.Email, customer.Email, ClaimValueTypes.String);
        var userIdClaim = new Claim(ClaimTypes.NameIdentifier, customer.Id.ToString(), ClaimValueTypes.Integer);
        var roleClaim = new Claim(ClaimTypes.Role, "customer", ClaimValueTypes.String);

        var claimsList = new List<Claim> { emailClaim, userIdClaim, roleClaim };

        var tokenDescriptor = new SecurityTokenDescriptor()
        {
            AppliesToAddress = "http://localhost/api",
            SigningCredentials = signingCredentials,
            TokenIssuerName = "http://localhost",
            Lifetime = new Lifetime(now, now.AddDays(30)),
            //Lifetime = new Lifetime(now, now.AddDays(1)),
            Subject = new ClaimsIdentity(claimsList)
        };

        store.Close();
        return tokenHandler.WriteToken(tokenHandler.CreateToken(tokenDescriptor));
    }

Best Answer

New-SelfSignedCertificate cmdlet uses key storage provider by default. Most of .NET Framework (X509Certificate2 specifically) do not support CNG keys. As the result, when you create X509Certificate2 instance from certificate with private key stored in CNG, get accessor on PrivateKey property throws exception:

at System.Security.Cryptography.X509Certificates.X509Certificate2.get_PrivateKey()

I believe, you don't own the code that calls getter on PrivateKey, therefore, you need to re-create your certificate by explicitly providing legacy provider name in the -Provider parameter in New-SelfSignedCertificate cmdlet call. For example, you can use microsoft enhanced rsa and aes cryptographic provider provider as a parameter value.

Related Topic