SSL Client Certificates

Have you ever heard of this strange thing called client certificates or did you ever encounter this strange popup?

client cert

As it turns out, client certificates are a feature of the TLS protocol and mainly used for single sign on (SSO) on intranet sites. While the idea seems kind of nice that you do not have to enter a username and password anymore, things get complicated when using and applying it from an end user perspective, especially in non-standard environments. In order to get some understanding of the basics, let us set up a simple node server using client certificates as a way of authentication.

As a first step, let us create a new certificate authority (CA) by creating a new key and self-sign it.

openssl genrsa -out rootCA.key 2048
openssl req -x509 -new -nodes -key rootCA.key
  -days 1024 -out rootCA.crt
  -subj "/C=DE/ST=Germany/L=HD/O=Company/OU=Org/CN=rootCA"

For our server part, we now generate a server key, create a certificate signing request (csr file) for localhost and sign this certificate with our new certificate authority.

openssl genrsa -out server.key 2048
openssl req -new -key server.key -out server.csr
  -subj "/C=DE/ST=Germany/L=HD/O=Company/OU=Org/CN=localhost"
openssl x509 -req -in server.csr -CA rootCA.crt
  -CAkey rootCA.key -CAcreateserial -out server.crt -days 500

For the client part, we do exactly the same as for the server part and export the client certificate in pkcs12 format.

openssl genrsa -out client.key 2048
openssl req -new -key client.key -out client.csr
  -subj "/C=DE/ST=Germany/L=HD/O=Company/OU=Org/CN=client"
openssl x509 -req -in client.csr -CA rootCA.crt
  -CAkey rootCA.key -CAcreateserial -out client.crt -days 500
openssl pkcs12 -export -inkey client.key -in client.crt
  -name client -out client.p12

Following these steps, we now have a client certificate and a server certificate signed by our self signed root certificate authority. With a few lines of node, we can now start a local server trusting our certificate authority and requesting a client certificate.

var https = require('https');
var fs = require('fs');

var options = {
    key:    fs.readFileSync('ssl/server.key'),
    cert:   fs.readFileSync('ssl/server.crt'),
    ca:     fs.readFileSync('ssl/rootCA.crt'),
    requestCert:        true,
    rejectUnauthorized: false
};

https.createServer(options, function (req, res) {
    if (req.client.authorized) {
        res.writeHead(200);
        res.end('approved');
    } else {
        res.writeHead(401);
        res.end('denied');
    }
}).listen(5000);

The first thing you will notice after starting the server is that the identity of your server is not valid. This is somehow reasonable as our client (chrome in this case) cannot verify the server certificate with any given certificate authority.

client cert

After importing the rootCA.crt file to your local keystore (keychain/ mac in this case), chrome is now trusting our local server (still yellowish, as no public records are found for this site).

client cert

After reloading you should see a denied response, as you also need to import the client certificate into your local keystore.

client cert

Finally, we get the mentioned popup to authenticate with our client certificate and our request is approved. Note that there are ways to configure the client to automatically select certificates via policies.

client cert

client cert

While not having to enter a username and password anymore is good (the user can be encoded in the CN record of the certificate), you can see that there a quite a few steps to take in order to connect to a server with a proper client certificate. Also note that you need to have access to the client certificate in order to make proper requests from any given client, and usually the storage of these keys is depending on the operating system (so the certificate needs to be imported or distributed). If you are interested to try it out yourself, you can also find all information on GitHub.

Written on June 27, 2015