I'm creating a web service and associated client that will use JWT encrypted by X509Certficates. When I use a symmetric key to encrypt and decrypt the tokens, everything works fine. However, I want to use encryption based on X509Certficates in production, and when I try one of those, I get these exceptions:
When running on .NET Core:
Microsoft.IdentityModel.Tokens.SecurityTokenKeyWrapException:
IDX10659: UnwrapKey failed, exception from crypto operation:
'Internal.Cryptography.CryptoThrowHelper+WindowsCryptographicException:
Key does not exist'
When running on .NET Framework 4.7 (note how the inner exception differs):
Microsoft.IdentityModel.Tokens.SecurityTokenKeyWrapException:
IDX10659: UnwrapKey failed, exception from crypto operation:
'System.Security.Cryptography.CryptographicException:
Error occurred while decoding OAEP padding.'
Switching from a SymmetricSecurityKey
to a X509SecurityKey
is just changing the value provided for an instance of EncryptingCredentials
, so I was expecting everything to just work.
Here's my code to generate a token:
public string Generate(NodeEntitlements entitlements)
{
if (entitlements == null)
{
throw new ArgumentNullException(nameof(entitlements));
}
var claims = CreateClaims(entitlements);
var claimsIdentity = new ClaimsIdentity(claims);
var securityTokenDescriptor = new SecurityTokenDescriptor
{
Subject = claimsIdentity,
NotBefore = entitlements.NotBefore.UtcDateTime,
Expires = entitlements.NotAfter.UtcDateTime,
IssuedAt = DateTimeOffset.Now.UtcDateTime,
Issuer = "https://example.com",
Audience = "https://example.com",
SigningCredentials = SigningCredentials,
EncryptingCredentials = EncryptingCredentials
};
var handler = new JwtSecurityTokenHandler();
var token = handler.CreateToken(securityTokenDescriptor);
return handler.WriteToken(token);
}
And here's my code to verify the same token (I've commented the line where the exception is thrown):
public VerificationResult Verify(string tokenString)
{
var validationParameters = new TokenValidationParameters
{
ValidateAudience = true,
ValidAudience = "https://example.com",
ValidateIssuer = true,
ValidIssuer = "https://example.com",
ValidateLifetime = true,
RequireExpirationTime = true,
RequireSignedTokens = SigningKey != null,
ClockSkew = TimeSpan.FromSeconds(60),
IssuerSigningKey = SigningKey,
ValidateIssuerSigningKey = SigningKey != null,
TokenDecryptionKey = EncryptionKey
};
try
{
var handler = new JwtSecurityTokenHandler();
// Exception is thrown here
var principal =
handler.ValidateToken(tokenString, validationParameters, out var token);
var entitlementIdClaim = principal.FindFirst("id");
if (entitlementIdClaim == null)
{
return VerificationResult.IdentifierNotPresent;
}
return VerificationResult.Valid;
}
catch (SecurityTokenException ex)
{
Console.WriteLine(ex);
return VerificationResult.InvalidToken;
}
}
To create the EncryptingCredentials
based on a certificate:
public static EncryptingCredentials CreateCertificateEncryptionCredentials()
{
var certificate = FindCertificate();
var key = new X509SecurityKey(certificate);
var result = new EncryptingCredentials(
key, SecurityAlgorithms.RsaOAEP, SecurityAlgorithms.Aes256CbcHmacSha512);
return result;
}
These methods are taken from a minimal repro (GitHub link) that I've extracted from my original codebase; there's a bit much code to publish here, but most of it is just infrastructure. The repro works entirely in-process and doesn't use anything from ASP.NET.
I've reproduced the problem with multiple different X509Certficates, on three different machines, under two different user accounts, so I think I've eliminated the certificate as the source of the problem. Signing works properly from end to end in each test, which I think shows that both the public and private keys from the certificate are properly accessible.
Why does decryption fail only with an X509SecurityKey
and not with a SymmetricSecurityKey
?
What's the right way to encrypt/decrypt JWT tokens with X509Certficates? Or, what's a workaround for this problem?
Updated: Simplified the code for reproducing the exception
Try using:
new RsaSecurityKey(certificate.GetRSAPrivateKey().ExportParameters(true));
instead of X509SecurityKey
.
There appears to be a bug in the implementation where it is not allowing JIT unwrapping of RSA keys.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With