Fun with Certificates

After years of running websites, I’ve come across a few simple tricks to help to deal with certificates. This post is as much for me as it is for the internet. I always have issues rewriting or rediscovering this stuff and figured I’d post it so I don’t forget it.

Inspecting a Certificate File

If you have a local certificate you’d like to inspect, I use openssl to interrogate the file. This will decode the base64 encoding of the certificate in human readable form.

openssl x509 -in ngetchell.com.cer -text -noout

Slap a | grep After to the command to get a quick readout of the certificate expiration date.

Retrieving Certificate Information from a Web Server

Every once in a while, you need to determine the state of an TLS certificate. SSL Labs provides a comprehensive inspection of a given site, but it does come with some limits. For one, it will only check a web server sitting on port 443. Another is, you have to do it all one at a time.

If you don’t need to delve deep into the security of the web server, or you need to rapidly iterate through a list of domains, you can use this.

Function Get-TLSCertificate {
    [CmdletBinding()]
    param(
        $DomainName,
        $Port
    )

    $request = [System.Net.Sockets.TcpClient]::new($DomainName, $Port)
    $stream = [System.Net.Security.SslStream]::new($request.GetStream())
    $stream.AuthenticateAsClient($DomainName)
    [PSCustomObject]@{
        DomainName = $DomainName
        Port = $Port
        TargetHostname = $stream.TargetHostName
        Subject = $stream.RemoteCertificate.Subject
        Thumbprint = $stream.RemoteCertificate.Thumbprint
        NotAfter = $stream.RemoteCertificate.NotAfter
        NotBefore = $stream.RemoteCertificate.NotBefore
        ExpirationCountdown = $(New-TimeSpan -end $stream.RemoteCertificate.NotAfter -start $(Get-Date) ).Days
        Issuer = $stream.RemoteCertificate.Issuer
        TLSProtocol = $stream.SslProtocol
        NegotiatedCipherSuite = $stream.NegotiatedCipherSuite
        SignatureAlgorithm = $stream.RemoteCertificate.SignatureAlgorithm.FriendlyName
        Extensions = @($stream.RemoteCertificate.Extensions.oid.friendlyname | select -unique )
    }
}

Get-TLSCertificate -DomainName ngetchell -Port 443

I’ve used this during mass certificate rotations to confirm we’d done our job. When migrating from one wildcard certificate to another, it is important to double-check your work. We started with an export of our DNS zone and iterated through each sub-domain to make sure the thumbprint changed. It caught a few that we thought we’d switched but missed for whatever reason. The script also helped to unearth some services that we believed were behind a load balancer but turns out were not.

Generating a CSR from a template file

As more and more services support the ACME protocol, manually generating certificate signing requests becomes less and less useful. Still, there are times you need to know how to generate a certificate. This template also allows you to add additional Subject Alternative Names, which is useful if your service is known by many names.

default_bits = 2048
default_keyfile = /etc/path/to/private.key
encrypt_key = no
default_md = sha256
prompt = no
utf8 = yes
distinguished_name = my_req_distinguished_name
req_extensions = my_extensions

[ my_req_distinguished_name ]
C = US
ST = Parts Unknown
L = Death Valley
O  = Blog
CN = gitlab-web.example.com

[ my_extensions ]
basicConstraints=CA:FALSE
subjectAltName=@my_subject_alt_names
subjectKeyIdentifier = hash

[ my_subject_alt_names ]
DNS.1 = gitlab-web.example.mobile
DNS.2 = gitlab-web.example.net

To invoke the template, you run the following.

openssl req -new -out example.csr -config csr.conf -key private.key