====== 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~~