We seem to have switched to https finally. That means we use certificates all the time, often without paying them much attention. How do you see the content of a certificate? Convert it to a different format? There are a bunch of useful commands that are hard to remember unless you use them frequently (or, conveniently, find a post about the topic). I want to share some of them, focusing on doing things from the command line. Maybe you have seen this wonderful article about OpenSSL commands already. I hope I can provide a bit more context.
As a refresher, we mostly use certificates to verify the identity of a server when using https. However, if you are using mTLS, the client needs to present a certificate as well. The X.509 format is how we commonly see them in the wild.
We are going to use some handy tools for this job, which can be installed with brew, for example:
Creating a test certificate
To get started, we need a certificate. If you own a domain, you’ll want to use LetsEncrypt to generate one programmatically. For demonstration purposes, I’ll use
mkcert -install mkcert example.com app1.example.com app2.example.com
The result is stored under
This certificate is issued for three domains, using what’s called a SAN, which extends the Common Name so that you can specify multiple domains. The CN is limited to 64 characters, which can be a problem for internal certificates with a lot of subdomains (don’t ask how I learned this!).
Even though it is self-signed, we can still verify it:
openssl verify -CAfile ~/Library/Application\ Support/mkcert/rootCA.pem example.com+2.pem
Reading a certificate
You can read the certificate by using:
openssl x509 -in example.com+2.pem -text -noout
which displays something like this:
Certificate: Data: Version: 3 (0x2) Serial Number: d7:98:98:19:27:dd:52:d0:1f:1f:07:b5:ea:85:54:eb Signature Algorithm: sha256WithRSAEncryption Issuer: O=mkcert development CA, OU=me@my-computer (My Name), CN=mkcert me@my-computer (My Name) Validity Not Before: Jun 1 00:00:00 2019 GMT Not After : Apr 6 20:21:32 2030 GMT Subject: O=mkcert development certificate, OU=me@my-computer (My Name) Subject Public Key Info: Public Key Algorithm: rsaEncryption Public-Key: (2048 bit) Modulus: 00:b1:b7:1b:14:f9:61:79:90:be:21:bc:91:12:78: (...) Exponent: 65537 (0x10001) X509v3 extensions: X509v3 Key Usage: critical Digital Signature, Key Encipherment X509v3 Extended Key Usage: TLS Web Server Authentication X509v3 Basic Constraints: critical CA:FALSE X509v3 Authority Key Identifier: keyid:34:0F:21:0C:F8:7D:D5:DF:E4:15:B6:15:B9:94:C9:2B:F3:E0:24:AD X509v3 Subject Alternative Name: DNS:example.com, DNS:app1.example.com, DNS:app2.example.com Signature Algorithm: sha256WithRSAEncryption a8:52:3b:19:dc:74:4f:e3:6b:9c:cd:0c:59:c3:fe:e8:0b:2d: (...)
We can see there all the relevant information. The X509v3 Subject Alternative Name tends to be the most interesting part because you can see the domains that the certificate covers.
Usually, certificates are stored in PEM format in Base64 ASCII. You recognize them by the
---- BEGIN CERTIFICATE---- line in the beginning. That is what
mkcert gives us, for instance.
Sometimes a PEM certificate is transmitted encoded in Base64 itself. Let’s say we receive our certificate over the wire. We store it under
/tmp/b64-cert, and it looks like this:
Yes, that’s a very long line.
openssl can’t read that out of the box, so we’ve got to decode it first. Moreover, it can be that, once decoded the lines are too long, which confuses
openssl. We’ll decode and fold that string so that we can read the certificate again:
base64 -d /tmp/b64-cert | fold -64 | openssl x509 -text -noout
Sometimes your certificate is encoded in DER, a binary format. The
JVM world really likes to transport certificates in this way. You’ll notice that if you inspect such a certificate, it will be mostly a bunch of special characters and gibberish. Luckily,
openssl can convert from DER to PEM, in both directions.
openssl x509 -in example.com+2.pem -outform DER -out example.com+2.der openssl x509 -inform DER -in example.com+2.der -text -noout
Binary certificates encoded in base64url
What if you have a certificate in DER format, that has been encoded in base64url? Your first thought might be, “That’s an oddly specific combination”. But it can actually happen. If you are implementing your own ACME server, it is part of the protocol to request a certificate. I recently spent quite a bit of time trying to make sense of that.
There is no out-of-the-box tool to decode Base64 URL, but you can use a script like this one. You can read this mysterious certificate by combining the script with the previous calls:
./base64url.sh decode `cat /tmp/b64url-cert | tr -d '\n'` | openssl x509 -inform DER -text -noout
You will probably never run into this, but if you do, this will save you a ton of time.
Checking a certificate on a remote server
What if you don’t have the certificate, but want to get it from a remote server? openssl is up to the task.
echo -n | openssl s_client -connect google.com:443 -servername google.com | openssl x509 -text -noout
s_client command, we are requesting the certificate presented by a host on a specific port (usually 443). In this case, we are checking the certificate from google.com. We can pipe the output into the standard command for reading a certificate. We pipe the
echo -n first so that it doesn’t hang.
You might be wondering about the
-servername option. That is related to SNI. If the target is serving multiple domains under the same IP address, you might want to select the one associated with a domain. I recently needed the option when testing a Kubernetes nginx ingress that was serving a self-signed certificate unless I specified that option.
We are only scratching the surface
This post was focused on reading existing certificates, but there are a ton more things that you can do with
openssl, including writing them, extracting keys and whatnot. Not to mention Certificate signing requests. Reading is a good place to familiarize yourself with certificates, however.
EDIT 19/06/2020: Fix
openssl s_client command