Share

PEM Loading in .NET Core and .NET 5

Scott Brady
C#

PEM is a file format that typically contains a certificate or private/public keys. PEM files have had patchy support in Windows and .NET but are the norm for other platforms. However, starting with .NET 5, .NET now has out of the box support for parsing certificates and keys from PEM files.

This article will show you how to manually load a PEM file in .NET Core 3.1 (the old way) and how to do the same using the new .NET 5 APIs. You’ll also see how to use PEM certificates for Kestrel TLS.

Loading a PEM Key

Let’s use an Elliptical Curve (EC) key as our example generated using OpenSSL.

-----BEGIN EC PRIVATE KEY-----
MHcCAQEEIP7n5rwD8HN7VUqcyYD5p+5jBNZQGkQEzoZ76tjXd2TmoAoGCCqGSM49
AwEHoUQDQgAEcH8RG+kV/n8Is+9LKRHS03yv4rXuWRn8rG2pTP2ZZ1I1mfgsJPmh
HCpBEdhlqGehYsNz3DpUM2PHRhzHZJB4Rg==
-----END EC PRIVATE KEY-----

Loading a PEM Key in .NET Core 3.1

In .NET Core, we can load this key by taking the base64 content between the PEM labels and passing it to ECDsa’s ImportECPrivateKey method, like so:

// loaded PEM file with labels stripped
var eccPem = @"MHcCAQEEIP7n5rwD8HN7VUqcyYD5p+5jBNZQGkQEzoZ76tjXd2TmoAoGCCqGSM49
              AwEHoUQDQgAEcH8RG+kV/n8Is+9LKRHS03yv4rXuWRn8rG2pTP2ZZ1I1mfgsJPmh
              HCpBEdhlqGehYsNz3DpUM2PHRhzHZJB4Rg==";

var key = ECDsa.Create();
key.ImportECPrivateKey(Convert.FromBase64String(eccPem), out _);

A similar approach can be taken with RSA keys.

However, this approach has a limitation: we’re assuming the format. The PEM format covers a lot of different things; PEM is just the -----BEGIN CONTENT----- and -----END CONTENT----- part. For instance, with RSA, there are 5 different PEM labels, with each one requiring a unique import method in .NET.

PEM Label Import method on RSA
BEGIN RSA PRIVATE KEY ImportRSAPrivateKey
BEGIN PRIVATE KEY ImportPkcs8PrivateKey
BEGIN ENCRYPTED PRIVATE KEY ImportEncryptedPkcs8PrivateKey
BEGIN RSA PUBLIC KEY ImportRSAPublicKey
BEGIN PUBLIC KEY ImportSubjectPublicKeyInfo

Wouldn’t it be nice if this was handled for us? Of course it would, which is why that’s now available in .NET 5.

For more details on loading different keys in .NET Core, check out Kevin Jones’ blog post on the topic. Kevin is also one of the people you can thank for the new PEM functionality in .NET 5.

Loading a PEM Key in .NET 5

In .NET 5, there are now ImportFromPem and ImportFromEncryptedPem methods on DSA, RSA, and ECDsa, which handle the most common PEM use cases.

var eccPem = File.ReadAllText("ecc.pem");

var key = ECDsa.Create();
key.ImportFromPem(eccPem);

There’s no more label checking or stripping. That’s a bit simpler.

Loading a PEM Certificate

You can also load in X509 certificates from a PEM file. For example, here’s a self-signed certificate generated for the above key:

-----BEGIN CERTIFICATE-----
MIIB3zCCAYWgAwIBAgIUImttQCULqkHxYbDivb1fzRNFYG8wCgYIKoZIzj0EAwIw
RTELMAkGA1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoMGElu
dGVybmV0IFdpZGdpdHMgUHR5IEx0ZDAeFw0yMDA5MTgxNDQyMzlaFw0yMTA5MTMx
NDQyMzlaMEUxCzAJBgNVBAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEwHwYD
VQQKDBhJbnRlcm5ldCBXaWRnaXRzIFB0eSBMdGQwWTATBgcqhkjOPQIBBggqhkjO
PQMBBwNCAARwfxEb6RX+fwiz70spEdLTfK/ite5ZGfysbalM/ZlnUjWZ+Cwk+aEc
KkER2GWoZ6Fiw3PcOlQzY8dGHMdkkHhGo1MwUTAdBgNVHQ4EFgQUOYFYa+w94G7t
MGD3bpM3T04WAxswHwYDVR0jBBgwFoAUOYFYa+w94G7tMGD3bpM3T04WAxswDwYD
VR0TAQH/BAUwAwEB/zAKBggqhkjOPQQDAgNIADBFAiEAxX7N6e+2NfuwR70u3AX0
mx5ZP9uQhdrvOi8qDBHSMMoCIEQenUMtTfYfOU8FwT3WZO4S5JB5jvPg9hCnlXPj
NwaC
-----END CERTIFICATE-----

Loading a PEM Certificate in .NET Core

In .NET Core, we can load certificate, by again taking the base64 content, without the labels and passing it into X509Certificate2’s constructor.

// loaded PEM file with labels stripped. Full value omitted for brevity
var certPem = "MIIB3zCCAYWgAwIBAgIUImttQCULqkHxYbDivb...FwT3WZO4S5JB5jvPg9hCnlXPjNwaC";

var cert = new X509Certificate2(Convert.FromBase64String(certPem));

// can be combined with the private key from the previous section 
var certWithKey = cert.CopyWithPrivateKey(key);

The initial load will only bring in the public key contained in the certificate. We would need to use the CopyWithPrivateKey extension method to combine the certificate with its private key (as would be the case with a PFX file).

Loading a PEM Certificate in .NET 5

This has again been improved in .NET 5, thanks to some new factory methods on X509Certificate2:

var certPem = File.ReadAllText("cert.pem");
var eccPem = File.ReadAllText("ecc.pem");

var cert = X509Certificate2.CreateFromPem(certPem, eccPem);

This factory method combines all of the functionality we’ve seen so far. It will parse the certificate from the PEM, load in the private key using the new PEM key import methods, and combine the two for us. If you use the CreateFromPemFile method, you get File.ReadAllText thrown in for good measure and can also place the key in the same file as the certificate..

Again, this is simpler, but this factory method is designed to replace the reliance on PFX and to make Kestrel’s PEM usage more straightforward. As a result, it requires a private key. You cannot currently use this method to load in just the certificate and public key.

You can find the review of this API on a .NET Design Review on YouTube.

Kestrel HTTPS Using PEM Certificate

With these methods combined, you have the power to use PEM format certificates & keys for Kestrel HTTPS.

If you’re not on Windows, you can do this by loading in the X509 certificate like above and passing it directly to Kestrel; however, at the time of writing, try to do that in Windows, and you’ll get an exception from SslStream:

System.ComponentModel.Win32Exception (0x8009030E): No credentials are available in the security package

This is because the private key is being loaded into memory (like the ephemeral keyset flag), but Windows needs the key to be in the system key set. This is again discussed in the .NET Design Review.

ASP.NET Core works around this in the Kestrel configuration loader, which means if you define your endpoints in config like so, you can use PEM files in Kestrel for HTTPS. Path needs to be the PEM file that contains your certificate and KeyPath needs to be the PEM file containing the corresponding private key.

{
  "Kestrel": {
    "Endpoints": {
      "HttpsFromPem": {
        "Url": "https://localhost:5001",
        "Certificate": {
          "Path": "../cert.pem",
          "KeyPath": "../ecc.pem"
        }
      }
    }
  }
}

Sample Source Code

As always, you can find some executable samples on GitHub, showing the above functionality, including Kestrel’s PEM support on Windows.