Accessing a CNG private key from an X509Certificate2 class

Currently, The .NET Framework X509Certificate2 class does not support certificates associated with a CNG private key provider.  That is, the X509Certificate.PrivateKey property can only be associated with an RSACryptoServiceProvider, a wrapper around the CryptoAPI provider.

 

If you attempt to acquire the private key of a CNG provider, the result is the following exception:

System.Security.Cryptography.CryptographicException: Invalid provider type specified.

at System.Security.Cryptography.Utils.CreateProvHandle(CspParameters parameters, Boolean randomKeyContainer)
at System.Security.Cryptography.Utils.GetKeyPairHelper(CspAlgorithmType keyType, CspParameters parameters, Boolean randomKeyContainer, Int32 dwKeySize, SafeProvHandle& safeProvHandle, SafeKeyHandle& safeKeyHandle)
at System.Security.Cryptography.RSACryptoServiceProvider.GetKeyPair()
at System.Security.Cryptography.RSACryptoServiceProvider..ctor(Int32 dwKeySize, CspParameters parameters, Boolean useDefaultKeySize)
at System.Security.Cryptography.X509Certificates.X509Certificate2.get_PrivateKey()

 

If your application is directly accessing the X509Certificate2.PrivateKey property there is a workaround for this known problem. 

The CLR team wrote an extension to the Cryptography classes including an X509Certificate2 property that allows access to a CNG private key.  The extension can be downloaded from CodePlex at https://clrsecurity.codeplex.com/wikipage?title=Security.Cryptography.dll

Once referenced in the project, the X509Certificate2 will contain the GetCngPrivateKey method.

Below is a code sample that demonstrates how an application can use the GetCngPivateKey method.

using System;
using System.Text;
using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates;
using Security.Cryptography;
using Security.Cryptography.X509Certificates;
using System.IO;

namespace SecurityTest
{
    class Program
    {
        static byte[] DecryptMessage(byte[] EncryptedData, CngKey BobsKey, byte[] pbAlicePublicKey)
        {
            int IVLength;
            byte[] Message = null;
            CngKey AlicesPublicKey = CngKey.Import(pbAlicePublicKey, CngKeyBlobFormat.EccPublicBlob);
            ECDiffieHellmanCng BobECDH = new ECDiffieHellmanCng(BobsKey);

            byte[] SymmetricKey = BobECDH.DeriveKeyMaterial(AlicesPublicKey);
            AesCryptoServiceProvider Aes = new AesCryptoServiceProvider();
            Aes.Key = SymmetricKey;
            IVLength = Aes.BlockSize/8;
            byte[] IV = new byte[IVLength];
            for (int n = 0; n < IVLength; n++)
            {
                IV[n] = EncryptedData[n];
            }
            Aes.IV = IV;

            MemoryStream ms = new MemoryStream();
            CryptoStream cs = new CryptoStream(ms, Aes.CreateDecryptor(), CryptoStreamMode.Write);

            cs.Write(EncryptedData, IVLength, EncryptedData.Length - IVLength);
            cs.FlushFinalBlock();
            cs.Close();

            Message = ms.ToArray();

            return Message;
        }

        static byte[] EncryptedMessageForBob(byte[] pbBobsPublicKey, CngAlgorithm Algorithm, out byte[] pbAlicePublicKey)
        {
            byte[] EncryptedBlob = null;
            byte[] bpMessage = Encoding.Unicode.GetBytes("This is an encrypted message from Alice");
            CngKey AliceKey = CngKey.Create(Algorithm);
            CngKey BobsPublicKey = CngKey.Import(pbBobsPublicKey, CngKeyBlobFormat.EccPublicBlob);
            pbAlicePublicKey = AliceKey.Export(CngKeyBlobFormat.EccPublicBlob);

            ECDiffieHellmanCng AliceECDH = new ECDiffieHellmanCng(AliceKey);
            byte[] SymmetricKey = AliceECDH.DeriveKeyMaterial(BobsPublicKey);

            AesCryptoServiceProvider Aes = new AesCryptoServiceProvider();
            Aes.Key = SymmetricKey;
            Aes.GenerateIV();
           
            MemoryStream ms = new MemoryStream();
            CryptoStream cs = new CryptoStream(ms, Aes.CreateEncryptor(), CryptoStreamMode.Write);

            ms.Write(Aes.IV, 0, Aes.IV.Length);
            cs.Write(bpMessage, 0, bpMessage.Length);
            cs.FlushFinalBlock();
            cs.Close();

            EncryptedBlob = ms.ToArray();

            return EncryptedBlob;
        }

        static void Main(string[] args)
        {            
            byte[] pbAlicesPublicKey = null;
            X509Store Store = new X509Store(StoreName.My, StoreLocation.CurrentUser);
            Store.Open(OpenFlags.ReadOnly);
            X509Certificate2Collection Certs = Store.Certificates;
        
            Certs = Certs.Find(X509FindType.FindBySubjectName, "CNGCert", false);
            if (Certs.Count > 0)
            {
                X509Certificate2 Cert = Certs[0];
                if (Cert.HasCngKey())
                {
                    // We are Bob in this scenario
                    CngKey Bobkey = Cert.GetCngPrivateKey();

                    // Get an encrypted message from Alice                                                         
                    byte[] EncryptedData = EncryptedMessageForBob(Bobkey.Export(CngKeyBlobFormat.EccPublicBlob), Bobkey.Algorithm, out pbAlicesPublicKey);

                    // Decrypt the message
                    byte[] Message = DecryptMessage(EncryptedData, Bobkey, pbAlicesPublicKey);

                    Console.WriteLine("Message from Alice : {0}", Encoding.Unicode.GetString(Message));
                }
                else
                {
                    // It's a CryptoAPI key
                    RSACryptoServiceProvider rsa = (RSACryptoServiceProvider)Cert.PrivateKey;                                   
                }
            }
        }
    }
}

Unfortunately, if you encounter the exception on in a product such as WCF, CRM or ADFS, the only option is to use a certificate with a CryptoAPI key since these products only know about the PrivateKey property.

Hopefully, a future version of .NET and products that use it will work with CNG keys. 

Follow us on Twitter, www.twitter.com/WindowsSDK.