Search code examples
c#windowscertificatex509certificatepkcs#12

C# import certificate and key (PFX) into CNG/KSP


I'm trying to import a certificate/key (PFX) into Windows Certificate Store, using a KSP to store the key. The idea behind that is to be able to use a custom KSP, even if for now i'm simply using the default one.

Storing the key into the KSP using CNG is somehow the "easy" part, and here's the code that works for me:

X509Certificate2 cert = new X509Certificate2(@"c:\temp\test.pfx", "test123", X509KeyStorageFlags.Exportable);
RSACng rsaCNG = new RSACng();
rsaCNG.FromXmlString(cert.GetRSAPrivateKey().ToXmlString(true));
var keyData = rsaCNG.Key.Export(CngKeyBlobFormat.GenericPrivateBlob);
var keyParams = new CngKeyCreationParameters
{
    ExportPolicy = CngExportPolicies.None,
    KeyCreationOptions = CngKeyCreationOptions.MachineKey,
    Provider = CngProvider.MicrosoftSoftwareKeyStorageProvider
};
keyParams.Parameters.Add(new CngProperty(CngKeyBlobFormat.GenericPrivateBlob.Format, keyData, CngPropertyOptions.None));
var key = CngKey.Create(CngAlgorithm.Rsa, "testKey", keyParams);

However, i'm then unable to associate this key with the certificate, in order to store the certificate correctly into the Windows store. The following code:

rsaCNG = new RSACng(key);
X509Certificate2 certOnly = new X509Certificate2(cert.Export(X509ContentType.Cert));
certOnly.PrivateKey = rsaCNG;
X509Store store = new X509Store(StoreName.My, StoreLocation.LocalMachine);
store.Open(OpenFlags.ReadWrite);
store.Add(certOnly);
store.Close();

fails with exception: Only asymmetric keys that implement ICspAsymmetricAlgorithm are supported. at line certOnly.PrivateKey = rsaCNG.

Does anybody know how to do it right? I'm using .NET Framework 4.6.2, and would like to avoid P/Invoke if possible. And i can upgrade to .NET Framework 4.7.X if necessary, though the code raises the same error in 4.7.2.


Solution

  • Use the CopyWithPrivateKey extension method from 4.7.2, it understands both CAPI and CNG keys.

    // certWithKey understands it has a persisted key reference if rsaCNG's key is
    // a named key, so adding it to the store and reading it back will work.
    X509Certificate2 certWithKey = certOnly.CopyWithPrivateKey(rsaCNG);
    ...
    store.Add(certWithKey);
    ...