====== X.509 client certificate authentication with mod_ssl on Apache2.2 ======
{{tag>apache openssl crypto ssl debian}}
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, [[http://tools.ietf.org/html/rfc5246#section-7.4.4|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 [[http://wiki.linuxwall.info/doku.php/fr:ressources:dossiers:ssl_pki:3_pki|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 ]
then
echo "usage: sh genclient.sh "
else
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
echo "Certificate generated for $1"
echo
echo
fi
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.
authorityKeyIdentifier=keyid,issuer
subjectKeyIdentifier=hash
subjectAltName=email:move
issuerAltName=issuer:copy
basicConstraints=CA:FALSE
nsCertType = client, email, objsign
keyUsage = nonRepudiation, digitalSignature, keyEncipherment
keyUsage is important here. Some enlightment from the [[http://www.ietf.org/rfc/rfc2459.txt|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**
{{:en:ressources:dossiers:apache:400_1344_firefox_encryption_tab.jpg|}}
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.
{{:en:ressources:dossiers:apache:400_1345_firefox_import_certificate.jpg|}}
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 [[http://www.modssl.org/|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 [[http://wiki.linuxwall.info/doku.php/fr:ressources:dossiers:ssl_pki:2_apache_ssl|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
EDH-RSA-DES-CBC3-SHA SSLv3 Kx=DH Au=RSA Enc=3DES(168) Mac=SHA1
EDH-DSS-DES-CBC3-SHA SSLv3 Kx=DH Au=DSS 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 [[http://tools.ietf.org/html/rfc5246#appendix-C|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:
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:
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
3586
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
[[http://httpd.apache.org/docs/2.0/mod/mod_ssl.html#sslrandomseed|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
~~DISCUSSION~~