Mini Guide: Quick and easy Client SSL Validation (with OpenSSL and Nginx)

Mini Guide: Quick and easy Client SSL Validation (with OpenSSL and Nginx)
Photo by Georg Bommeli / Unsplash

I run a number of services from home, for my own convenience, that I want to be able to access remotely, securely, on the go, without the cumbersome need for usernames and passwords all the time.

This mini-guide looks at securely identifying the end user with client SSL certificates instead of conventional credentials.

Some of my other posts cover aspects like remote access and the complete SSL setup.

Disclaimer

This is the very simple version.

There is no CA and it has potential flaws, but it works for me and this use case as I am the only client - if the certificate were to be compromised I can regenerate the key and cert and be fine.

For a multi user environment a private CA would be required but that is outside the scope of this post.

Motivation

The services running include tools to view the CCTV streams, manage doorbell notifications, access local devices, code-server and more.

When I'm on the move, or not at home (for example in the office) I want to be able to quickly access these services, but still securely - I don't want any randomers finding and accessing it so something totally open is not an issue, firewall rules won't do as IP Addresses change when mobile, VPN is clunky on the phone, etc.

The ideal thing for me is verifying the client device, which is where client SSL verification comes in.

Client SSL Certificates

We're mostly used to server SSL authentication/verification where the server has a certificate that can be resolved back to a trusted entity that granted it after confirming it's all legitimate (usually by DNS, .well-known or email).

In the case of Client SSL Certificates, the device or browser (i.e. client) can present an SSL certificate for the server to check and validate. If the certificate is valid according to the server (not expired and matching the expected cert/chain) it can permit the traffic.

OpenSSL

We need a private and public key pair, in the SSL world this is usually a key (private) and certificate (public).

First, the key

openssl genrsa -out client.key 4096

Then, the certificate

openssl req -new -x509 -days 3650 -key client.key -out client.crt

It's naughty, but this one is valid for 10 years as it's a pain to reinstall on all the devices I have regularly.

The fields don't matter so I enter a . to keep it blank mostly, even the fqdn isn't checked:

Finally, the pfx

openssl pkcs12 -export -out client.pfx -inkey client.key -in client.crt	

Having a pfx to import has a few benefits, firstly its what browsers and mobile devices prefer, second - you can password protect it with a passphrase, so if the file itself is compromised you have a layer of protection.

Nginx

For each site that needs securing...

Before any location directives we need to be sure the SSL client certificate is verified using:

if ($ssl_client_verify != SUCCESS) {
  return 403;
}

And we also need to specify the certificate to check against:

ssl_client_certificate /etc/nginx/certs/client.crt;

passphase

As the browser (client) may (or is highly unlikely to randomly offer up a client cert) we need to make the challenge optional:

ssl_verify_client optional;

Otherwise we'll be denied access completely.

The if above guards against the verification then failing when a certificate is presented.

A complete config is as follows, the letsencrypt aspects are a topic for another post:

The Browser (client)

Without any certificate we get a 403:

With the wrong certificate, not much better:

In Chrome, a client certificate can be imported from the Security settings:

Once imported the org name should be listed:

I've found that a complete browser restart is often required to refresh the certificates at times.

The certificate challenge will prompt the browser to confirm the certificate to use:

Once the correct certificate is selected the site is accessible: