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.
Comments
- Anonymous
December 22, 2014
The comment has been removed