When restricting access to web content or web applications, more often than not, basic username/password authentication is the solution. While this might be enough for a personal blog, sites with sensitive content will probably need a little more work.
A couple of commonly used and recommended options are restricting access by IP, limiting the number of login attempts, using captchas to combat bots, one time passwords etc. An option that is rarely mentioned is SSL Client Certificates.
By using SSL Client Certificates you deny access to all except those clients that have a valid certificate installed in their browser. For obvious reasons this method can not (conveniently) be used on a site where registration is public, for instance social sites like Twitter or Facebook. Intranets and administration tools are typically used only by a select few users that are known to the site administrator, in these cases SSL Client Certificates are a great option.
The first step in getting SSL Client Certificate authentication up and running for your application is getting it working on your dev machine, something I had to do myself the other day. Symantec provides a great SSL guide for Apache. It's quite extensive so I'm going to recap the steps involved in getting a development environment up and running on your local machine.
I'm going to assume that you already have openssl and apache2 installed. I'm also going to assume that you are running a UN*X system since that's what all cool developers do ;)
Creating the SSL Certificates
We'll start by creating the SSL certificates. We need one for the web application, another one for signing our client certificates and then a third for our client. Assuming we only have one client that is. The first certificate is for the server. This is required for basic SSL (https) functionality. In this example we'll create a self signed certificate but for your live servers you should use a certificate signed by a trusted CA (such as Verisign). Read the semantic article series mentioned above to learn more about this.
openssl req -new -x509 -days 365 -sha1 -newkey rsa:1024 -nodes -keyout ssl/server.key -out ssl/server.crt -subj '/O=Seccure/OU=Seccure Labs/CN=test.local'
The above will create two files, server.key and server.crt. Notice that at the end I add the hostname of the server (or vhost) that we want to create a certificate for. In the example I use test.local, you should change this to whatever you are using. Move server.crt and server.key to a directory called 'ssl' inside your apache configuration folder. If ssl doesn't exist create it. example: /etc/apache2/ssl/
The next two certificates involve a few extra steps. First we need to create a local Certification Authority which basically mean we make a certificate that we'll use to sign other certificates. The second step is creating and signing the client certificate.
We start by jumping to a folder in which we can fool around, I'll use /tmp/ssl/.
#mkdir -p ssl-ca/certs
#mkdir -p ssl-ca/crl
mkdir -p ssl-ca/requests
mkdir -p ssl-ca/private
mkdir -p ssl-ca/newcerts
echo "01" > ssl-ca/serial
touch ssl-ca/index.txt
touch ssl-ca/openssl.cnf
mkdir clients
Now we need to create the openssl configuration, open ssl-ca/openssl.cnf in your favorite editor and add the configuration settings below. Note that you have to change the path to your ssl-ca folder if you don't use the same one I did.
RANDFILE = /dev/urandom
[ ca ]
default_ca = CA_default
[ CA_default ]
dir = /tmp/ssl/ssl-ca
certs = $dir/certs
new_certs_dir = $dir/newcerts
crl_dir = $dir/crl
database = $dir/index.txt
private_key = $dir/private/ca.key
certificate = $dir/ca.crt
serial = $dir/serial
crl = $dir/crl.pem
RANDFILE = $dir/private/.rand
default_days = 365
default_crl_days = 30
default_md = sha1
preserve = no
policy = policy_anything
name_opt = ca_default
cert_opt = ca_default
[ policy_anything ]
countryName = optional
stateOrProvinceName = optional
localityName = optional
organizationName = optional
organizationalUnitName = optional
commonName = supplied
emailAddress = optional
[ req ]
default_bits = 1024
default_md = sha1
default_keyfile = privkey.pem
distinguished_name = req_distinguished_name
x509_extensions = v3_ca
string_mask = nombstr
[ req_distinguished_name ]
countryName = Country Name (2 letter code)
countryName_min = 2
countryName_max = 2
stateOrProvinceName = State or Province Name (full name)
localityName = Locality Name (eg, city)
0.organizationName = Organization Name (eg, company)
organizationalUnitName = Organizational Unit Name (eg, section)
commonName = Common Name (eg, YOUR name)
commonName_max = 64
emailAddress = Email Address
emailAddress_max = 64
[ usr_cert ]
basicConstraints = CA:FALSE
[ ssl_server ]
basicConstraints = CA:FALSE
nsCertType = server
keyUsage = digitalSignature, keyEncipherment
extendedKeyUsage = serverAuth, nsSGC, msSGC
nsComment = "OpenSSL Certificate for SSL Web Server"
[ ssl_client ]
basicConstraints = CA:FALSE
nsCertType = client
keyUsage = digitalSignature, keyEncipherment
extendedKeyUsage = clientAuth
nsComment = "OpenSSL Certificate for SSL Client"
[ v3_req ]
basicConstraints = CA:FALSE
keyUsage = nonRepudiation, digitalSignature, keyEncipherment
[ v3_ca ]
basicConstraints = critical, CA:true, pathlen:0
nsCertType = sslCA
keyUsage = cRLSign, keyCertSign
extendedKeyUsage = serverAuth, clientAuth
nsComment = "OpenSSL CA Certificate"
[ crl_ext ]
basicConstraints = CA:FALSE
keyUsage = digitalSignature, keyEncipherment
nsComment = "OpenSSL generated CRL"
Time to create the CA certificate.
cd ssl-ca
openssl req -config openssl.cnf -new -x509 -days 3652 -sha1 -newkey rsa:1024 -keyout private/ca.key -out ca.crt -subj '/O=Seccure/OU=Seccure Root CA'
Time to create and sign the client certificate.
cd /tmp/ssl/clients/
openssl req -new -sha1 -newkey rsa:1024 -nodes -keyout testuser.key -out testuser.pem -subj '/O=Seccure/OU=Seccure Labs/CN=TestUser'
This time we have to sign the certificate with our CA certificate. Only certificates signed by us will have access to our web application.
mv testuser.pem ../ssl-ca/requests/
cd ../ssl-ca/
openssl ca -config openssl.cnf -policy policy_anything -extensions ssl_client -out requests/signed.pem -infiles requests/testuser.pem
In this step we export the certificate to a format that your browser can use, namely PKCS#12.
mv requests/signed.pem /usr/ssl/clients/testuser.pem
openssl pkcs12 -export -clcerts -in testuser.pem -inkey testuser.key -out testuser.p12
You should now have 3 certificates(files), server.crt, ca.crt and testuser.p12.
Configuring Apache for SSL
Now that our required certificates are created we can put it all together. We start by enabling SSL in Apache. Open up your httpd.conf in you favorite editor and add the following.
Listen 443
AddType application/x-x509-ca-cert .crt
#AddType application/x-pkcs7-crl .crl
#SSLPassPhraseDialog builtin
SSLSessionCache "shmcb:/var/run/ssl_scache(512000)"
SSLSessionCacheTimeout 300
SSLMutex "file:/var/run/ssl_mutex"
SSLOptions +StrictRequire
SSLProtocol +all
SSLCipherSuite HIGH:MEDIUM:!aNULL:+SHA1:+MD5:+HIGH:+MEDIUM
SSLCipherSuite HIGH:MEDIUM:EXP:!aNULL:+SHA1:+MD5:+HIGH:+MEDIUM:+EXP
The values for SSLSessionCache and SSLMutex may vary depending on platform. You probably have defaults for them in the file extras/httpd-ssl.conf in your apache configuration folder. Somewhere in httpd.conf you should have a LoadModule directive for mod_ssl.so. Make sure that row is uncommented.
LoadModule ssl_module libexec/apache2/mod_ssl.so
We also have to configure our site to use SSL. In my case I'm setting up the web application as a virtual host.
<VirtualHost test.local:443>
ServerName test.local
DocumentRoot "/home/www/test/"
SSLEngine on
SSLCertificateFile /etc/apache2/ssl/server.crt
SSLCertificateKeyFile /etc/apache2/ssl/server.key
SSLOptions +ExportCertData
SSLCACertificateFile /etc/apache2/ssl/ca.crt
SSLVerifyClient require
<Directory "/home/www/test/">
SSLRequireSSL
Options Indexes FollowSymLinks
AllowOverride All
Order allow,deny
Allow from all
</Directory>
</VirtualHost>
If you've been paying attention you've noticed that we reference two of our certificates in the configuration above.
The SSLVerifyClient directive tells apache to require a valid SSL Client Certificate while SSLCACertificateFile tells Apache which CA certificate the client certificates should be signed with. We've already moved server.crt and server.key in to the right place. Now lets do the same with ca.crt.
cp /tmp/ssl/ssl-ca/ca.crt /etc/apache2/ssl/
That takes care of our server configuration. Restart Apache and make sure no errors turned up in the logs. If you load up the site in your browser you should now be denied access. The actual error given may vary, in my case it says 'SSL connection error'. To fix this we need to import our client certificate in the browser. The procedure may vary from browser to browser. I'll use Firefox as an example since that's my browser of choice.
Importing the SSL Client certificate into your browser
The final step is telling our browser to use the client certificate. In Firefox this is done by importing the certificate. It's probably just as simple in other browsers.
In "Preferences / Advanced / Encryption" you have a "View Certificates" button. Clicking that opens up a window with a list and a bunch of buttons. At the top you have a menu with the option "Your Certificates". Click that item and then on the "Import" button near the bottom of the window. This will open a file selection dialog. Select the browser prepared certificate (testuser.p12) and click open.
Thats it, you have now completed all the steps and should be able to access your secured web application. To improve it further you could modify your application so that each of your users are tied to a specific client certificate. But I'll save that subject for another blog post.
Stay tuned and happy hacking.