Search code examples
c#sslcertificatex509certificate2

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


In the past I have been making secure TcpListener by exporting a PFX certificate with a password, but would like to know if this step could be skipped.

I'm not using commercial SSL certificates, and have a Root CA, that I use to issue server certificates. These server certificates require additional steps when hosting a TcpListener in C# (I guess because the CSR wasn't used)... but what if I do have the Private Key, and the Certificate that OpenSSL generates/uses.

sslCertificate = new X509Certificate2("myExportedCert.pfx", "1234");

So this is great, however I have to issue an openssl command to make a pfx file from the Certificate and the Private Key, then make up some password. Then include this password in my code.

I was wondering if this step was quite necessary. Is there a way to make up a X509Certificate2 from the Cert, and then apply the Private Key. The constructor arguments allow the Cert only part, but encrypting fails then because there is no private key.

Also, I don't want to rely on OpenSSL or IIS to export the pfx.... seems clumsy.

Ideally i would like:

sslCertificate = new X509Certificate2("myCert.crt");
sslCertificate.ApplyPrivateKey(keyBytes) // <= or "private.key" or whatever

sslStream.AuthenticateAsServer(sslCertificate, false, SslProtocols.Default, false);

Solution

  • 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).