Client certificates and HTTPS: A deep dive into the world of certificates
Recently I've been checking out client side certificates in order to secure access to various secret parts of this website. In order to do this, I found myself learning more than I ever thought I would about certificates and certification authorities, so I thought I'd share it here.
Before I get into how client side certificates work, I should start at the beginning. In order to serve pages over HTTPS, a web server must have both a private key and a certificate. The private key does the encryption, and the certificate verifies the identity of the web server (so you know you're connecting to the real thing and not a fake).
The certificate is signed by a Certification Authority. A certification authority (CA) is an organisation that has a Root Certificate, which is a certificate that's explicitly trusted by your operating system and browser. If a chain of valid certificates can be made from the web server's certificate back to a trusted root certificate, then we know that the web server's certificate can be trusted. Since a picture is worth 1000 words, here's a diagram describing the above:
The web server isn't the only one who can have a certificate. The client can also have a certificate, and the web server can choose whether or not it likes the certificate that the client presents based on which certificates it can be linked back to in the signed certificate chain.
This presents some interesting possibilities. We can create our own certification authority and use it to issue client certificates. Then we can tell our web server that it should only accept clients certificates that can be linked back to our own certification authority.
For the certificate creation process, I can recommend tinyca2 (direct apt install link), which should be available in the repositories of most linux distributions. It's got a great GUI that makes the process relatively painless - this tutorial is pretty good at explaining it, although you'll need to read it more than once to understand everything. XCA is pretty good too, although a tad more complex. Once you've created your CA and certificates, come back here.
Next up is configuring our web server. I'm using Nginx in this tutorial (that's the web server that's currently behind this website!), but there are guides elsewhere for Apache 2, Lighttpd, Hiawatha (For Hiawatha it's the RequiredCA
configuration directive in the manual!), and probably others too!
Configuring Nginx to accept client certificates signed by our CA is actually fairly straight forward, despite the lack of information on the internet currently. I'm going to assume that you've already got a working Nginx webserver online that supports HTTPS (or you haven't try checking out this and this and this) To do this, grab a copy of your root CA's certificate (excluding the private key of course) and upload it to your server. Then, open up your Nginx configuration file in your favourite text editor (I use sudo nano
) and add the following lines to your http
block:
ssl_client_certificate /path/to/your/root/certificate.pem;
ssl_verify_client on;
ssl_verify_depth 2;
Let's go through these one at a time. The ssl_client_certificate
directive tells Nginx where to find our root certificate, which it should use to verify the certificate of connecting clients. ssl_verify_client
tells Nginx that it should perform verification of all clients connecting via HTTPS, and that nobody is allowed to connect unless they have a certificate signed by the above root CA. Finally, the ssl_verify_depth
parameter instructs Nginx that if a client's certificate isn't directly signed by our root CA then it should follow the certificate chain down to a depth of 2.
Once done, you should only be able to connect to your webserver if you have an appropriate certificate installed in your browser. This is nice and all, but what if we only want to apply this to a single subdomain? Or even a single folder? This presents a problem: Nginx only supports client certificates on everything, or nothing at all.
Thankfully, there's a workaround. First, change ssl_verify_client
from on
to optional
, and then the following to the server
or location
block you want to verify certificates for:
if ($ssl_client_verify != SUCCESS) {
return 403;
}
I've found through trial and error that the whitespace is important. If it doesn't work, double check it and try again! The above snippet simply checks to see if the user has connected with a valid certificate, and will send an HTTP 403 error if they haven't.
That concludes this tutorial. Did it work for you? Did you find it useful Did you read this far? Comment below!