Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Cannot export generated certificate with a private key to byte array in .NET 4.0/4.5

I need to export and import generated certificates with private keys to and from byte arrays, and I don't have any problems unless I use .NET framework 4.0 and 4.5. I'm generating self-signed certificates with BouncyCastle library and then converting them to .NET format (X509Certificate2 object). Unfortunately with the upgrade to a newest framework I cannot export private keys. Here is the code:

using System;
using System.Diagnostics;
using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates;
using Org.BouncyCastle.Asn1.X509;
using Org.BouncyCastle.Crypto;
using Org.BouncyCastle.Crypto.Generators;
using Org.BouncyCastle.Crypto.Parameters;
using Org.BouncyCastle.Crypto.Prng;
using Org.BouncyCastle.Math;
using Org.BouncyCastle.Security;
using Org.BouncyCastle.X509;

namespace X509CertificateExport
{
    class Program
    {
        static void Main(string[] args)
        {
            var certificate = Generate();
            var exported = certificate.Export(X509ContentType.Pfx);
            var imported = new X509Certificate2(exported, (string)null, X509KeyStorageFlags.Exportable | X509KeyStorageFlags.PersistKeySet);

            Console.WriteLine("Certificate has private key: " + imported.HasPrivateKey);
            Console.ReadKey();
        }

        public static X509Certificate2 Generate()
        {
            var keyPairGenerator = new RsaKeyPairGenerator();
            var secureRandom = new SecureRandom(new CryptoApiRandomGenerator());
            keyPairGenerator.Init(new KeyGenerationParameters(secureRandom, 1024));
            var keyPair = keyPairGenerator.GenerateKeyPair();
            var publicKey = keyPair.Public;
            var privateKey = (RsaPrivateCrtKeyParameters)keyPair.Private;

            var generator = new X509V3CertificateGenerator();
            generator.SetSerialNumber(BigInteger.ProbablePrime(120, new Random()));
            generator.SetSubjectDN(new X509Name("CN=Test"));
            generator.SetIssuerDN(new X509Name("CN=Test"));
            generator.SetNotAfter(DateTime.Now + new TimeSpan(10, 10, 10, 10));
            generator.SetNotBefore(DateTime.Now.Subtract(new TimeSpan(7, 0, 0, 0)));
            generator.SetSignatureAlgorithm("MD5WithRSA");
            generator.SetPublicKey(publicKey);

            var newCert = generator.Generate(privateKey);
            var dotNetPrivateKey = ToDotNetKey(privateKey);
            var dotNetCert = new X509Certificate2(DotNetUtilities.ToX509Certificate(newCert));
            dotNetCert.PrivateKey = dotNetPrivateKey;

            return dotNetCert;
        }

        public static AsymmetricAlgorithm ToDotNetKey(RsaPrivateCrtKeyParameters privateKey)
        {
            var rsaProvider = new RSACryptoServiceProvider();
            var parameters = new RSAParameters
            {
                Modulus = privateKey.Modulus.ToByteArrayUnsigned(),
                P = privateKey.P.ToByteArrayUnsigned(),
                Q = privateKey.Q.ToByteArrayUnsigned(),
                DP = privateKey.DP.ToByteArrayUnsigned(),
                DQ = privateKey.DQ.ToByteArrayUnsigned(),
                InverseQ = privateKey.QInv.ToByteArrayUnsigned(),
                D = privateKey.Exponent.ToByteArrayUnsigned(),
                Exponent = privateKey.PublicExponent.ToByteArrayUnsigned()
            };

            rsaProvider.ImportParameters(parameters);
            return rsaProvider;
        }
    }
}

After a closer look to the generated certificate I've noticed that PrivateKey.CspKeyContainerInfo.Exportable flag is true for .NET framework 3.5, but for later versions it throws:

'Exportable' threw an exception of type
'System.Security.Cryptography.CryptographicException' / Key does not exist

The only difference I see is in PrivateKey.CspKeyContainerInfo.m_parameters.Flags: .NET 3.5 - 'NoFlags'; .NET 4.5 - 'CreateEphemeralKey'. Documentation states that 'CreateEphemeralKey' creates a temporary key that is released when the associated RSA object is closed. It was introduced with 4.0 framework and didn't exist before. I've tried to get rid off this flag by creating CspParameters explicitly:

public static AsymmetricAlgorithm ToDotNetKey(RsaPrivateCrtKeyParameters privateKey)
{
    var cspParams = new CspParameters
    {
        Flags = CspProviderFlags.UseMachineKeyStore
    };

    var rsaProvider = new RSACryptoServiceProvider(cspParams);
    // ...

but with no luck. 'CreateEphemeralKey' is added anyway, so I'm getting as a result UseMachineKeyStore | CreateEphemeralKey flags and I don't see how I can remove it. Is there a way I can ignore this flag and export the certificate with private key normally?

like image 645
username Avatar asked May 07 '13 13:05

username


People also ask

Can you export a certificate with private key?

To export a certificate with the private keyIn the console tree under the logical store that contains the certificate to export, click Certificates. In the details pane, click the certificate that you want to export. On the Action menu, point to All Tasks, and then click Export.

How do I export backup SSL certificate with private key?

Go to: Certificates > Personal > Certificates. Right-click on the certificate you wish to export and go to All Tasks and hit Export. Hit Next on the Certificate Export Wizard to begin the process. Select “Yes, export the private key” and hit next.


1 Answers

I haven't noticed that CspKeyContainerInfo.CspParameters.KeyContainerName is empty after key creation in .NET 4.0 and .NET 4.5, but it was autogenerated in .NET 3.5. I've set a unique name for container and now I'm able to export the private key.

public static AsymmetricAlgorithm ToDotNetKey(RsaPrivateCrtKeyParameters privateKey)
{
    var cspParams = new CspParameters
    {
          KeyContainerName = Guid.NewGuid().ToString(),
          KeyNumber = (int)KeyNumber.Exchange,
          Flags = CspProviderFlags.UseMachineKeyStore
    };

    var rsaProvider = new RSACryptoServiceProvider(cspParams);
    // ...
like image 181
username Avatar answered Oct 09 '22 15:10

username