I'm trying to sign a file with ECDSA using the CNG API and a certificate from the Microsoft Certificate Store. I've read through a lot of documentation and and near done but I get hung up on importing the private key from the certificate. I've done this very same thing with RSA but it appears to be done very differently. Here's the code I have so far:
static void signFile()
{
X509Certificate2 myCert =
selectCert(StoreName.My,
StoreLocation.CurrentUser,
"Select a Certificate",
"Please select a certificate from the list below:");
Console.Write("Path for file to sign: ");
string path = Console.ReadLine();
TextReader file = null;
try
{
file = new StreamReader(path);
}
catch (Exception e)
{
Console.WriteLine(e.Message);
Console.Write("\nPress any key to return to the main menu: ");
Console.ReadKey();
}
UnicodeEncoding encoding = new UnicodeEncoding();
byte[] data = encoding.GetBytes(file.ReadToEnd());
ECDsaCng dsa = new ECDsaCng(
CngKey.Import(StringToByteArray(myCert.PrivateKey.ToString()),
CngKeyBlobFormat.EccPrivateBlob,
CngProvider.MicrosoftSoftwareKeyStorageProvider));
dsa.HashAlgorithm = CngAlgorithm.Sha384;
byte[] sig = dsa.SignData(data);
TextWriter signatureFile = new StreamWriter("signature.txt");
signatureFile.WriteLine("-----BEGIN SHA384 SIGNATURE-----" +
ByteArrayToString(sig) +
"-----END SHA384 SIGNATURE-----");
signatureFile.Close();
}
And I get the error
System.NotSupportedException: The certificate key algorithm is not supported.
My certificate is ECDSA_P256 sha384ECDSA with the following extensions:
Digital Signature, Non-repudiation, independent signing revocation list (CRL), CRL Signing (CRL) (c2)
Server Authentication (1.3.6.1.5.5.7.3.1)
Client Authentication (1.3.6.1.5.5.7.3.2)
Code Signing (1.3.6.1.5.5.7.3.3)
Unknown Key Usage (1.3.6.1.4.1.311.2.1.22)
Unknown Key Usage (1.3.6.1.4.1.311.2.1.21)
IKE-intermediary IP-security (1.3.6.1.5.5.8.2.2)
It would appear as if the certificate was the problem but I'm not sure if it could be the code or not.
Here's my certificate with the public key:
Certificate:
Data:
Version: 3 (0x2)
Serial Number: 2 (0x2)
Signature Algorithm: ecdsa-with-SHA384
Issuer: C=##, O=#######, OU=#####, OU=#####, CN=###########
Validity
Not Before: Apr 27 16:35:51 2012 GMT
Not After : Apr 26 16:35:51 2017 GMT
Subject: C=##, O=###########, OU=#####, CN=#############
Subject Public Key Info:
Public Key Algorithm: id-ecPublicKey
Public-Key: (256 bit)
pub:
04:fc:d5:ce:ad:1f:0c:19:b9:3d:2b:bd:7d:f0:8c:
44:46:db:e3:42:14:b1:1a:9f:7c:ab:e1:be:ad:a5:
0c:03:2d:0f:ff:3f:10:d4:69:eb:4c:82:a1:2a:61:
56:45:03:04:a6:49:f7:16:6e:dd:60:22:c6:20:c5:
4d:44:49:21:41
ASN1 OID: prime256v1
X509v3 extensions:
X509v3 Key Usage: critical
Digital Signature, Non Repudiation, CRL Sign
X509v3 Extended Key Usage: critical
TLS Web Server Authentication, TLS Web Client Authentication, Co
de Signing, Microsoft Commercial Code Signing, Microsoft Individual Code Signing
, 1.3.6.1.5.5.8.2.2
X509v3 Authority Key Identifier:
DirName:/C=##/O=#######/OU=#####/OU=#####/CN=######
serial:01
X509v3 Subject Key Identifier:
B7:A8:F9:55:9A:43:9E:BE:1C:4B:62:52:91:C2:F1:39:72:E1:CE:1B
X509v3 Basic Constraints: critical
CA:FALSE
Signature Algorithm: ecdsa-with-SHA384
30:81:88:02:42:01:75:55:f3:64:f9:aa:2a:66:55:b1:ca:dc:
86:ac:1f:7d:2a:ec:10:87:db:74:88:0e:77:e3:18:82:15:a7:
32:91:1a:2d:ea:07:2e:78:8d:dc:8a:18:3c:2b:5a:9b:6a:0f:
97:f6:f8:8d:c5:fc:0e:9f:20:e9:b0:16:90:1a:c4:58:ac:02:
42:01:dc:b3:88:ae:44:54:c4:e0:b7:c2:37:88:0b:19:6b:96:
99:f7:21:12:45:12:21:e5:ab:83:39:a6:47:3a:08:87:b0:fa:
0e:31:1b:97:83:8d:65:30:a1:43:c1:82:27:77:6e:93:89:1b:
bd:57:b1:7a:54:9e:cc:e1:44:cc:74:16:c5
I've been battling ECDsa and CngKey with X509 certificates for a looong time and had the exact same issues. We ended up creating our own CngKeys with ECDsa_P256 SHA256, but I do believe that i learnt something digging in to CryptoApi:
When you have a certificate that is marked with "Server Authentication (1.3.6.1.5.5.7.3.1)" (use as SSL certificate), your certificate will contain the key exchange algorithm ECDH. And somehow this "takes precedence" over the ECDsa algorithm group. Thus you get the dreaded "The certificate key algorithm is not supported".
I spent over an hour with Symantec looking over my shoulder and they could not solve the puzzle, so they gave up with a "Sorry, we don't support use of SSL certs for anything but SSL".
You can get your private CngKey from the cert with CLRSecurity from Codeplex (http://clrsecurity.codeplex.com/). This gives your x509Certificate2 an extension that allows for this code:
Inspect "k" and see that your algorithm group is probably something else than expected. Mine was ECDH...
Solution I am trying now is to set up a new CA server force it to do exactly what I want. Basically that would be an X509 cert that is ONLY used for code signing...
"X509v3 Key Usage: critical Digital Signature" might have to be the ONLY usage allowed...
Hope this helps someone else out there :-)
If you are running Windows Vista or Windows 2008, the CngKeyBlobFormat.EccPrivateBlob is not supported. What OS are you using? CngKey.Import throws CryptographicException only on some machines
.NET 4.6.1 solved the core needs of this problem. The new code would be
.NET 4.6.1 also fixed the issue where some cert keys come back as ECDH and thus fail. (Well, it didn't solve the problem of some private keys being considered ECDH -- which had nothing to do with the Server Auth EKU, but was a good guess -- but considers those keys to be valid now).