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 http://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.

 


Comments (1)

  1. sb says:

    We have an application that signs ".hckx" file using the HCK API. PackageManager.Sign takes  X509Certificate2 as input. This works fine when the provider is a CSP provider. An error "invalid provider type" is displayed when a CNG provider is used.

    I have the CNG extensions for X509Certificate2 installed and can access the CNGKey. Is there anything else that can be done to ensure that PackageManager.Sign() works with the CNG provider?

    Thanks

Skip to main content