Validate client SSL certificates in your application with HaProxy

It's really useful to be able to validate the clients that connect to your app by issuing SSL certificates for them.

This comes as a possible 2-step procedure. On one hand, only clients that present a valid (i.e: not expired, signed by a trusted CA, etc) will be able to connect to/use your services.

But on the other hand you can use additional checks in your application to authorize that a specific certificate can be used for different actions, but not others.

My use case was an API that could be used by clients without a certificate, but at the same time by clients that required to use a certificate (due to internal security policies that required that we issue a client SSL certificate for them in order to use our API).

So we had to check that some API keys should use specific SSL certificates, but others will be allowed to access the API without any specific SSL certificates at all.

Here's what we did.

Becoming your own CA

When you get a certificate from VeriSign, GoDaddy, Comodo, or any other provider, you will get a certificate signed by them.

That means that others will trust your certificate (for example when presented by your web server) because these specific entities signed your certificate, they are the CAs or Certificate Authorities that everyone trust (their own certificates are being shipped with the major operating systems, thus they are trusted "out of the box").

When you sign SSL certificates for your clients, you become the CA. This doesn't mean that your certificates will be trusted by others "out of the box", but you can trust certificates signed by your own CA (i.e: when clients present the certificate to your web server) and sign other certificates and that will be fine.

Create your OpenSSL CA so you can sign client certificates

There are plenty of tutorials out there about how to create and sign certificates, these are some simple instructions to get you through. The following 2 commands will create your CA private key and your CA certificate, you will use these to sign your client certificates later on:

Create and sign SSL certificates for your clients using your own CA

Once you have your CA certificate in place, you can start signing certificates. The instructions below will reproduce the steps needed in order to create a Certificate Signing Request and to sign it with your CA.

Ideally, your clients should send you their CSR, but in this case we're just going to create one for them:

The first command creates a private key for the CSR. The second command will create the CSR. These are the two commands that your clients should run on their own.

The last command is the one that actually signs the CSR with your CA and creates a certificate that's ready to be used.

Get the SHA1 fingerprint of the SSL certificate

For every issued certificate, you might need to associate it with specific permissions in your API or Web server, one way of recognicing each individual certificate is by their SHA1 fingerprint:

The output string is what identifies each certificate and is what you will be attaching your API keys (or permissions) to.

Setup HAProxy for SSL connections and to check client certificates

The next step is to setup HaProxy to so SSL offloading, that means that HaProxy "will talk" SSL with your clients, and forward the requests in plain HTTP to your API/Web servers.

Note how we use the crt directive to tell HaProxy which certificate it should present to our clients.

We are also using:

ca-file /etc/ssl/certs/myca.crt verify optional crt-ignore-err all

To state that we want to check the CA signing all client certificates with our own CA certificate, and that if there are validation errors, to not close the connection (that's ok, since we're going to forward the verification details to our own API/Web server and carry on the additional validations, this is needed because we also want to let clients without a certificate to access our services).

Send the information of the client SSL certificate to your app

So far, HaProxy will validate the SSL certificates of your clients, but it will not do anything else with that information. Let's forward that information to our own API/Web server so we can do more complex things there. For this, we're going to set some extra special headers in the requests:

Validate your client certificates before allowing access to your services

Now let's say that you want to authorize some clients without a certificate to access your services, you can then check if the header x-ssl-client-cert is "1" (presented a certificated) or "0" (no client certificate available). You can then take action.

Let's also say that in case a client presented its SSL certificate (x-ssl-client-cert="1") you want to know if it was valid or not (i.e: the right signing CA, expiration dates, etc), you can then check the header x-ssl-client-verify against the value "0" (no errors).

But you can take this one step further, and check the SHA1 fingerprint of the presented certificate to know if this specific certificate is allowed to use a specific API key or service, you can check the value of the head x-ssl-client-sha1, so mixing the 3 checks that would mean x-ssl-client-cert="1", and x-ssl-client-verify="0" and x-ssl-client-sha1="your_known_allowed_sha1".

That's it! You can now do SSL offload with HaProxy, but at the same time have full control over complex policies for authentication and authorization of your clients using SSL certificates.