amuck-landowner

Client-side SSL certificates

perennate

New Member
Verified Provider
gist: https://gist.github.com/uakfdotb/389463820c7f6808f851

note: this may be a bit rough, I wrote it in 20 min

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.

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.
 
Last edited by a moderator:
Top
amuck-landowner