Client PKI (x509) Certificates with Mac OS X Server
Integrating Mac OS X Server into a PKI sounds
scary and hard, but it really isn't. This article will teach you how to set up
Mac OS X Server to authenticate PKI (or x509) client certificates for static web
pages, WebObjects apps, Perl CGIs and PHP.
"This new web site needs to authenticate
with PKI certificates. Can your Mac server do that? If not..." Not the kind
of thing server admin who is trying to hold on to his Mac server wants to hear.
Yet, that is exactly what I heard a few years ago. After some hard work, I was
able to answer yes. Now I know how to do it, and I'm going to share that
knowledge with you.Some ground
rules, first. My experience of certificates is within a Public Key
Infrastructure. That is, there is a root certificate that my clients have
gotten their certificates from, and I got my server certificate from the same
chain. It wasn't necessarily the same source, but the two chains meet up at
some point. I haven't worked with client certificates in the real world, where
the certificates come problem multiple roots. I have read the mod_ssl web site
about this situation, and will discuss it when we get to that point, but I have
no direct experience with it
myself.I'm also not going to get
into the nuts and bolts of how clients and servers go through the dance of
exchanging certificates. We are just going to go through the steps of setting
up your Mac OS X 10.4 Server to allow your clients to authenticate with PKI
(x509) certificates. This can be done on previous versions Mac OS X Server, but
it requires a little more work an a trip to the command line. If you are in
that environment, drop me an email, and I'll be happy to give you some
pointers.To start with, you will
need to set up your web site to work with SSL. Let's begin by creating the
certificate request. Open up Server Admin and connect to your server. Select
the server, then click the Settings button at the bottom, and finally click on
the Certificate
tab:
Click the Plus sign. You will see a form to
fill
out:
The Common Name should be set to the DNS
name of your site. Check with your certificate authority about how they want
the rest of the fields filled out. My company was particular about the
organization, less so about the Organization Unit, and the locality was the
actual city and state we're in. In the post they have been more particular
about the Organization Unit, so make sure you find out what yours wants. Type a
pretty robust passphrase so your certificate won't be comprised. And remember
the passphrase. You'll need it
later.When you get everything
filled out, click the Save button. If you don't, you won't be able to click the
"Request SIgned Certificate From CA..." button, and that is the point of this
exercise, after all. After you Click Save, click "Request SIgned Certificate
From CA..." and you will see a
sheet:
Follow the instructions. If you need to put
the CSR into some other mechanism, you can drag the certificate icon to the
desktop, and you will get a text clipping including the CSR. However you send
the CSR in, you will eventually get back a signed certificate, and hopefully a
CA Chain file, which will give you the certificates of the signer, and its
signers all the way back to a root. When you do, you will need to return to the
Server Admin, and the certificates Setting, and edit the self signed certificate
you just created. Click the "Add Signed Certificate" button. You will
see:
Follow the instructions. My company's CA
returned me the signed certificate in a plcs7 format. I had to take a trip to
the command line, where openssl pkcs7 -in
file.pem -print_certs printed out what I
wanted. Actually, it printed the whole chain, so by executing
openssl pkcs7 -in file.pem -print_certs -out
certs.pem, I was able to create a chain file
as well. This isn't needed for basic SSL operation, but it is necessary for
authenticating client
certificates.Now we're ready to
set up the web site. Start by clicking Web in the list of services for the
server in Server Admin, and click the Sites
tab:
You probably have a site at port 80 that you
would just as soon keep running there. Click the + sign at the bottom to add a
site, and click the pencil to edit it. You should set up the general settings
for your new site. Then click the Security
tab:
Check the "Enable Secure Sockets
Layer (SSL)" check box. If you didn't enter 443 as the port under the General
settings, Server Admin will inform you that it is switching the port to 443 for
you. Now select your site certificate from the Certificate popup.

When you click Save, we have done as much
as we can in Server Admin. We now have to start editing conf files. I am
going to give you the basic how-to, but if you want to understand SSL, I would
recommend that you start in the same place I did, the mod_ssl website
.I think that editing conf files
is best done in the Terminal. To start, we will need to navigate to the place
where the site configuration files are.
cd to
/etc/httpd/sites/.
You will see there a conf file for each site
you have on your server (a site is determined by a unique combination of IP
number, port number and dns name. If you
cat (that is,
print out) the conf file for the site you just created, you will see something
like:## Default Virtual Host
Configuration<VirtualHost
*:443> ServerName
server.quandir.com ServerAdmin
admin@quandir.com DocumentRoot
"/Library/WebServer/Documents"
DirectoryIndex index.html index.php
CustomLog "/var/log/httpd/access_log" "%h %l %u %t \"%r\" %>s
%b" ErrorLog
"/var/log/httpd/error_log"
ErrorDocument 404 /error.html
<IfModule mod_ssl.c>
SSLEngine On SSLLog
"/var/log/httpd/ssl_engine_log"
SSLCertificateFile
"/etc/certificates/server.quandir.com.crt"
SSLCertificateKeyFile
"/etc/certificates/server.quandir.com.key"
SSLCipherSuite
"ALL:!ADH:RC4+RSA:+HIGH:+MEDIUM:+LOW:!SSLv2:+EXP:+eNULL"
</IfModule> <IfModule
mod_dav.c> DAVLockDB
"/var/run/davlocks/.davlock100"
DAVMinTimeout 600
</IfModule> <Directory
"/Library/WebServer/Documents">
Options All -Indexes -ExecCGI -Includes
+MultiViews AllowOverride
None <IfModule
mod_dav.c> DAV
Off
</IfModule>
</Directory> <IfModule
mod_rewrite.c>
RewriteEngine On RewriteCond
%{REQUEST_METHOD} ^TRACE
RewriteRule .* - [F]
</IfModule> <IfModule
mod_alias.c>
</IfModule> LogLevel
warn</VirtualHost>
This is a good start, and would allow
us to serve web pages over SSL. But there are some modifications we need to
make if we want to authenticate client certificates. The modifications are
mostly in the <IfModule mod_ssl.c>
block.
If we are inside a Public Key
Infrastructure, where the server certificate and the client certificates all
come from the same root, we need to get our certificate chain in there. It is
probably a good idea to copy the certificate chain file into the
/etc/certificates/
directory, since that is where the other
certificates are. Then we can add a
SSLCertificateChainFile
directive that points to that file. Then we
need to add a
SSLCACertificateFile
directive that points to the same file (I know it looks like the same directive,
but it's not. See the CA after the SSL?).
Of course, this doesn't make a
lot of sense if our root is not the source of the client certificates,
especially if the clients are from multiple roots. In this case, we still want
to give our own chain with the
SSLCertificateChainFile
directive. But we have a choice for the CAs
of the clients. We can either put their root certificates into a directory and
point to it with the SSLCACertificatePath
directive. Or you can assemble the root
certificates into an all-in-one file and use the
SSLCACertificateFile
directive. Again, I haven't done this
myself. I commend you to the mod_ssl web site for more
details.You may not need to, but
I found I had to modify the
SSLCipherSuite
directive. Unless it simply says
"ALL", the
browsers I have used complain that the server is allowing less than 128 bit
encryption.The directive that
actually makes it possible to authenticate with certificates is
SSLVerifyClient
require. Without this directive, the browser
wouldn't send the client certificate to the server. Actually, this is not quite
true. There is another value for this directive that was designed to get the
browser to send a certificate if it had one, and that is "optional." In
practice, however, that value is almost never used because one browser
misinterprets it. OK, let me call a spade a spade. The "optional" value was
designed to ask for a certificate, and if one wasn't available to let the server
decide what to do. The Microsoft Internet Explorer team, in their infinite
arrogance, decided that the optional referred to the browser, and that if it was
optional, they just wouldn't return it, even if the browser had one. As you can
tell, this has caused me a fair amount of trouble, and some quite inelegant
workarounds. Next, we need to
tell the server how deep to look in the signer chain for a match. I check
deeper than I need to, but it doesn't seem to matter. The
SSLVerifyDepth 10
directive takes care of
that.That's all the
<IfModule mod_ssl.c>
block changes needed if you just want to
authenticate for static pages or WebObjects. If you need Perl or PHP, you will
need one more directive, SSLOptions
+StdEnvVars. This tells mod_ssl to hand the
environment variables (including the certificate information) to cgi or PHP.
The mod_ssl website says that this will slow down
operation, but if you need it, you need
it.So, the complete
<IfModule mod_ssl.c>
block would
be: <IfModule
mod_ssl.c> SSLEngine
On SSLLog
"/var/log/httpd/ssl_engine_log" SSLCertificateChainFile
"/etc/certificates/quandir_cert_ca.crt"
SSLCertificateFile
"/etc/certificates/server.quandir.com.crt"
SSLCertificateKeyFile
"/etc/certificates/server.quandir.com.key" SSLCACertificateFile
"/etc/certificates/quandir_cert_ca.crt"
SSLCipherSuite "ALL"
SSLVerifyClient require
SSLVerifyDepth 10 SSLOptions
+StdEnvVars
</IfModule>Just a little
more and we'll be done with the conf file. What we are about to add is actually
all we need to do to authenticate with client certificates for static web pages.
The Directory
directive sets the realm for access. You can
use the
SSLRequire
directive inside the
Directory
directive in order to specify the conditions
for access. Once again, I commend the mod_ssl web site to you for details on the
kinds of expressions you can you, but as an example, you can check for the
presence of one of a set of email addresses in the certificate
subject: <Directory
"/Volumes/Storage/SecureWeb/Documents/myOrg/allhands">
SSLRequire %{SSL_CLIENT_S_DN_Email} in {"Charley.McCarthy@dummy.com","Mortimer.Snerd@dummy.com","Knucklehead.Smith@dummy.com"}
</Directory>When you
restart the webserver, you are serving static web pages and authenticating with
client certificates.Now for the
dynamic stuff. The WebObjects adapter that comes with Mac OS X 10.4 Server is
set to relay the SSL headers to your WebObjects application. Again, it is
possible to do this for previous versions of Server, you will just need to
uncomment a line in the adaptor sample code, recompile it and put it in the
right place. Drop me an email if you need help. You need to have this in your
Session code, somewhere that will be checked before sending back a
page: String
str =
this.context().request().headerForKey("ssl_client_cert_cn");Then
you need to process
str to parse
out the part of the certificate subject that you need, e.g., the email address.
And of course, you will want to test it, probably checking it against a database
of users.The process is pretty
much the same for Perl. As I mentioned above, for Perl and PHP, we need to set
the directive in the conf file to hand over the environment variables. Once we
do that, our Perl CGI can look something like
this#!/usr/bin/perl
-wuse DBI; # You'll want to use
something to talk to a database. I used DBI, you can do whatever you
like.use strict;
my $email =
$ENV{"SSL_CLIENT_S_DN_Email"}; #You don't have to test against email address.
See the mod_ssl
website for other choices.$email
=~ tr/A-Z/a-z/; #this lowercases the email address. Its utility depends on the
way you store the email addresses to compare
against.#That's about it. Pass this
off in a database query or test it in any way you
like.#If the test is successful, proceed
with what your CGI is supposed to do. If not, you can tell the user why not and
end.The technique for PHP is very
similar, again checking the environment variables. This code in a php
file:<?=$_SERVER["SSL_CLIENT_S_DN_Email"]?>should
print out the email of the certificate owner. Caveat: I don't really know PHP.
This snippet courtesy of my friend, Chris Newton, who
does
know php.There, that wasn't hard,
was it?
Posted: Mon - March 20, 2006 at 05:54 PM
|
Quick Links
Calendar
| | Sun | Mon | Tue | Wed | Thu | Fri | Sat
|
Categories
Archives
XML/RSS Feed
Search
Statistics
Total entries in this blog:
Total entries in this category:
Published On: Jun 10, 2006 05:40 PM
|