Download, parse, and store SSL certificates in C#

As part of elmah.io Uptime Monitoring, we offer SSL expiration warnings. We recently both simplified the code and had to do some additional parsing of the common names in the certificates. This post is a summary of how we did that.

When dealing with SSL certificates in C#, that part is typically handled by the HTTP client used. In some cases, you may need to download and store the certificate. You may even need to parse the content of the various fields within the certificate. Well, this is the post for you.

I have seen a range of different tips (hacks basically) to use HttpClient for fetching an SSL certificate. I even used some of them myself until seeing this post from Gérald Barré. To sum up, the easiest way to fetch a certificate from a URL can be done like this:
RemoteCertificateValidationCallback certCallback = (_, _, _, _) => true;
using var client = new TcpClient("blog.elmah.io", 443);
using var sslStream = new SslStream(client.GetStream(), true, certCallback);
await sslStream.AuthenticateAsClientAsync(domain);
var serverCertificate = sslStream.RemoteCertificate;
var certificate = new X509Certificate2(serverCertificate);

The code uses the TcpClient and SslStream classes to fetch the certificate without a single HttpClient. Then create a new X509Certificate2 instance which we will work with in the following sections. If you are running on a previous version of C#, make sure to add the following property to your csproj file:

<LangVersion>9.0</LangVersion>

Once loaded into memory, a certificate can be easily saved by using the Export method to generate a byte array representation and save it:

var content = certificate.Export(X509ContentType.Cert);
await File.WriteAllBytesAsync("certificate.cer", content);

So, how about displaying the fields from the SSL certificate. You've probably seen the Windows dialog to display an SSL certificate:

Let's build something similar, but for the command line. To illustrate how to parse the various fields within the certificate, I'll go through how we can display the Issued to field.

Would your users appreciate fewer errors?

➡️ Reduce errors by 90% with elmah.io error logging and uptime monitoring ⬅️

The X509Certificate2 class has a range of properties to fetch the different information about the certificate. The Issued to information is located in a property named Subject. In security, the subject means the thing being secured. In this case the domain. Let's write that to the screen:

Console.WriteLine(certificate.Subject);

You might expect this to write out sni.cloudflaressl.com. Sorry to disappoint you:

CN=sni.cloudflaressl.com, O="Cloudflare, Inc.", L=San Francisco, S=California, C=US

That's the "distinguished name" (DName) of the subject. To get the common name (CN) you could start parsing and splitting the text. Or you can install the Rfc2253 NuGet package by Eric Li:

dotnet add package Rfc2253

The package is great at parsing DNames like the one we already have in the subject:

var subject = DistinguishedName.Create(certificate.Subject);
var commonName = subject.Rdns.FirstOrDefault(x => x.Type.Value == "CN");
if (commonName != null)
{
    Console.WriteLine(commonName.Value);
}

Rfc2253 parses the subject into an array of keys and values. All we have to do then is to find the common name by looking for the CN type.

As expected, we now see the common name in the console:

sni.cloudflaressl.com