Associate a Private Key with the X509Certificate2 Class in .Net

Associate a private key with the X509Certificate2 class in .net

For everyone else with the same problem, I found a neat little piece of code that let's you do exactly that:

http://www.codeproject.com/Articles/162194/Certificates-to-DB-and-Back

byte[] certBuffer = Helpers.GetBytesFromPEM(publicCert, PemStringType.Certificate);
byte[] keyBuffer = Helpers.GetBytesFromPEM(privateKey, PemStringType.RsaPrivateKey);

X509Certificate2 certificate = new X509Certificate2(certBuffer, password);

RSACryptoServiceProvider prov = Crypto.DecodeRsaPrivateKey(keyBuffer);
certificate.PrivateKey = prov;

EDIT: The code for the Helper method (which otherwise requires a codeproject login) is as follows:

public static byte[] GetBytesFromPEM(string pemString, PemStringType type)
{
string header; string footer;
switch (type)
{
case PemStringType.Certificate:
header = "-----BEGIN CERTIFICATE-----";
footer = "-----END CERTIFICATE-----";
break;
case PemStringType.RsaPrivateKey:
header = "-----BEGIN RSA PRIVATE KEY-----";
footer = "-----END RSA PRIVATE KEY-----";
break;
default:
return null;
}

int start = pemString.IndexOf(header) + header.Length;
int end = pemString.IndexOf(footer, start) - start;
return Convert.FromBase64String(pemString.Substring(start, end));
}

Update

As of .NET 5 you can simply use CreateFromPem(ReadOnlySpan, ReadOnlySpan):

Creates a new X509 certificate from the contents of an RFC 7468
PEM-encoded certificate and private key.

example:

X509Certificate2 cert = X509Certificate2.CreateFromPem(
certPem, //The text of the PEM-encoded X509 certificate.
keyPem //The text of the PEM-encoded private key.
);

Or if you have a string with both the cert and its private key, you can pass it in for both the cert arg and the key arg:

X509Certificate2 cert = X509Certificate2.CreateFromPem(
certPem, //The text of the PEM-encoded X509 certificate.
certPem// The text of the PEM-encoded private key.
);

Import X509 certificate to certlm with private key (.NET Core 6)

How can I import the cert + private key without resorting to this awkward workaround?

By creating the key as a persisted key to begin with.

using RSA rsa = RSA.Create(2048);

This line creates an ephemeral (in-memory only) private key. Instead you can do

CngKeyCreationParameters keyParams = new CngKeyCreationParameters
{
ExportPolicy = CngExportPolicies.AllowPlaintextExport,
KeyCreationOptions = CngKeyCreationOptions.MachineKey,
Parameters =
{
new CngProperty("Length", BitConverter.GetBytes(2048), CngPropertyOptions.Persist),
},
};

using CngKey cngKey = CngKey.Create(CngAlgorithm.Rsa, Guid.NewGuid().ToString("D"), keyParams);
using RSA rsa = new RSACng(cngKey);

And now the created certificate is associated with an already-persisted key.

(Note that this snippet uses MachineKey and exportable because that's what the question has as the PFX import flags)

I try to generate a X509Certificate2 key pair with dot-net but private key is missing

The HasPrivateKey property set to true the actual PrivateKey property is null

You shouldn't trust or use the PrivateKey property, it's always null for ECDSA/ECDH/DH-based certificates, and can also be null (as you saw) for RSA and DSA. The GetRSAPrivateKey() method (and others, for different algorithms) is the better answer.

How do I get both, private and public key to be added to the windows key store?

The problem is that CertificateRequest binds the key as-is to the certificate. You have an ephemeral key (created by RSA.Create(4096)) so that certificate is associated with an ephemeral key. When you add it to the store the ephemeral key is then forgotten.

You could either change your key creation to create a named CNG key, or just export the cert as a PFX, re-import it with PersistKeySet, and add that to the store.

var cert = req.CreateSelfSigned(DateTimeOffset.UtcNow.AddDays(-1),
DateTimeOffset.UtcNow.AddYears(30));
cert.FriendlyName = firendly_name;

using (cert)
using (var tmpCert = new X509Certificate2(cert.Export(X509ContentType.Pfx), "", X509KeyStorageFlags.MachineKeySet | X509KeyStorageFlags.PersistKeySet))
using (var store = new X509Store(StoreName.My, StoreLocation.LocalMachine))
{
store.Open(OpenFlags.ReadWrite);
store.Add(tmpCert);
store.Close();
}

Create X509Certificate2 from Cert and Key, without making a PFX file

There are a couple of different things you're asking for, with different levels of ease.

Attaching a private key to a certificate

Starting in .NET Framework 4.7.2 or .NET Core 2.0 you can combine a cert and a key. It doesn't modify the certificate object, but rather produces a new cert object which knows about the key.

using (X509Certificate2 pubOnly = new X509Certificate2("myCert.crt"))
using (X509Certificate2 pubPrivEphemeral = pubOnly.CopyWithPrivateKey(privateKey))
{
// Export as PFX and re-import if you want "normal PFX private key lifetime"
// (this step is currently required for SslStream, but not for most other things
// using certificates)
return new X509Certificate2(pubPrivEphemeral.Export(X509ContentType.Pfx));
}

on .NET Framework (but not .NET Core) if your private key is RSACryptoServiceProvider or DSACryptoServiceProvider you can use cert.PrivateKey = key, but that has complex side-effects and is discouraged.

Loading the private key

This one is harder, unless you've already solved it.

For the most part the answer for this is in Digital signature in c# without using BouncyCastle, but if you can move to .NET Core 3.0 things get a lot easier.

PKCS#8 PrivateKeyInfo

Starting in .NET Core 3.0 you can do this relatively simply:

using (RSA rsa = RSA.Create())
{
rsa.ImportPkcs8PrivateKey(binaryEncoding, out _);
// do stuff with the key now
}

(of course, if you had a PEM you need to "de-PEM" it, by extracting the contents between the BEGIN and END delimiters and running it through Convert.FromBase64String in order to get binaryEncoding).

PKCS#8 EncryptedPrivateKeyInfo

Starting in .NET Core 3.0 you can do this relatively simply:

using (RSA rsa = RSA.Create())
{
rsa.ImportEncryptedPkcs8PrivateKey(password, binaryEncoding, out _);
// do stuff with the key now
}

(as above, you need to "de-PEM" it first, if it was PEM).

PKCS#1 RSAPrivateKey

Starting in .NET Core 3.0 you can do this relatively simply:

using (RSA rsa = RSA.Create())
{
rsa.ImportRSAPrivateKey(binaryEncoding, out _);
// do stuff with the key now
}

(same "de-PEM" if PEM).



Related Topics



Leave a reply



Submit