I’ve already viewed these questions and their answers:
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 createX509Certificate2
instance from certificate with private key stored in CNG,get
accessor onPrivateKey
property throws exception: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 inNew-SelfSignedCertificate
cmdlet call. For example, you can usemicrosoft enhanced rsa and aes cryptographic provider
provider as a parameter value.