multi stage authentication using Apache HTTPd 2.4
Here is a short tutorial on how to setup a multi stage authentication with an Apache HTTPd 2.4 server.
The first stage will do a 2-way SSL encryption, so both server and client will need to present a certificate.
The second stage will be password authentication, the username has to match the username in the CN of the client certificate.
This is similar to the Belgian eID, instead of needing a PIN to unlock the certificate.
We require a certificate and a password. Although this is not 100% the same, the security it offers is comparable for most purposes.
In my 2 test cases the password is queried from PAM, but a simple htpasswd file will work as well.
I assume you can configure a normal SSL based virtual host on httpd and have this working. I also assume you are able to sign and create the clients certificates yourself using openssl and a private CA. If there is demand, I may write a short post on that at a later date.
Below is a full configuration1
Listen 8443
<VirtualHost *:8443>
ServerName secure.blackdot.be
ServerAlias nara
CustomLog /srv/http/logs/access_log common
ErrorLog /srv/http/logs/error_log
SSLEngine on
SSLCertificateFile /srv/http/ssl/server.crt
SSLCertificateKeyFile /srv/http/ssl/server.key
SSLCACertificateFile /srv/http/ssl/ca.crt
SSLCARevocationFile /srv/http/ssl/ca.crl
SSLProtocol TLSv1.2 TLSv1.1 TLSv1
SSLCipherSuite HIGH:!LOW:!aNULL:!MD5
# php lockdown
php_admin_value open_basedir "/srv/http/htdocs:/srv/http/tmp:/tmp"
php_admin_value upload_tmp_dir "/srv/http/tmp"
php_admin_value session.safe_path "/srv/http/tmp/sessions"
<FilesMatch "\.(cgi|shtml|phtml|php)$">
#SSLOptions +StdEnvVars +ExportCertData
SSLOptions +StdEnvVars
</FilesMatch>
## Fancy SSL Authentication
# /: optional client ceritificate
# /*: require client certificate + 2 step authentication
# /gatekeeper: don't need client certificate
DefineExternalAuth pwauth pipe /srv/http/bin/pwauth
DocumentRoot /srv/http/htdocs
<Directory /srv/http/htdocs>
SSLVerifyClient require
AuthType Basic
AuthName "Secure Area"
AuthBasicProvider external
AuthExternal pwauth
# work around a bug in the new authentication code, should be fixed in 2.4.3
#<RequireAll>
# Require valid-user
# <RequireAny>
# Require user workaround_for_PR_52892
# Require expr ( \
# (%{SSL_CLIENT_S_DN_O} == "Blackdot") && \
# ((%{SSL_CLIENT_S_DN_OU} == "Admins") || (%{SSL_CLIENT_S_DN_OU} == "Users")) && \
# (%{SSL_CLIENT_S_DN_CN} == %{REMOTE_USER}) \
# )
# </RequireAny>
#</RequireAll>
<RequireAll>
Require valid-user
Require expr ( \
(%{SSL_CLIENT_S_DN_O} == "Blackdot") && \
((%{SSL_CLIENT_S_DN_OU} == "Admins") || (%{SSL_CLIENT_S_DN_OU} == "Users")) && \
(%{SSL_CLIENT_S_DN_CN} == %{REMOTE_USER}) \
)
</RequireAll>
</Directory>
# fix upload of large files (bug in renegotiate)
<Directory /srv/http/htdocs>
SSLRenegBufferSize 134217728
</Directory>
</VirtualHost>
Server Certificate
SSLCertificateFile /srv/http/ssl/server.crt
SSLCertificateKeyFile /srv/http/ssl/server.key
SSLCACertificateFile /srv/http/ssl/ca.crt
SSLCARevocationFile /srv/http/ssl/ca.crl
It is very important to include your CA, it is required for client certificate validation.
I also recommend setting the Certificate Revocation List, that way you can revoke access to compromised client certificates.
Enabling Client Certificate Authentication
<Directory /srv/http/htdocs>
...
SSLVerifyClient require
...
</Directory>
You can also set this to optional, this can be useful if you have a portal to retrieve the client certificate. But it’s safer to have it set to require for the entire virtual host.
Enabling HTTP Authentication
<Directory /srv/http/htdocs>
...
AuthType Basic
AuthName "Secure Area"
AuthBasicProvider external
AuthExternal pwauth
...
</Directory>
This is the second stage of the authentication, I’m using mod_auth_external in combination with pwauth to authenticate against PAM.
This makes it easy for users with shell access to change their own passwords, however you probably want to use file base authentication instead. Every authentication provided should work.
Linking Client Certificate to the HTTP User
<Directory /srv/http/htdocs>
...
<RequireAll>
Require valid-user
Require expr ( \
(%{SSL_CLIENT_S_DN_O} == "Blackdot") && \
((%{SSL_CLIENT_S_DN_OU} == "Admins") || (%{SSL_CLIENT_S_DN_OU} == "Users")) && \
(%{SSL_CLIENT_S_DN_CN} == %{REMOTE_USER}) \
)
</RequireAll>
...
</Directory>
Here is where the magic happens! This is why this only works on 2.4 branch: we can now nest requirements!
The <RequireAll>
block will require all the require
statements inside to validate to true, if not the request is rejected.
The valid-user
should be self explaining. The real interesting stuff is the expr
line. Here we will check if the following conditions are met for the client certificate:
- Organization matched ‘blackdot’
- OU matched ‘Admins’ or ‘Users’
- Common Name matched the username provided via HTTP Authentication
Of course the validity of the certificate is already checked by SSLVerifyClient
.
This is probably where the most editing will be needed to get this to fit your needs.
The main example also has a alternative, with a workaround for pre 2.4.3 releases.
Fix file uploads
<Directory /srv/http/htdocs>
SSLRenegBufferSize 134217728
</Directory>
There seems to be some issues with file uploads, setting the SSLRenegBufferSize
large enough seems to solve this.
That’s it, enjoy!
-
I will add an explanation to the important parts below. ↩︎