aspnetcore: Data protection throws `CryptographicException: Keyset does not exist` even when X509Certificate2 private key is specified programmatically
When configuring ASP.NET MVC Core application to use X509Certificate2
that is not in computer’s certificate store, application throws CryptographicException: Keyset does not exist
.
I am running ASP.NET MVC Core on top of .NET Framework 4.7.1, but I suspect that is not an issue. More details bellow.
This is sample code where I configure MVC service for DataProtection
in startup class:
var x509Certificate2 = CertificateLoader.Load();
services.AddDataProtection()
.SetApplicationName("MyApp")
.ProtectKeysWithCertificate(x509Certificate2)
.PersistKeysToFileSystem(new DirectoryInfo("D:\\Temp\\DataProtection"));
Reason for CryptographicException
seems to be that private class EncryptedXmlWithCertificateKeys
calls base class method EncryptedXml.DecryptEncryptedKey
. Base class has no knowledge of private _certificates
collection of subclass, so it probably goes to certificate store by default. Bellow are lines from EncryptedXmlWithCertificateKeys
class.
public override byte[] DecryptEncryptedKey(EncryptedKey encryptedKey)
{
byte[] key = base.DecryptEncryptedKey(encryptedKey);
if (key != null)
{
return key;
}
Relevant stack trace:
System.Security.Cryptography.Utils.CreateProvHandle(CspParameters parameters, bool randomKeyContainer) System.Security.Cryptography.Utils.GetKeyPairHelper(CspAlgorithmType keyType, CspParameters parameters, bool randomKeyContainer, int dwKeySize, ref SafeProvHandle safeProvHandle, ref SafeKeyHandle safeKeyHandle) System.Security.Cryptography.RSACryptoServiceProvider.GetKeyPair() System.Security.Cryptography.RSACryptoServiceProvider…ctor(int dwKeySize, CspParameters parameters, bool useDefaultKeySize) System.Security.Cryptography.X509Certificates.X509Certificate2.get_PrivateKey() System.Security.Cryptography.X509Certificates.RSACertificateExtensions.GetRSAPrivateKey(X509Certificate2 certificate) System.Security.Cryptography.CngLightup.GetRSAPrivateKey(X509Certificate2 cert) System.Security.Cryptography.Xml.EncryptedXml.DecryptEncryptedKey(EncryptedKey encryptedKey) Microsoft.AspNetCore.DataProtection.XmlEncryption.EncryptedXmlDecryptor+EncryptedXmlWithCertificateKeys.DecryptEncryptedKey(EncryptedKey encryptedKey) System.Security.Cryptography.Xml.EncryptedXml.GetDecryptionKey(EncryptedData encryptedData, string symmetricAlgorithmUri) System.Security.Cryptography.Xml.EncryptedXml.DecryptDocument() Microsoft.AspNetCore.DataProtection.XmlEncryption.EncryptedXmlDecryptor.Decrypt(XElement encryptedElement) Microsoft.AspNetCore.DataProtection.XmlEncryption.XmlEncryptionExtensions.DecryptElement(XElement element, IActivator activator) Microsoft.AspNetCore.DataProtection.KeyManagement.XmlKeyManager.Microsoft.AspNetCore.DataProtection.KeyManagement.Internal.IInternalXmlKeyManager.DeserializeDescriptorFromKeyElement(XElement keyElement) Microsoft.AspNetCore.DataProtection.KeyManagement.DeferredKey+<>c__DisplayClass1_0.<GetLazyDescriptorDelegate>b__0() System.Lazy<T>.CreateValue() System.Lazy<T>.get_Value() Microsoft.AspNetCore.DataProtection.KeyManagement.KeyBase.get_Descriptor() Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.CngGcmAuthenticatedEncryptorFactory.CreateEncryptorInstance(IKey key) Microsoft.AspNetCore.DataProtection.KeyManagement.KeyBase.CreateEncryptor() Microsoft.AspNetCore.DataProtection.KeyManagement.KeyRing+KeyHolder.GetEncryptorInstance(out bool isRevoked) Microsoft.AspNetCore.DataProtection.KeyManagement.KeyRing.get_DefaultAuthenticatedEncryptor() Microsoft.AspNetCore.DataProtection.KeyManagement.KeyRingBasedDataProtector.Protect(byte[] plaintext) Microsoft.AspNetCore.Authentication.SecureDataFormat<TData>.Protect(TData data, string purpose) Microsoft.AspNetCore.Authentication.SecureDataFormat<TData>.Protect(TData data) Microsoft.AspNetCore.Authentication.OpenIdConnect.OpenIdConnectHandler.WriteNonceCookie(string nonce) Microsoft.AspNetCore.Authentication.OpenIdConnect.OpenIdConnectHandler.HandleChallengeAsync(AuthenticationProperties properties) Microsoft.AspNetCore.Authentication.AuthenticationHandler<TOptions>.ChallengeAsync(AuthenticationProperties properties) Microsoft.AspNetCore.Authentication.AuthenticationService.ChallengeAsync(HttpContext context, string scheme, AuthenticationProperties properties) Microsoft.AspNetCore.Mvc.ChallengeResult.ExecuteResultAsync(ActionContext context) Microsoft.AspNetCore.Mvc.Internal.ResourceInvoker.InvokeResultAsync(IActionResult result) Microsoft.AspNetCore.Mvc.Internal.ResourceInvoker.InvokeAlwaysRunResultFilters() Microsoft.AspNetCore.Mvc.Internal.ResourceInvoker.InvokeFilterPipelineAsync() Microsoft.AspNetCore.Mvc.Internal.ResourceInvoker.InvokeAsync() Microsoft.AspNetCore.Builder.RouterMiddleware.Invoke(HttpContext httpContext) Microsoft.AspNetCore.Authentication.AuthenticationMiddleware.Invoke(HttpContext context)
About this issue
- Original URL
- State: closed
- Created 6 years ago
- Reactions: 1
- Comments: 20 (15 by maintainers)
This should be resolved in 2.2 with this change: https://github.com/aspnet/DataProtection/pull/314
That’s a good point. Your comment gave me an idea I don’t think we’ve explored. We could look into changing the precedence of the where we look for private keys. Presumably, if you pass us a X509Certificate2 object with a private key, that could come first, before we look at the Windows cert store.
Thoughts @blowdart ?
@natemcmaster Allow me to disagree here. I am configuring data-protection with “standalone” certificate loaded from file. I am not expecting that if I copy certificate in the Windows certificate store application suddenly blows up. Fact that there is dependency to the certificate store is hidden implementation detail.
At least, all this quirks should be clearly documented. We even had advise here to add
UnprotectKeysWithAnyCertificate()
which turned out not to be needed at all. It just proves to me that there is solid confusion how to configure this feature properly.One more thing, can you confirm this cert is not installed to any local machine or current user cert store? You can check quickly by running this command in powershell
I cannot reproduce this error on my own. If you have a standalone repro, please share.
Without a repro, the best I can offer is a guess, and my guess is that your certificate is actually being loaded from the cert store. For certificates not in the store, what is supposed to happen is this:
Microsoft.AspNetCore.DataProtection.XmlEncryption.EncryptedXmlDecryptor+EncryptedXmlWithCertificateKeys
and requests to decrypt an XML file.EncryptedXmlWithCertificateKeys.DecryptDocument
first needs to load the private key. This callsEncryptedXmlWithCertificateKeys.DecryptEncryptedKey
EncryptedXmlWithCertificateKeys.DecryptEncryptedKey
will first check if the key can be decrypted using the X509Store by callingbase.DecryptEncryptedKey
. This should return null if the X509Certificate is not in the CurrentUser\My and LocalMachine\My X509Store.EncryptedXmlWithCertificateKeys.DecryptEncryptedKey
then searches its own dictionary of X509Certificate2 objects which come from DataProtectionBuilder.ProtectKeysWithCertificate and .UnprotectKeysWithCertificate.Your stack trace suggestions the problem is coming from step 3 – checking the X509Store for the cert. Your stack trace indicates it threw from https://github.com/dotnet/corefx/blob/6d571f70a69a3a1dcf54f73ab828c545de40690a/src/System.Security.Cryptography.Xml/src/System/Security/Cryptography/Xml/EncryptedXml.cs#L438. If I’ve inspected the code correctly, the only way you get to this point is if your X509Certificate is in the LocalMachine\My and/or CurrentUser\My cert store.