Keyset does not exist although the PrivateKey is set

Viewed 1571

I'm trying to sign a message using CmsSigner and attaching X509 certificate:

public static byte[] Sign(X509Certificate2 certificate, string keyXml)
{
    ContentInfo contentInfo = new ContentInfo(new Oid("1.2.840.113549.1.7.1"), Encoding.ASCII.GetBytes("hello"));

    RSACryptoServiceProvider rsa = new RSACryptoServiceProvider();
    rsa.FromXmlString(keyXml);

    using (certificate.GetRSAPrivateKey())
    {
        certificate.PrivateKey = rsa;
    }
    var signer = new CmsSigner(certificate);

    signer.DigestAlgorithm = new Oid("2.16.840.1.101.3.4.2.1");
    signer.SignedAttributes.Add(new Pkcs9SigningTime());

    var signedCms = new SignedCms(contentInfo, false);
    signedCms.ComputeSignature(signer);
    var encodeByteBlock = signedCms.Encode();
    return encodeByteBlock;
}

the certificate doesn't have a key (HasPrivateKey is false) so I set it with the right key generated using OpenSSL:

openssl req -new -sha256 -x509 -days 7300 -out ca.crt -keyout ca.key.pem -nodes

I convert the ca.key.pem to XML.

But when ComputeSignature is called, this exception is thrown:

System.Security.Cryptography.CryptographicException: 'Keyset does not exist'

Stack trace:

at System.Security.Cryptography.Pkcs.PkcsUtils.CreateSignerEncodeInfo(CmsSigner signer, Boolean silent, SafeCryptProvHandle& hProv) at System.Security.Cryptography.Pkcs.SignedCms.Sign(CmsSigner signer, Boolean silent) at System.Security.Cryptography.Pkcs.SignedCms.ComputeSignature(CmsSigner signer, Boolean silent) at System.Security.Cryptography.Pkcs.SignedCms.ComputeSignature(CmsSigner signer) at ConsoleTest472.Program.Sign(X509Certificate2 certificate, String keyXml) in D:\me\Projects\ConsoleTest472\ConsoleTest472\Program.cs:line 56 at ConsoleTest472.Program.Main(String[] args) in D:\me\Projects\ConsoleTest472\ConsoleTest472\Program.cs:line 63

What is wrong with the code I'm using? doesn't the keyset is set when the private key is..set?

2 Answers

What is wrong with the code I'm using?

using (certificate.GetRSAPrivateKey())
{
    certificate.PrivateKey = rsa;
}

doesn't make much sense. You're acquiring the private key, then ignoring it, attempting to replace it, then disposing it.

A better version would be

var signedCms = new SignedCms(contentInfo, false);

using (X509Certificate2 certWithKey = certificate.CopyWithPrivateKey(rsa))
{
    var signer = new CmsSigner(certWithKey);

    signer.DigestAlgorithm = new Oid("2.16.840.1.101.3.4.2.1");
    signer.SignedAttributes.Add(new Pkcs9SigningTime());

    signedCms.ComputeSignature(signer);
}

var encodeByteBlock = signedCms.Encode();
return encodeByteBlock;

Which does the work of binding the private key to a copy of the certificate (rather than do mutation), and does whatever is necessary to make that work (in this case, it'll end up replacing the RSACryptoServiceProvider key with an RSACng key, because the platform only supports CNG for memory-only (ephemeral) keys).

The private key must be either:

  • in the MY Key Store of the user executing the code
  • in the MY Key Store of the Machine, with permission granted to the user
  • stored together with the certificate in a PFX/PKCS#12 file and loaded as X509Certificate2 with the password protecting it.
Related