Lesson learned: Cannot unlock software certificate pincode from code

Due to a number of reasons we needed to ensure that the sender of a message, which we were going to sign, really knew the secret password/pincode to the certificate. The basic design of this is actually flawed but we could not change this as we only replace one component in a larger flow of information. But that is not relevant to the topic of this blog...

We were looking at utilizing Windows certificate store to hold the certificates so it was natural (we thought) to associate a pincode when importing the certificate. What happens is that when you try to open the certificate you will be prompted by Windows to enter the pincode through an interactive dialog. Now all we had to do was to write some code that, in run-time, unlocked the certificate without displaying the interactive dialog.

We first tried used the CspParameters.KeyPassword property, but that didn't work due to what seems to a problem with .NET. This would have been really nice and clean solution, using new .NET 2.0 functionality to solve a problem. Had the fortune to discuss this with Shawn Farkas who had some good ideas.

As a result we tried doing CryptoAPI and .NET together. Our code looked something like this:

// Our method for getting a certificate
X509Certificate2 cert = InternalImpl.RetrieveX509CertificateByOrgNumber("984718268");

bResult = CryptAcquireCertificatePrivateKey(cert.Handle, 0, IntPtr.Zero, hCryptProv, dwKeySpec, boolRes);
if (!bResult)
{
int err = Marshal.GetLastWin32Error();
throw new Win32Exception();
}

bResult = CryptSetProvParam(hCryptProv, PP_SIGNATURE_PIN, new ASCIIEncoding().GetBytes("pincode"), 0)

// Do something interesting with the hCryptProv...

But we just kept getting error 0x00000057 (ERROR_INVALID_PARAMETER) when calling CryptSetProvParam. We changed the code and dropped the X509Certificate2 class and used a pure CryptoAPI implementation, but the problem remained the same.

What to do? I sent a mail to an internal mailing list and got a quick response, "CryptSetProvParam(PP_SIGNATURE_PIN) is used to set a PIN on a provider context for a smartcard csp. It is not recognized by software csps." This means that what we were trying to do is not supported!

I guess that the reason for this is that if you lock down a certificate in certificate store you do it to force user intervention. If you could code around this you would break the security.

Lesson learned:
Certificates that have been locked down using a pincode in the certificate store cannot be unlocked using code. A user will always have to enter the pin interactively.

I hope that describing this experience will help someone in the future.

How did we solve our problem?
We decided to put the certificate files (.p12 and .cert) on disk instead and open them using one of the constructor of X509Certificate2 that takes a password as parameter.
cert = new X509Certificate2(fullpath, password);

Actually this is the same design as the old system used, I guess there was a reason for it... But we really wanted to use the certificate store, so did an attempt and failed.