Client Side Certificates for Web Apps

October 19, 2011

After figuring out how to call a web service from Java using client side certificates, I was keen to try and set up one of my own, using nginx.

Create Certificate Authority

The first thing to do is create the CA certificate, a role normally performed by one of the commercial certificate suppliers. Doing it ourselves is good for testing, but shouldn't be used for production.

We need to create a key, and then generate a certificate from it:

$ openssl genrsa -des3 -out ca.key 4096
$ openssl req -new -x509 -days 365 -key ca.key -out ca.crt

Our ca.crt file is valid for 365 days.

Create Server Side SSL Certificate

Next, we need to create another key. This will be used to generate an SSL certificate for use on the server. Make sure that the certificate contains the url it's going to be used at (for example drumcoder.co.uk). This should go in the Common Name (eg, YOUR name) field. Don't include any https:// prefix.

$ openssl genrsa -des3 -out server.key 4096
$ openssl req -new -key server.key -out server.csr

Next, we need to sign this key with our CA certificate:

$ openssl x509 -req -days 365 -in server.csr -CA ca.crt -CAkey ca.key -set_serial 01 -out server.crt

Our new server.crt is valid for 365 days.

The serial field is used to ensure that the certificate can be superseded by a new one later on.

We can remove the pass phrase from server.key so that it isn't required each time the web server is restarted:

$ openssl rsa -in server.key -out server.key.insecure
$ mv server.key server.key.secure
$ mv server.key.insecure server.key

Create Client Side Certificate

At this point we have enough to put a normal server side certificate on the web server, but we want to go further than that. We want our server to require the presence of a client side certificate, signed by the same ca.crt, before allowing access to the web application.

First we generate another new key and certificate:

$ openssl genrsa -des3 -out client.key 1024
$ openssl req -new -key client.key -out client.csr

Make sure that the certificate does NOT contain the url it's going to be used at (for example drumcoder.co.uk). Put your name in the Common Name (eg, YOUR name) field. Don't include any reference to the url being accessed.

We then sign this new certificate with our ca.crt:

$ openssl x509 -req -days 365 -in client.csr -CA ca.crt -CAkey ca.key -set_serial 91 -out client.crt

Note that we're using a different serial. If you use the same one as the server SSL certificate, then this will cause problems when you come to import it into the browser.

We're now going to convert this client.crt into client.p12 ready for import into Firefox.

$ openssl pkcs12 -export -in client.crt -inkey client.key -out client.p12 -name "drumcoder"

Copy client.p12 from the server to the client, and import it into Firefox using Advanced/Encryption/View Certificates/Import. The certificate should show up on the Your Certificates tab.

nginx

It's now possible to set up an https server in nginx. Simply copy an existing config and change the following fields:

server {
    listen 443;
    ssl on;
    server_name server.baal;

    ssl_certificate             /etc/nginx/certs/server.crt;
    ssl_certificate_key         /etc/nginx/certs/server.key;
    ssl_client_certificate      /etc/nginx/certs/ca.crt;
    ssl_verify_client           on;

    ...
}

ssl_certificate and ssl_certificate_key are used for normal https access.

ssl_client_certificate points to the certificate that must have signed the client certificate for access to be granted.

ssl_verify_client turns on client certificate validation. Without a client certificate present, nginx will return a 400 error.

References