Eric Norman from the University of Wisconsin graciously
contributed the following writeup about an elegant way to enable PKI
authentication for web applications (which he implemented for Geeklog).
Mark Franklin at Dartmouth asked to to write up what I did to
enable use of client certificates for an application. Here it
comes.
The application is a collaborative tool used by a hundred or so
folks hereabouts and thereabouts and called Geeklog (www.geeklog.net).
It's written in php and we run it on a Macintosh with OSX (panther,
the latest). Geeklog has its own set of user identifiers
(screennames)
and they aren't the same as the "normal" identifiers used here at the
university (and they can't be for the thereabouts folks). I also
needed to do this such that possession of a client certificate is
sufficient, but not necessary; if someone doesn't have one,
username/password must still be possible.
I'll start with the shock and awe: I did the authentication part by
making *zero* modifications to the geeklog code.
First the server. OSX comes with an Apache web server and
mod_ssl.
Apple also provides a web-based interface for administration.
This
interface does not yet have support for enabling client certificate
requests during the SSL handshake. Here's what the SSL
configuration
looks like:
<IfModule mod_ssl.c>
SSLEngine On
SSLLog "/private/var/log/httpd/ssl_engine_log"
SSLCertificateChainFile "/LAN/Users/ejnorman/vidi_chain.pem"
SSLCertificateFile "/LAN/Users/ejnorman/vidi_cert.pem"
SSLCertificateKeyFile "/LAN/Users/ejnorman/vidi_key.pem"
SSLCipherSuite "ALL:!ADH:RC4+RSA:+HIGH:+MEDIUM:+LOW:!SSLv2:+EXP:+eNULL"
SSLCACertificateFile "/LAN/Users/ejnorman/vidi_trust.pem"
SSLVerifyDepth 5
SSLOptions +StdEnvVars +ExportCertData
SSLVerifyClient optional
</IfModule>
The last four SSL... lines are what enables client certificate
requests.
(Yes, I know stuff shouldn't be in my home directory; this will be
fixed).
Anyway, these 4 lines were inserted manually. The good news is
that use
of Apple's administration interface doesn't mess with them (and better
news is that it even knows about them since it preserved them but
rearranged their order).
All the above is just to configure mod_ssl to request a client
certificate;
it has nothing to do with the geeklog code.
Now for the "magic". I wrote a separate php file that you execute
by
using a separate URL; i.e. https://archlog.doit.wisc.edu/cert
gets
you to my php code. This code:
(1) extracts the email address form the client certificate (mod_ssl
puts it in an environment variable),
(2) uses that to query the geeklog user database and extract the
geeklog username (geeklog uses mysql and the user table
contains
both username and email address along with other stuff),
(3) creates a session for that user (geeklog has a function to do that;
sessions are maintained in a session table of the mysql
database),
(4) creates a cookie containing the session identifier (that's how
geeklog maintains session information), and
(5) redirects to the regular geeklog HTML producing php code.
When the regular geeklog code runs, it notices the cookie, notices that
it has a session with that identifier, and concludes "user already
logged
in"!
If anything fails during the above, e.g. no SSL, no client certificate
supplied, no row in database matching email address, etc, then
neither a session nor a cookie is created before going to the regular
geeklog code.
Observations:
+ This "trick" will work with lots of different web-based applications;
all you have to do is figure out how a session is maintained.
+ You might also have to figure out a different URL for the
redirection that bypasses the username/password prompt; I didn't
in
the geeklog case. This will work regardless of whether
session
identifiers are passed around with cookies, in URLs, or via
hidden
variables.
+ The separate URL could be eliminated by one "well placed if
statement".
+ This scheme does not mean that all users with certificates that are
trusted by the server are allowed to use geeklog; they also have
to
register with geeklog to get into the user table.
+ Registration of geeklog users via X.509 certificates is not
addressed.
+ Reconfiguring mod_ssl to also trust certificates from thereabouts
is all it will take to include those folks too (along with their
registration, which they have to do anyway).
Now I did add about two lines of geeklog code to propagate the "https"
in URLs that appear in links and form actions as you continue with the
geeklog code. However, since there aren't any confidentiality
concerns
in this case, this wouldn't be necessary. I'll leave it as an
exercise
for the reader to figure out why not.
One of the things this leads me to wonder about is if it would be
better
to focus on the concept of session instead of things like
authentication,
etc. Some questions that might be pertinent are:
+ What information is contained in a session?
+ What needs to happen pre-session?
+ What needs to happen during a session?
+ What can happen during a session (browser fails? back button pushed?)
+ What needs to happen post-session?
+ What parts of the "session concept" are common to just about
everyone?