19

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);
abatishchev
  • 98,240
  • 88
  • 296
  • 433
Conrad
  • 397
  • 1
  • 2
  • 12
  • I'm using .net 4, if it makes any difference – Conrad Apr 01 '19 at 13:54
  • Hi Conrad, I know this is old, I'm stuck with exact same problem, can we have a chat and sort this out. Thank you – Clint Apr 30 '20 at 05:39
  • 1
    @Clint, I left my solution with the OpenSSL call in place. It works without fail, and though is an external application to reference (and less clean or pure code), it works! – Conrad May 19 '20 at 19:53

2 Answers2

32

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

bartonjs
  • 30,352
  • 2
  • 71
  • 111
  • 3
    Thanks, @bartonjs, If I got to .NET Core, ill use this - for now, I'l just use a Process() to get OpenSSL to make the .pfx file, since I have the .crt and .key files at hand. (Does seem odd tho that this is not available in .NET4 - seems like quite a rudimentary requirement to be able to host a secure TCP service with a CA, Certificate and private key) – Conrad Apr 02 '19 at 06:57
  • I am not too familiar with security. What is the process required to create a `privateKey` from a `.key` file? – Nican Mar 22 '21 at 03:33
  • Is there some reason that I'm not seeing as to why you don't just use `X509Certificate2.CreateFromPem`? You make a statement about de-PEMing everything, but the class supports PEM-encoded certs and keys. There's also `RSA.ImportFromPem` and `RSA.ImportFromEncryptedPem` for importing keys directly. I only ask all of this because I spent an inordinate amount of time building my own de-PEMing logic today after reading your answer, and then I found the docs for those methods. – b4ux1t3 Aug 10 '21 at 20:35
  • @b4ux1t3 Just the linear nature of time. This answer was written before any of those methods were created. – bartonjs Aug 10 '21 at 20:36
  • Ah, you're right, it looks like these were added in .NET 5! My mistake. – b4ux1t3 Aug 10 '21 at 20:53
-4

In the end i did this, and it works fine:

...
if (!File.Exists(pfx)) {
    // Generate PFX
    string arguments = "openssl pkcs12 -export -in " + certPath + "" + certFile + ".crt -inkey " + certPath + "" + certFile + ".key -out " + certPath + "" + certFile + ".pfx -passout pass:" + pfxPassword;
    ProcessStartInfo opensslPsi = new ProcessStartInfo("sudo", arguments);
    opensslPsi.UseShellExecute = false;
    opensslPsi.RedirectStandardOutput = true;
    using (Process p = Process.Start(opensslPsi)) {
        p.WaitForExit();
    }
    // Set Permission
    ProcessStartInfo chmodPsi = new ProcessStartInfo("sudo", "chmod 644 " + certPath + "" + certFile + ".pfx");
    chmodPsi.UseShellExecute = false;
    chmodPsi.RedirectStandardOutput = true;
    using (Process p = Process.Start(chmodPsi)) {
        p.WaitForExit();
    }
}
sslCertificate = new X509Certificate2(pfx, pfxPassword);
...
Conrad
  • 397
  • 1
  • 2
  • 12