Client SSL certificate authentication with Apache
Passwords are one of the most common authentication methods used for websites. There are however alternative methods. This post is going to go over configuring Apache to authenticate users using Client X.509 certificates.
Note: the examples given in this post are for CentOS 7, they will need to be adapted slightly for other Linux distributions.
Installing required packages
Start by installing Apache, the Apache mod_ssl
module and OpenSSL using
yum
:
yum install -y httpd mod_ssl openssl
Once Apache is installed, enable and start the service with systemctl
:
systemctl enable httpd.service
systemctl start httpd.service
If everything goes well you should now be able to make a test file and request
it with curl
:
echo 'Hello World' > /var/www/html/test.txt
curl --insecure https://localhost/test.txt
Note: the --insecure
option is used because Apache will be using a
default self signed certificate.
Setup a certificate authority
Start by making a copy of the /etc/pki/CA/
directory, and restricting the
permissions on the private
directory:
cp -r /etc/pki/CA/ /etc/httpd/conf/ca
chmod 700 /etc/httpd/conf/ca/private
Then take a copy of the default openssl.cnf
configuration and update the CA
path:
cp /etc/pki/tls/openssl.cnf /etc/httpd/conf/ca/openssl.cnf
sed -i 's|/etc/pki/CA|/etc/httpd/conf/ca|' /etc/httpd/conf/ca/openssl.cnf
The openssl req
command can then be used to generate a private key and a new
CA certificate.
openssl req \
-config /etc/httpd/conf/ca/openssl.cnf \
-nodes \
-newkey rsa:4096 \
-keyout /etc/httpd/conf/ca/private/cakey.pem \
-new \
-x509 \
-days 7300 \
-sha256 \
-extensions v3_ca \
-subj '/C=GB/ST=England/O=Alice Ltd/OU=Alice Ltd Certificate Authority/CN=Alice Ltd Root CA' \
-out /etc/httpd/conf/ca/cacert.pem
Note: the -nodes
option will remove the need to encrypt the private key
with a passphrase. Skipping this option to use a passphrase would be better
from a security point of view.
Once the new CA certificate has been generated create an new index and serial file:
touch /etc/httpd/conf/ca/index.txt
echo 1000 > /etc/httpd/conf/ca/serial
/etc/httpd/conf/ca/
should now have a folder structure similar to the
following:
.
+-- cacert.pem
+-- certs
+-- crl
+-- index.txt
+-- newcerts
+-- openssl.cnf
+-- private
¦ +-- cakey.pem
+-- serial
The CA created with the steps above is fine as an example. However it does not do things like use an intermediary CA. For a more detailed guide on setting up a CA with OpenSSL I would recommend having a look at OpenSSL certificate authority by Jamie Nguyen.
Create a client certificate
Commands similar the following can be used to generate a private key and certificate signing request for a user:
CLIENT_NAME='alice'
CLIENT_EMAIL="[email protected]"
mkdir -p "/etc/httpd/conf/users/${CLIENT_NAME}/"
openssl req -newkey rsa:4096 \
-keyout "/etc/httpd/conf/users/${CLIENT_NAME}/key.pem" \
-out "/etc/httpd/conf/users/${CLIENT_NAME}/request.pem" \
-days 365 -nodes \
-subj "/C=GB/ST=England/O=Alice Ltd/CN=${CLIENT_NAME}/emailAddress=${CLIENT_EMAIL}"
The request can then be signed with the CA:
openssl ca \
-config /etc/httpd/conf/ca/openssl.cnf \
-in /etc/httpd/conf/users/alice/request.pem \
-out "/etc/httpd/conf/users/${CLIENT_NAME}/cert.pem"
Configure Apache
The following directives need to be set in /etc/httpd/conf.d/ssl.conf
:
SSLCACertificateFile /etc/httpd/conf/ca/cacert.pem
SSLVerifyClient require
SSLVerifyDepth 1
The SSLCACertificateFile directive tells
Apache which CA certificate to use when verifying if client certificates are
valid, the SSLVerifyClient directive ensures
clients have to authenticate with a valid certificate, and the
SSLVerifyDepth directive tells Apache how many
issuers in a certificate chain should be checked before giving up. For the
examples in this post client certificates are always signed directly by the
root CA, so 1
is fine.
To avoid files being accessed over HTTP create
/etc/httpd/conf.d/ssl_required.conf
with the following contents:
<Location '/'>
SSLRequireSSL
</Location>
Finally restart Apache using systemctl
to load the new configuration:
systemctl restart httpd.service
If everything goes well, the previous curl
command should now only work if a
valid client certificate is used:
$ curl --insecure https://localhost/test.txt
curl: (35) NSS: client certificate not found (nickname not specified)
$ curl \
--insecure \
--cert /etc/httpd/conf/users/alice/cert.pem \
--key /etc/httpd/conf/users/alice/key.pem \
https://localhost/test.txt
Hello World
Future improvements
The configuration in this post skips over a few important topics such as certificate revocation. Next weeks post will look at some of these topics in more detail.