ECDSA Certificates and Keys in .NET
It’s not uncommon to need to sign something with the private key of a certificate.
If you’re using RSA or DSA certificates, that’s been a fairly straight forward
process with the .NET Framework. If your certificate was an ECDSA certificate,
this was not a straight forward process. You often had to fall back to p/invoke
using CryptAquireCertificatePrivateKey
to obtain an NCrypt CNG key handle.
In the .NET Framework 4.6, this got a whole lot easier with the extension method
GetECDsaPrivateKey
.
I did run in to a problem with it though. I was getting an exception:
System.ArgumentException: Keys used with ECDsaCng algorithm must have an algorithm group of ECDsa.
I did a lot of double checking of the certificate, yes the certificate had an ECC key in it and the algorithm parameters explicitly defined the P256 curve for ECDSA. What gives?
I decided to fall back to old tricks and use CryptAquireCertificatePrivateKey
to create an instance of CngKey
, which then I would pass to ECDsaCng
so I
could sign something.
This, also, failed when passing the CngKey
to the constructor of ECDsaCng
.
Upon examining the CngKey
instance itself, CNG believed the key was ECDH, not
ECDSA. This was getting bizarre. Strangely enough, I had another certificate
where this worked perfectly fine and CNG was happy to announce that the algorithm
was ECDSA.
ECDH and ECDSA keys are interchangeable. You probably shouldn’t use the same key as a key agreement (ECDH) and signing (ECDSA), but ultimately they are just points on a curve. Yet somehow, CNG was making a distinction.
We can throw out the certificate itself being the source of the problem. If I opened the private key by name, it still believed the key was for ECDH. Clearly, this was an issue with the private key itself, not the certificate.
The cause of all of this mess turned out to be how the CNG’s key usage gets set. Every CNG key has a “key usage” property. For an ECC key, if the key is capable of doing key agreement, CNG decides that the key is ECDH, even though the key is also perfectly valid for signing and verifying.
Now the question is, how do we set the key usage? Key usage needs to be set
before the key is finalized, which means during creation. It cannot be changed
once NCryptFinalizeKey
has been called on the key.
My certificate and private key were imported as a PKCS#12 (.pfx) file through the install wizard. It’s during this process that the key’s usage is getting set.
After a bit of trial and error, I determined that setting the keyUsage extension on the certificate does not matter. That is, if the keyUsage extension was marked critical as set to signature (80), the CNG key would still get imported as AllUsages.
Eventually, a lightbulb came on and I examined the PKCS#12 file itself. It turns out that the PKCS#12 file was controlling how the private key’s usage was being set.
A PKCS#12 file contains a number of things, one of them is a “key attributes”
property. If you use OpenSSL to create a PKCS#12 file from a certificate and
private key, OpenSSL won’t set the key attributes to anything by default. If you
create the PKCS#12 file with the -keysig
option then the import wizard will
correctly set the key’s usage. If you create the PKCS#12 file with Windows, then
Windows will preserve the key usage during export when creating a PKCS#12 file.
Let’s sum up:
If you have an ECDSA certificate and private key and you create a PKCS#12 file
using OpenSSL, it will not set the key attributes unless you specify the -keysig
option. So to fix this problem, re-create the PKCS#12 file from OpenSSL with the
correct options.
Alternatively, you can wait for the .NET Framework 4.6.2. In this version of the
framework, the ECDsaCng
class is happy to use a ECDH key if it can. This is
also the only option you have if you really do want to have a key’s usage set
to ‘AllUsages’.