Search code examples
c#.netwindowsprivate-keyx509certificate2

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


I try to create a private-public key pair with dot-net and install it in the local key store. I was confused when the private key was not installed with the public key and then I noticed that despite the certificate having the HasPrivateKey property set to true the actual PrivateKey property is null. How do I get both, private and public key to be added to the windows key store? The certificate is later needed for port binding.

My current code looks basically like this:

public static X509Certificate2 CreateSelfSigned(string issuer="", string firendly_name = "")
{
using (var key = RSA.Create(4096))
{
    var req = new CertificateRequest(
        $"CN={issuer}",
        key,
        HashAlgorithmName.SHA256,
        RSASignaturePadding.Pkcs1);
    
    req.CertificateExtensions.Add(
        new X509BasicConstraintsExtension(true, false, 0, true));

    req.CertificateExtensions.Add(
        new X509EnhancedKeyUsageExtension(
            new OidCollection
            {
                // Server Authentifikation
                new Oid("1.3.6.1.5.5.7.3.2"),
                // Client Authentifikation
                new Oid("1.3.6.1.5.5.7.3.1")
            },
            true));

    req.CertificateExtensions.Add(
        new X509SubjectKeyIdentifierExtension(req.PublicKey, false));


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


    using (var store = new X509Store(StoreName.My, StoreLocation.LocalMachine))
    {
        store.Open(OpenFlags.ReadWrite);
        store.Add(cert);
        store.Close();
    }

}
}

I already searched the internet but couldn't find a solution that works for me. Really scratching my head about this one for hours. The program is a installer type of program and previously called PowerShell to create a self signed certificate which only worked half of the time and was dirty as hell. I'm limited to dot-net 4.7.2 and can't rely on external libraries very much because of project constraints.


Solution

  • 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();
    }