Skip to content

Instantly share code, notes, and snippets.

@uakfdotb
Created June 29, 2014 06:22
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save uakfdotb/389463820c7f6808f851 to your computer and use it in GitHub Desktop.
Save uakfdotb/389463820c7f6808f851 to your computer and use it in GitHub Desktop.
webssl-tutorial
Subject: Client-side SSL certificates
Hi all,
This is a tutorial for modifying your web application (we'll use PHP in particular) to support client-side SSL certificates, based on my own experience with doing it. I'm still learning, so do let me know if I've done anything that seems insecure or otherwise unwise.
I'm posting here since I don't know anywhere else to post it :)
First I'd like to mention these two web pages for offering a good overview.
* http://cweiske.de/tagebuch/ssl-client-certificates.htm
* https://gist.github.com/mtigas/952344
These have lots of information, but here I will assume a model where you want to run your own self-signed certificate authority on the web server, while not requiring users to install your CA. This means that the web application must allow users to upload certificate signing request, and return signed certificates to them after validation.
So the steps are:
* Client logs in normally and uploads CSR
* Server signs the CSR and sends certificate back to client
* Client takes certificate and key and imports into web browser
* Client then authenticates using certificate later on and server recognizes and accepts it
Set up the CA
=============
The first step is easy. Set up your CA. I enter 3650 days below so that you don't need to update your CA too often.
cd /etc/webssl/
openssl genrsa -des3 -out ca.key 4096
openssl req -new -x509 -days 3650 -key ca.key -out ca.crt
We want to trust our own CA. On Ubuntu:
mkdir -p /usr/share/ca-certificates/extra
cp ca.crt /usr/share/ca-certificates/extra/ca.crt
dpkg-reconfigure ca-certificates
Configure your web server
=========================
Now, you have to configure your web server. We want the web server to use server-side SSL everywhere, and also require client-side certificate in a special directory ("secure" folder). When this directory is accessed, we'll have our web application check the certificate information (provided by the web server via "SSLOptions +StdEnvVars"); if it matches an existing user in the database, the web application should set whatever session variables as needed and then redirect the client to the authenticated area.
Suppose you have these paths already set up for SSL:
* /etc/webssl/web.crt - your website certifictae
* /etc/webssl/web.key - your website key
* /etc/webssl/webca.crt - certificate authority chain for your website
In order to get this to work with Apache, we'll modify webca.crt and actually add ca.crt to it, so that the web server accepts client certificates signed by one of the CA's in webca.crt. Our web application can do the work of verifying that its actually ca.crt and not one of the other CA's. So:
cd /etc/webssl
cat webca.crt ca.crt > webca_.crt
mv webca_.crt webca.crt
Now, in addition to whatever you have set up for SSL in Apache configuration, add configuration for our special secure directory:
<Location /secure>
SSLVerifyClient require
SSLVerifyDepth 1
SSLOptions +StdEnvVars
</Location>
SSLVerifyClient tells Apache to force client-side SSL authentication. SSLVerifyDepth means we shouldn't look higher than one node up in the certificate chain. StdEnvVars tells Apache to forward the detected SSL data to the web application (at least it works for PHP).
Update your database
====================
Your database needs to support storage of certificates that you have signed. In fact you only need to keep track of the serial number and the associated user. In MySQL:
CREATE TABLE certificates (serial INT NOT NULL UNIQUE, user_id INT NOT NULL);
Our web application will use this table to determine whether the client should be allowed.
Sign client certificates
========================
See here for sample code for signing the client certificates: https://gist.github.com/uakfdotb/13f57153abbec9d75c00
In particular, we need to verify that the common name of the CSR matches the user's email address. We probably don't actually need to do this, but it makes verification simpler. Since CSR is generated on client-side, this means that the client will need to enter his or her email address when generating the CSR.
Note that certificates should all have unique serial numbers. If you want to allow your users to delete certificates from the table, then you should find some other means of obtaining unique $next_serial values for each certificate.
The certificates in the example are signed for 365 days. You may wish to allow users to configure this to some degree (it should still be limited to some ceiling probably).
Authenticate client certificates
================================
Here is sample code that goes in the secure/index.php or something similar: https://gist.github.com/uakfdotb/571eb262108e11fb80dc
Basically we verify that the common name and serial match a user. If so, we login.
User-side setup
=================
The user will have to create the CSR, have you sign it, and then import it into browser. To do this, first create CSR:
openssl genrsa -des3 -out client.key 4096
openssl req -new -key client.key -out client.csr
Make sure to use email address for common name. Now copy the certificate signed by server to client.crt, and run:
openssl pkcs12 -export -clcerts -in client.crt -inkey client.key -out client.p12
rm client.crt client.key client.csr
Then most browsers will be able to import client.p12 and you can use that in https://example.com/secure/ to login and redirect to the authenticated users area.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment