Keyset does not exist

Problem

You are trying to access the PrivateKey property of an X509Certificate object from within a web application hosted in IIS.
You get the following exception:

Keyset does not exist
Stacktrace:
System.Security.Cryptography.CryptographicException: Keyset does not exist
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)
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 MY_CONTROLLER_CLASS.CONTROLLER_ACTION()
at lambda_method(ExecutionScope , ControllerBase , Object[] )
at System.Web.Mvc.Async.ReflectedAsyncActionDescriptor.c__DisplayClass7.b__5(IAsyncResult asyncResult)
at System.Web.Mvc.Async.AsyncResultWrapper.WrappedAsyncResult`1.End()
at System.Web.Mvc.Async.AsyncResultWrapper.End[TResult](IAsyncResult asyncResult, Object tag)
at System.Web.Mvc.Async.AsyncControllerActionInvoker.c__DisplayClass39.b__38(IAsyncResult asyncResult)
at System.Web.Mvc.Async.AsyncResultWrapper.WrappedAsyncResult`1.End()
at System.Web.Mvc.Async.AsyncResultWrapper.End[TResult](IAsyncResult asyncResult, Object tag)
at System.Web.Mvc.Async.AsyncControllerActionInvoker.c__DisplayClass31.c__DisplayClass33.b__2d()
at System.Web.Mvc.Async.AsyncControllerActionInvoker.c__DisplayClass49.b__43()
at System.Web.Mvc.Async.AsyncControllerActionInvoker.c__DisplayClass31.b__30(IAsyncResult asyncResult)
at System.Web.Mvc.Async.AsyncResultWrapper.WrappedAsyncResult`1.End()
at System.Web.Mvc.Async.AsyncResultWrapper.End[TResult](IAsyncResult asyncResult, Object tag)
at System.Web.Mvc.Async.AsyncControllerActionInvoker.c__DisplayClass1f.c__DisplayClass24.b__1a()
at System.Web.Mvc.Async.AsyncControllerActionInvoker.c__DisplayClass1f.b__1c(IAsyncResult asyncResult)
at System.Web.Mvc.Async.AsyncControllerActionInvoker.EndInvokeAction(IAsyncResult asyncResult)
at System.Web.Mvc.AsyncController.c__DisplayClass17.b__12(IAsyncResult asyncResult)
at System.Web.Mvc.Async.AsyncResultWrapper.c__DisplayClass4.b__3(IAsyncResult ar)
at System.Web.Mvc.Async.AsyncResultWrapper.WrappedAsyncResult`1.End()
at System.Web.Mvc.AsyncController.EndExecuteCore(IAsyncResult asyncResult)
at System.Web.Mvc.Async.AsyncResultWrapper.c__DisplayClass4.b__3(IAsyncResult ar)
at System.Web.Mvc.Async.AsyncResultWrapper.WrappedAsyncResult`1.End()
at System.Web.Mvc.AsyncController.EndExecute(IAsyncResult asyncResult)
at System.Web.Mvc.MvcHandler.c__DisplayClass8.b__3(IAsyncResult asyncResult)
at System.Web.Mvc.Async.AsyncResultWrapper.c__DisplayClass4.b__3(IAsyncResult ar)
at System.Web.Mvc.Async.AsyncResultWrapper.WrappedAsyncResult`1.End()
at System.Web.Mvc.MvcHandler.EndProcessRequest(IAsyncResult asyncResult)
at System.Web.Mvc.MvcHttpHandler.c__DisplayClass6.b__1(IAsyncResult asyncResult)
at System.Web.Mvc.Async.AsyncResultWrapper.c__DisplayClass4.b__3(IAsyncResult ar)
at System.Web.Mvc.Async.AsyncResultWrapper.WrappedAsyncResult`1.End()
at System.Web.Mvc.MvcHttpHandler.EndProcessRequest(IAsyncResult asyncResult)
at System.Web.HttpApplication.CallHandlerExecutionStep.OnAsyncHandlerCompletion(IAsyncResult ar)

What did I do to cause this?
Well, I have a self-signed certificate with private key (RSA 1024) for signing messages. I ran the following code from within IIS (well, actually from within SharePoint 2010 hacking my way through with MVC – but this is unrelated):

// Sign using private key
var privateCertificate = GetCertificate(PrivateCertificate_SN);
if(privateCertificate == null)
  throw new Exception("Private certificate has not been installed");
rsaCryptoSP = privateCertificate.PrivateKey as RSACryptoServiceProvider;
byte[] signature = rsaCryptoSP.SignData(encryptedCipher, "MD5");
string signatureString = Convert.ToBase64String(signature).Replace("+", "-").Replace("/", "_");

The call to privateCertificate.PrivateKey is obviously the culprit. However, “this worked on my dev machine!”.
And as is mostly the case with non-dev machines, security is an issue.

Cause

I looked around the web and figured that although the certificate holding the key is accessible to IIS, the private key doesn’t seem to be.
After some more digging I came to a tool called “FindPrivateKey” released by MSDN. It’s important to understand that although certificates are stored in the registry, private keys are still held in physical files on disk. The FindPrivateKey tool tells you where a certificate’s private key is located. The security in the directory that stores those private keys cannot be set from Windows Explorer easily, we’ll need to use cacls which displays and allows you to edit Access Control Lists (ACL’s) for files and folders.

You can download the “Sample” of FindPrivateKey on the “.NET Framework 3.5” version of the article.
The rest of the article is also a very good explanation of what you should do (summarized here):

  1. Find your certificate’s thumbprint (MMC > Certificate Snapin > Double click > Details > Scroll down)
  2. Execute:

    findprivatekey.exe My LocalMachine -t "<your_thumbprint>"

  3. Note the output you receive:

    Private key directory:
    C:\ProgramData\Microsoft\Crypto\RSA\MachineKeys
    Private key file name:
    6451f168c4feeb5a972a112b45658366_ed6e0ecd-8525-4f86-be3a-33ff26e5f563

  4. Execute the following which grants read permissions on the private key file to your IIS application pool identity

    cacls C:\ProgramData\Microsoft\Crypto\RSA\MachineKeys\<private_key_file_name> /E /G "<USERNAME>":R

Still doesn’t work, right? Read on.
Thanks to a colleague, we figured out that when going through IIS it seems the account used to get the private key file is not the application pool identity, but rather the local user “IUSR”. Whether it’s a configuration error on our side or a bug in IIS, substitute the username in the cacls command by “IUSR” and you’re set.

Advertisements

4 thoughts on “Keyset does not exist

  1. Hi,
    Thanks you saved my day, not straight though. The IIS application pool identity for my windows 8.1 machine is “IIS AppPool\DefaultAppPool”. This and IUSR, with both of them it worked.
    Thanks
    Vivek

  2. HI,

    I tried the above but still it is giving me same error. Anything else that i need to do to get this fixed ? Any help is appreciated.

    • Hi Sam,

      Did you validate that the steps I mention above actually made a change to your system?
      If not, try again until it works. If so, I suggest you continue reading online to find other causes of this problem.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s