Search code examples
c#visual-studioauthenticationiisx509certificate

Invalid provider type specified one more time


I’ve already viewed these questions and their answers:

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"  

Then:

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

Solution

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