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
How to Test Your Request.Querystring[] Variables
The Bare Minimum Needed to Write a Msmq Sample Application
What Is Applicationexception for in .Net
How to Get the Last Four Characters from a String in C#
Setting Up Swagger (ASP.NET Core) Using the Authorization Headers (Bearer)
How to Derive Xml Element Name from an Attribute Value of a Class Using Annotations
Why Would One Ever Use the "In" Parameter Modifier in C#
Displaying a Collection of Controls in Windows Forms
How to Compile and Run C# Program Without Using Visual Studio
Why Do We Need the "Event" Keyword While Defining Events
How to Use C# 6 with Web Site Project Type
Additional Setup in Visual Studio Installer Projects
Recommend a C# Task Scheduling Library
System.Badimageformatexception: Reference Assemblies Should Not Be Loaded for Execution