X.509 client certificate authentication with mod_ssl on Apache2.2

Authenticating users is a pretty straightforward task. On todays internet you almost never see a web application that doesn't require a username and password to access specific features. But this mechanism has a trade off: you need to allow the visitor to negociate TCP and HTTP session before you process the credentials, thus opening a door for all sorts of content injections.

On some very specific web application, you might want to cover this risk. And the solution for this is to force the user to send a X.509 certificate to authentificate himself BEFORE you even access the data.

This X.509 certificate, when used on the client side, is generally called a client certificate. To successfully negociate a TLS session with the server, the server can require the client to furnish a valid client certificate. The server does so by sender a Certificate Request during the TLS Handhshake (right avec the ServerKeyExchange message, section 7.4.4 of the RFC).

So, what we will do in this article, is to configure the client certificate authentication in Apache 2.2 and explain how you can check the X509 fields to describe who can access what.

Generate a client certificate

Obviously, if you want to give x.509 certificates to your users, you need a PKI somewhere. I am not going to describe how to set up a whole PKI (I have already did it), but only describe the client certificate part.

For client certificate generation, I use a bash script similar to this one. The comments are directly in the script so it's easier to read. The script will generate a private key and a CSR, submit the CSR for signature to the Root CA private key, and bundle the whole thing into a PKCS#12 format (contains the private key and the certificate into a ciphered enveloppe).

#! /bin/sh                     
if [ $# != 1 ]           
        echo "usage: sh genclient.sh <name or fqdn>"
        mkdir $1
        # generate a new pair of RSA keys
        # and a certificate request (CSR)
        openssl req -new -config ca/openssl.cnf \
        -nodes -keyout $1/$1.key -out $1/$1.csr
        # Ask the RootCA to sign the CSR and 
        # thus create a signed certificate
        # The configuration file (openssl.cnf) must 
        # contain a section called [client_cert]
        openssl ca -config ca/openssl.cnf -extensions client_cert \
        -in $1/$1.csr -out $1/$1.pem
        # Export the client signed certificate and the
        # private RSA key into a PKCS#12 file
        openssl pkcs12 -export -in $1/$1.pem -inkey $1/$1.key \
        -name "$1 Personal Certificate" -certfile \
        ./ca/ca-linuxwall.crt -out $1/$1.p12
        echo "Certificate generated for $1"

in your OpenSSL configuration, you need a special section for client certificates. The [client_cert] in openssl.cnf for our client certificate generation is the following:

[ client_cert ]      
# These extensions are added when 'ca' signs a request.
nsCertType = client, email, objsign
keyUsage = nonRepudiation, digitalSignature, keyEncipherment

keyUsage is important here. Some enlightment from the RFC:

      The nonRepudiation bit is asserted when the subject public key is
      used to verify digital signatures used to provide a non-
      repudiation service which protects against the signing entity
      falsely denying some action, excluding certificate or CRL signing.
      The digitalSignature bit is asserted when the subject public key
      is used with a digital signature mechanism to support security
      services other than non-repudiation (bit 1), certificate signing
      (bit 5), or revocation information signing (bit 6). Digital
      signature mechanisms are often used for entity authentication and
      data origin authentication with integrity.
      The keyEncipherment bit is asserted when the subject public key is
      used for key transport.  For example, when an RSA key is to be
      used for key management, then this bit shall asserted.

Import it into you browser

Now that we have a PKCS#12 file for our user, we need to import it into the software the client is going to use to negociate the TLS session. Most likely, if your server is apache, your client will be Firefox :)

Firefox will also require the certificate of the Root CA in DER format.

The steps to import you certificate into Firefox are the following.

1. Go to Tools > Options > Advanced > Encryption

2. Click on View Certificates > Authorities There, you need to Import the PKI Root CA signed certificate. Check the 3 lines to make sure it's valid for all cases.

3. Still in the View Certificates window, go to Your certificates and import the PKCS#12 file. Firefox should ask you the password to unlock the PKCS#12 enveloppe and extract the private key and the certificate.

That's all. :)

Apache's virtual host configuration

I assume you have a working Apache Installation. If not, maybe setting up client certificate authentication is not the first thing you should try to do :)

The SSL support for Apache is provided by mod_ssl which, in turns, uses OpenSSL library.

On the server side, you also need a X.509 signed certificate generated on your PKI. Once again, I have already described this here.

Mod_SSL configuration

Let's suppose you have a virtual host that receives the requests directed to https://secure.mydomain.duh If, like me, you use Debian, you will most likely have a virtual host configuration file in /etc/apache2/sites-available/ that is called secure.

Let's start with the global SSL options:

# activate SSL for the virtual host
SSLEngine On
# path to the server signed certificate
SSLCertificateFile /etc/apache2/certs/secure.mydomain.duh.pem
# path to the server key
SSLCertificateKeyFile /etc/apache2/certs/secure.mydomain.duh.key
# path to Root CA signed certificate
SSLCACertificateFile /etc/apache2/certs/ca-mydomain.duh.crt
# path to CA Certificate Revocation List
SSLCARevocationFile /etc/apache2/certs/crl-mydomain.duh.crl
# duration of the session cache
SSLSessionCacheTimeout 3600

Select the appropriate cipher list

mod_ssl uses the syntax of openssl to define a list of acceptable ciphers. The client will send a list of the ciphers he knows in the CLIENT HELLO message, and the server will select the best one that is present in the client's list and its own. You can test you CipherList in the openssl command line to check which ciphers are going to be accepted.

For example, if you want to use the directive SSLCipherSuite ALL:!LOW:!MED:!SSLv2:!MD5:!DES, you can test the list of corresponding ciphers as follow:

$ openssl ciphers -v 'ALL:!LOW:!MED:!SSLv2:!MD5:!DES'
ADH-AES256-SHA       SSLv3 Kx=DH   Au=None Enc=AES(256)  Mac=SHA1
DHE-RSA-AES256-SHA   SSLv3 Kx=DH   Au=RSA  Enc=AES(256)  Mac=SHA1
DHE-DSS-AES256-SHA   SSLv3 Kx=DH   Au=DSS  Enc=AES(256)  Mac=SHA1
AES256-SHA           SSLv3 Kx=RSA  Au=RSA  Enc=AES(256)  Mac=SHA1
ADH-AES128-SHA       SSLv3 Kx=DH   Au=None Enc=AES(128)  Mac=SHA1
DHE-RSA-AES128-SHA   SSLv3 Kx=DH   Au=RSA  Enc=AES(128)  Mac=SHA1
DHE-DSS-AES128-SHA   SSLv3 Kx=DH   Au=DSS  Enc=AES(128)  Mac=SHA1
AES128-SHA           SSLv3 Kx=RSA  Au=RSA  Enc=AES(128)  Mac=SHA1
ADH-DES-CBC3-SHA     SSLv3 Kx=DH   Au=None Enc=3DES(168) Mac=SHA1
DES-CBC3-SHA         SSLv3 Kx=RSA  Au=RSA  Enc=3DES(168) Mac=SHA1
RC4-SHA              SSLv3 Kx=RSA  Au=RSA  Enc=RC4(128)  Mac=SHA1

The columns correspond to the fields defined in the appendix C of the RFC

This list of ciphers is quite acceptable. We can add it to our virtual host file as follow:

# list of accepted ciphers
SSLCipherSuite ALL:!LOW:!MED:!SSLv2:!MD5:!DES

So far, this is a classic Server side configuration. Nothing specific to client certificate authentication here.

It's coming now :)

Give access to specific resources

Imagine that you want to protect the folder containing the photos of your drunk brother humping a pumpkin (and yes you might want to do that), you need to add the following section to your virtual host file:

        <Directory /var/www/secure/drunkbrotherhumpkinpumkin/>
                Options Indexes FollowSymLinks
                AllowOverride None
                Order allow,deny  
                allow from all
                SSLVerifyClient Require
                SSLVerifyDepth 3
                SSLRequire %{SSL_CLIENT_S_DN_O} eq "Family"

The first part of the Directory is classic, but the second is more interesting. Basically, it says the following:

  • SSLVerifyClient Require imposes the use of a client certificate to establish a TLS session with the server
  • SSLVerifyDepth 3 indicates the maximum level of verification of the client certificate with the Root CA. We give the possibility to have a intermediate CA between the Root CA and the client.
  • SSLRequire %{SSL_CLIENT_S_DN_O} eq “Family” checks that the X.509 certificate has an organization field set to Family. You can thus control who is accessing this folder among the users of the PKI.

Similarly, you can control the access to a proxy directive as follow:

        <Proxy *>         
                Order deny,allow  
                Allow from all
                SSLVerifyClient Require
                SSLVerifyDepth 3
                SSLRequire %{SSL_CLIENT_S_DN_O} eq "Administrator"
        ProxyRequests Off  
        ProxyPass /control-panel/ http://localhost:10000/
        ProxyPassReverse /control-panel/ http://localhost:10000/

Yes, this is a proxy to a webmin interface. Yes webmin is a major security flaw on a production server, but if you must have one, at least have it secured ;)

Some mod_ssl tuning

Entropy source

You might want to specify which entropy source is used by mod_ssl. On Linux, the best entropy source available is /dev/random, but /dev/urandom is not bad either.

What's the difference ?

When read, the /dev/random device will only return 
random bytes within the estimated number of bits of 
noise in the entropy pool. /dev/random should be 
suitable for uses that need very high quality randomness 
such as one-time pad or key generation. When the entropy 
pool is empty, reads from /dev/random will block 
until additional environmental noise is gathered.

A read from the /dev/urandom device will not block 
waiting for more entropy. As a result, if there is 
not sufficient entropy in the entropy pool, the returned 
values are theoretically vulnerable to a cryptographic 
attack on the algorithms used by the driver. Knowledge 
of how to do this is not available in the current 
non-classified literature, but it is theoretically 
possible that such an attack may exist. If this is a 
concern in your application, use /dev/random instead. 

Note that the entropy pool is only 4KB… so if you use /dev/random and SSL is slow, take a look at :

# cat /proc/sys/kernel/random/entropy_avail

Anyway, if you want to set this in Apache, add the following line somewhere in httpd.conf:

SSLRandomSeed startup file:/dev/random 1024
SSLRandomSeed connect file:/dev/random 1024

Documentation is here.

SSL Session Cache

If you want to enable session caching, and avoid complete handshakes every time a client reconnect, add the following to httpd.conf:

SSLSessionCache dbm:/var/cache/apache2/sslcache


en/ressources/dossiers/apache/client_certificate.txt · Last modified: 2011/03/16 01:30 (external edit)
CC Attribution-Noncommercial-Share Alike 3.0 Unported
www.chimeric.de Valid CSS Driven by DokuWiki do yourself a favour and use a real browser - get firefox!! Recent changes RSS feed Valid XHTML 1.0