It’s possible to authenticate a user on a website using client certificates instead of a username and a password. The webserver, in my case Apache, uses a server certificate and only clients with the correct client certificate are able to connect to it.
Last night, for an experiment, I’ve created such a setup on OS X 10.8.4 Mountain Lion using the Apache that is pre-installed on OS X.
To save others some headache, here’s the walkthrough on how to go about it. Please keep in mind that I am not an Apache2 config expert or an ssl-guru. There’s probably room for improvement in the configuration files and process listed below.
Make sure you check out the link I’ve pasted at the very bottom of this article. It was a huge help to make this work!
TODOs
- Set up some small app to serve the protected content
- Configure Apache to act as a proxy for that app, SSL Only
- Create Certificate Authority so I can sign my self-made certs
- Create certificates
- Configure Apache to only accept (and proxy) clients that present the right client certificate
- Profit
So let’s get to work.
1. Set up some small app to server the protected content
For me this was a minimal Rails app that is serving a static webpage. In my case I was using thin
and I’ve just started it listening to port 9090.
2. Configure Apache to act as a proxy for that app, SSL Only
The default Apache on OSX has its config files in the folder /etc/apache2/
which in fact is /private/etc/apache2
. It has a subfolder users
that has configs for each user on the system. So in my case, I edited /etc/apache2/users/lukas.conf
.
This just means that every connection that comes in on port :443 will be forwarded to port :9090. Replace :443 with :80 and restart Apache and you should be able to see the app from Step 1. when typing http://localhost
in your browser.
To restart apache you can type
Now, because we said “SSL Only” in the task description, we have to ensure that only SSL connections are accepted. So here’s what my lukas.conf
looks like if we do that:
The config mentions two files, web.crt
and web.key
. It’s time to create those.
Note: If this doesn’t work, then take a look at the apache config in /etc/apache2/httpd.conf
and ensure that the Proxy and SSL modules are being loaded. (That the appropriate lines are not commented out.)
3. Create Certificate Authority so I can sign my self-made certs
I think a CA (and all certs) can be created using the keychain tool that is included in OS X. However, I prefer the command line approach with openssl. It also has the nice side-effect that the guide will be (mostly) valid on a Linux-machine.
I’ve created two directories, /etc/apache2/certs/
and /etc/apache2/certs/ca
. And then I’ve used the shell script from here to create the CA. I’ve pasted the contents into create_ca.sh
in /etc/apache2/certs
.
As you can see, it (amongst other files) creates the ca.conf
in /etc/apache2/certs/ca
with the contents of everything between the two occurrences of EOF
. Afterwards it creates a key, a signing request, and finally the singing certificate ca.crt
in the ca
-folder.
So now just run this shell script with bash create_ca.sh
.
It will ask you to enter details for your CSR. Make sure you enter localhost
when it asks for the FQDN. (Or your domain should you be following these steps for anything else than localhost.) Whatever it is, it should match what you have entered in the apache config as ServerName
.
When it’s done make sure you open /etc/apache2/certs/ca/ca.conf
and replace the string REPLACE_LATER
with /etc/apache2/certs/ca
.
Voila, we have our own GoDaddy now. Just a pity that no browser in the world, not even our own, will trust our certificates!
Let’s move on.
4. Create certificates
We need two certificates. The before mentioned web.crt and a client.crt that will be imported into the OS X-keychain so the browsers can access it.
Creating a certificate is straightforward. Here are the basic steps:
- Create a private key
- Use private key to generate a Certificate Signing Request
- Sign CSR with own CA and create the certificate.
So first let’s create the server certs (these will have passphrases that you must type and remember):
When you now restart Apache it will ask for the passphrase. You should also be now able to connect to https://localhost
in your browser. (Your browser will display an ugly, scary warning that you can ignore since it’s just your local box and you won’t scare away any users..)
Okay, now let’s create the client certificate. The one our browser will have to show to get access to the webapp.
For the CN you can enter your own name, since the certificate is issued for you, the user that wants to access the webapp.
Finally, let’s convert the cert to pkcs#12.
It may ask for an export password. You will need it when you (or the user you issued this for) later import the cert into the keychain.
Before we move on to the last step, let me just mention that the whole CA-business doesn’t have to be in a subfolder of your apache config. It can be on a completely different machine. It’s just a few files that help with issuing certificates and they haven’t got anything to do with Apache. It’s in this folder here for convenience (you would only need the ca.crt file in this setup, btw.), but I would probably put them in an entirely different (and very safe) place if I for example was GoDaddy..
Now the grand finale:
5. Configure Apache to only accept (and proxy) clients that present the right client certificate
Back to the Apache config! We have to tell Apache to disconnect everyone with an SSL-error that doesn’t present the right client certificate.
Restart apache. You should no longer be able to connect to https://localhost
.
6. Profit
In order to be able to connect again you must import the client certificate into the keychain.
Simply open client.p12
and keychain should automatically open and ask you to import the key. Now refresh your browser, you should be able to connect.
Finally, especially in case of any problems, don’t miss this post on garex.net. It’s a fantastic guide on how to make this work and I would probably still be scratching my head if it wasn’t for this text.
P.S.: You can follow me on Twitter.