Apache Server Client Certificate Authentication

by DanielBlack

Apache client side authentication is based off the httpd mod_ssl documentation and has been deployed for a number of CACert systems like lists and webmail (for staff). Apache configurations for client side authentication should appear in a VirtualHost directive though they can exist under other directives like Location. These directives are in addition to SSL server configuration though I tend to use SSLCACertificatePath and not use SSLCertificateChainFile.

Basic Client Side Authentication

This is for the case we want a preposition of the website to be accessible by certificate only. In this case any certificate from a set of CA's.

## Client Verification
SSLVerifyClient optional
SSLVerifyDepth 3
SSLCADNRequestPath /usr/share/ca-certificates/cacert.org/

# error handling
RewriteEngine        on
RewriteCond     %{SSL:SSL_CLIENT_VERIFY} !=SUCCESS
RewriteRule     .? - [F]
ErrorDocument 403 "You need a client side certificate issued by CAcert to access this site"

SSLCADNRequestPath contains a path of the certificates that you will accept for this site. This will need to be in the openssl format contain links from the subject_hash to the file like follows. Openssl packages contain a rehash or c_rehash script that can generate these using a command c_rehash /usr/share/ca-certificates/cacert.org/.

drwxr-xr-x 2 root root 4096 2009-05-14 23:22 .
drwxr-xr-x 9 root root 4096 2007-05-16 23:12 ..
lrwxrwxrwx 1 root root    8 2009-05-14 23:22 5ed36f99.0 -> root.crt
-rw-r--r-- 1 root root 2151 2007-03-04 05:23 class3.crt
lrwxrwxrwx 1 root root   10 2009-05-14 23:22 e5662767.0 -> class3.crt
-rw-r--r-- 1 root root 2569 2007-03-04 05:23 root.crt

Note I have made SSLVerifyClient optional. This is because the error message when SSLVerifyClient required and a person without a certificate installed access the site is rather unintuitive(firefox request to improve). The simple Rewrite directives at the bottom mean that a forbidden page with that error as per ErrorDocument. You will need mod_rewrite installed and enabled to use this.

Specific Certificates allowed - by List

In addition to those directives above:

SSLOptions           +FakeBasicAuth
SSLRequireSSL
AuthName             "Admin Only Area"
AuthType             Basic
AuthUserFile         /var/www/.htpasswd
require              valid-user

The /var/www/.htaccess is like:

/CN=Daniel Black/emailAddress=daniel@cacert.org:xxj31ZMTZzkVA

The password bit xxj31ZMTZzkVA is always the same. The first bit is obtained by openssl x509 -noout -subject -in certificate.crt where certificate.crt is the certificate that you want to give access to.

Specific Certificates allowed - Expression

Sometime you want to say - yes accept any certificate from CAcert that has an email of @example.com and not worry about maintaining long lists. This is possible as follows:

SSLRequire %{SSL_CLIENT_S_DN_Email} =~ m/^[^@]*@example\.com$/

A full list is here http://httpd.apache.org/docs/trunk/mod/mod_ssl.html#sslrequire. Sometime certificates can contain more that one email so:

SSLRequire %{SSL_CLIENT_S_DN_Email} =~ m/^[^@]*@example\.com$/
          or %{SSL_CLIENT_S_DN_Email_0} =~ m/^[^@]*@example\.com$/
          or %{SSL_CLIENT_S_DN_Email_1} =~ m/^[^@]*@example\.com$/
          or %{SSL_CLIENT_S_DN_Email_2} =~ m/^[^@]*@example\.com$/
          or %{SSL_CLIENT_S_DN_Email_3} =~ m/^[^@]*@example\.com$/

You should change your error message (above) to say that certificates for @example.com are required also.

Logging

The standard apache combined log file has a field for username, however using client certificates doesn't utilise this. Keeping the log in the same format however is handly if you every want to analysis it without customing analysis software.

To do this use the CustomLog using the combined log format replacing %u with %{SSL_CLIENT_S_DN_Email}x for an email address (or any other SSL_CLIENT* variable you may find useful.)

Web Application Authentication

A number of web application can use the REMOTE_USER environment variable to provide access control to areas of the web application. These web application normally will describe the usage of this feature with the Apache Basic or Apache Digest authentication. You can use SSL certificates here.

How you do this is using the SSL option SSLUserName followed with a username environment variable. SSL_CLIENT_S_DN_Email is a useful though it depend on the web application and the users if having an email as a username is acceptable.

CustomLog /var/log/apache2/ssl_access.log  "%h %l %{SSL_CLIENT_S_DN_Email}x %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-agent}i\""

Revoked Certificate Checking

OCSP can be used to check if certificates have been revoked. Only versions of Apache after 2.3 are able to check this for you OCSPEnable.

If you use Apache 2.2 or lower you will have to use CRLs to do the revocation checking because it does not support OCSP. To do that you have to set up a cron job that downloads the current CRLs and tell Apache to use them:

  1. Create a directory where the CRLs get stored into. A good place is usually /var/local/ssl/crls

  2. Set up the cron job that does the downloading. I have prepared a shell script that you can just put into /etc/cron.hourly (or daily or whatever). The script requires rsync, the c_rehash utility from openssl and relies on service apache2 reload to reload the Apache configuration. Adjust it to your needs if you have a setup that doesn't fulfil these dependencies.

  3. Put the following into your Apache config:
    SSLCARevocationPath /var/local/ssl/crls/
  4. Manually run the cron job script for the first time which will also reload the Apache configuration
  5. Check if everything works correctly


Inputs & Thoughts

20100524-UlrichSchroeter

Win32 (w/ Cygwin) implementation details:

1.)

The Openssl rehash nor the c_rehash nor the c_rehash perl script doesn't work for me. Also Sysinternals Junction or other solutions doesn't do what expected. After 2 days of experiences I've stopped playing around to find a workaround and decided to do the 2 essential parts of the scripts manualy from the DOS prompt:

$>openssl x509 -noout -hash <root.crt
  noted the hash result and added to the next Cygwin ln program command line
$>ln --symbolic --no-target-directory root.crt 5ed36f99.0
                                               ^^^^^^^^
$>openssl x509 -noout -hash <class3.crt
  noted the hash result and added to the next Cygwin ln program command line
$>ln --symbolic --no-target-directory class3.crt e5662767.0
                                                 ^^^^^^^^
note: Cygwin's 'ln' command has another usage syntax.

2.)

The SSLRequire doesn't work for me under Apache/2.2.11 (Win32) mod_ssl/2.2.11 OpenSSL/0.9.8e ...
The apache conf syntax
SSLRequire %{SSL_CLIENT_S_DN_Email} =~ m/^[^@]*@example\.com$/
          or %{SSL_CLIENT_S_DN_Email_0} =~ m/^[^@]*@example\.com$/
          or %{SSL_CLIENT_S_DN_Email_1} =~ m/^[^@]*@example\.com$/
          or %{SSL_CLIENT_S_DN_Email_2} =~ m/^[^@]*@example\.com$/
          or %{SSL_CLIENT_S_DN_Email_3} =~ m/^[^@]*@example\.com$/
needs to be changed to:
SSLRequire ( (%{SSL_CLIENT_S_DN_Email} in {"alias@example.com"}) or (%{SSL_CLIENT_S_DN_Email} in {"alias2@example.com"}) )
all in one row, parts included in brackets (), otherwise i've ever got service start exceptions:
   "The Apache service terminated with service-specific error 1"
or connection failures:
   "failed, reason: SSL requirement expression not fulfilled"

3.)

in http.conf I've added the basic definitions for an Alias:
Alias /users/ "D:/Apache/Apache2/wwwroot2/users"
<Directory "D:/Apache/Apache2/wwwroot2/users">
  SSLOptions +FakeBasicAuth +StdEnvVars +ExportCertData
    Options Indexes MultiViews
    AllowOverride None
    Order allow,deny
    Allow from all
</Directory>

and with the ssl.conf inclusion I've added the SSLrequire statement under a location section:
<Location /user1/>
 SSLRequire (( ( %{SSL_CLIENT_S_DN_Email} in {"alias@example.com"} ) or ( %{SSL_CLIENT_S_DN_Email} in {"alias2@example.com"}) ) and ( %{SSL_CLIENT_V_REMAIN} > 0 ) and (( %{SSL_CLIENT_I_DN_CN} in {"CA Cert Signing Authority"}) or ( %{SSL_CLIENT_I_DN_CN} in {"CAcert Class 3 Root"}) ))
</Location>

The SSLRequire statement have to be all in one line (!) (see top 2)
The SSL_CLIENT_V_REMAIN checks the client cert if its not expired. The SSL_CLIENT_I_DN_CN checks the CAcert's root and class3 issuer statements in the client cert.


regards, uli   ;-)


20110521-u60

script to run once a week or once every 2 weeks (with cygwin installed)

wget -nc http://crl.cacert.org/revoke.crl
wget -nc http://crl.cacert.org/class3-revoke.crl

rem  for all 0 files  delete old hash 0 files

for %%a in (*.r0.lnk) do attrib -R %%a
for %%a in (*.r0.lnk) do del %%a

openssl crl -in revoke.crl -inform DER -out revoke-cacert_root_now.crl -outform PEM
openssl crl -noout -hash<revoke-cacert_root_now.crl>temphash.tmp
set /p hash= <temphash.tmp
ln --symbolic --no-target-directory revoke-cacert_root_now.crl %hash%.r0

openssl crl -in class3-revoke.crl -inform DER -out revoke-cacert_class3_now.crl -outform PEM
openssl crl -noout -hash<revoke-cacert_class3_now.crl>temphash.tmp
set /p hash= <temphash.tmp
ln --symbolic --no-target-directory revoke-cacert_class3_now.crl %hash%.r0

## don't forget to restart the apache daemon / service


20110527-u60

To check at application level with php on top of apache, you have to do some php coding.
An example is given in following article:


20110602-u60

Configuration for a mixed Client Cert required / No Client Cert required environment

1. configure your https apache server to not require client certs by default, except special areas
   /secure/     Client Cert required area
   /unsecure/   Area where no client cert is required, also to place a landing page
   /other/      if other directories should be secured with Client Cert required,
                add a httpd.conf <directory ..> directive like for /secure/,
                add a .htaccess like below /secure/.htaccess sample

httpd.conf
.........................................
[...]
# default port 80 configuration
                                                 /secure/.htaccess
<Directory .../WWWROOT/secure/>                  ......................................
# #Redirect permanent                            RewriteEngine on
# from http://../secure/.*                       RewriteCond %{SERVER_PORT} !^443$
# to        //../unsecure/index.php              RewriteRule ^(.*)$ /unsecure/index.php [R=301,L]
    AllowOverride All                            ......................................
</Directory>

[...]
<IfModule mod_ssl.c>
    Include conf/ssl.conf
</IfModule>
[...]
.........................................


ssl.conf
.........................................
<VirtualHost _default_:443>
SSLEngine on
SSLVerifyDepth 3

#Server keys
SSLCertificateFile     /Apache/Apache2/conf/ssl.crt/your-pub-server-key.pem
SSLCertificateKeyFile  /Apache/Apache2/conf/ssl.key/your-private-server-key.pem
#Server keyfile verification against CA
SSLCertificateChainFile /Apache/Apache2/conf/ssl.crt/cacert-chain.pem

# Client Cert verification pre-definition, that is required in later <directory ..> directives
SSLCACertificateFile /Apache/Apache2/conf/cacert.org/root.crt
SSLCACertificatePath I:/Apache/Apache2/conf/ssl.vrf/
SSLCADNRequestPath /Apache/Apache2/conf/ssl.vrf/

#   Certificate Revocation Lists (CRL):
SSLCARevocationFile /Apache/Apache2/conf/ssl.crl/revoke-cacert_root.crl
SSLCARevocationFile /Apache/Apache2/conf/ssl.crl/revoke-cacert_class3.crl

#  EOT default definitions


#  NO CLIENT CERT REQUIRED
<Directory /unsecure/>
###  NO Client Verification
SSLVerifyClient none
</Directory>

# CLIENT CERT REQUIRED
<Directory /Apache/Apache2/WWWROOT/secure/>
##  SSLRequireSSL
### Client Verification
SSLVerifyClient require
SSLVerifyDepth 3
#SSLCADNRequestPath /Apache/Apache2/conf/ssl.vrf/   see pre-configuration

SSLRequire ( ( %{SSL_CLIENT_V_REMAIN} > 0 ) and (( %{SSL_CLIENT_I_DN_CN} in {"CA Cert Signing Authority"}) or ( %{SSL_CLIENT_I_DN_CN} in {"CAcert Class 3 Root"}) ))

### error handling
RewriteEngine        on
RewriteCond     %{SSL:SSL_CLIENT_VERIFY} !=SUCCESS
RewriteRule     .? - [F]
ErrorDocument 403 "You need a valid client certificate from CAcert.Org to
access this page on this site."
</Directory>

</VirtualHost>
.........................................


php scripts

/unsecure/index.php
.........................................
[...]
<script language="javascript" type="text/javascript">
<!--
function $(id) { return document.getElementById(id); }
// -->
</script>
[...]
Description/Warning: You need a valid client cert installed in your browser to continue with client cert login
[...]
<form action="https://../secure/login.php" method="post" id="login">
<input type="submit" value="Cert Login">
<button onclick="$('login').action='/unsecure/cancel-infopage.html';$('login').submit();">Cancel</button>
</form>
[...]
.........................................


/secure/login.php
.........................................
<?php

$user = $_SERVER["SSL_CLIENT_S_DN_CN"];
$email = $_SERVER["SSL_CLIENT_S_DN_Email"];
$server = $_SERVER["SSL_SERVER_S_DN_CN"];
$clisscert = $_SERVER["SSL_CLIENT_I_DN_CN"];

if ($_SERVER["SSL_CLIENT_VERIFY"]!="SUCCESS") {
  header ("Location: /unsecure/index.php");
  exit;
} else {
?>
[html code goes here]
[...]
<h2>Successful Client Cert Login</h2>

<p>Hello <?php echo $user; ?>,<br>
You've successfuly logged in to <?php echo $server; ?><br>
with your 
<?php
if ($clisscert=="CAcert Class 3 Root") {
 echo $clisscert." (Class3 subroot)";
} else {
 echo $clisscert." (Class1 root)";
} 
?> issued client cert.<br>
<br>
This page you can only read if successfuly authenticated with
a valid client cert.<br>
So now you have access to client cert enabled, restricted areas.<br>
Feel free to continue your journey on this website ...</p>

</body>
</html>
<?php
}
?>
.........................................


YYYYMMDD-YourName

Text / Your Statements, thoughts and e-mail snippets, Please


ApacheServerClientCertificateAuthentication (last edited 2013-10-28 17:23:55 by MichaelTänzer)