
Authenticating HTTPS clients using TLS certificates has long been very
easy to do. The hardest part, in fact, is to create a PKI infrastructure
robust enough so that compromised (or otherwise no longer desirable)
certificates cannot be used anymore. While setting that up can be fairly
involved, it's all pretty standard practice these days.
But authentication using a private CA is laughably easy. With apache,
you just do something like:
SSLCACertificateFile /path/to/ca-certificate.pem
SSLVerifyClient require
...and you're done. With the above two lines, apache will send a
CertificateRequest message to the client, prompting the client to search
its local certificate store for certificates signed by the CA(s) in the
ca-certificate.pem file, and using the certificate thus found to
authenticate against the server.
This works perfectly fine for setting up authentication for a handful of
users. It will even work fine for a few thousands of users. But what if
you're trying to set up website certificate authentication for
millions
of users? Well, in that case, storing
everything in a single CA just doesn't work, and you'll need
intermediate CAs to make things not fall flat on their face.
Unfortunately, the
standard does not
state what should happen when a client certificate is signed by an
intermediate certificate, and the distinguished name(s) in the
CertificateRequest message are those of the certificate(s) at the top of
the chain rather than the intermediates. Previously, browsers would
not
just send out the client certificate, but also send along the
certificate that it knew to have signed that client certificate, and so
on until it found a self-signed certificate. With that, the server would
see a chain of certificates that it could verify against the root
certificates in its local trust store, and certificate verification
would succeed.
It would appear that browsers are currently changing this behaviour,
however. With the switch to
BoringSSL, Google
Chrome on the GNU/Linux platform no longer sent the signing
certificates, and instead only sends the leaf certificate that it wants
to use for certificate authentication. In the
bug
report,
Ryan Sleevi explains that while the change was premature, the long term
plans are for this to be the behaviour not just on GNU/Linux, but on
Windows and macOS too. Meanwhile, the issue has been resolved for Chrome
54 (due to be released), but there's no saying for how long. As if that
was not enough, the new version of Safari as shipped with macOS 10.12
has also stopped sending intermediate certificates, and expects the web
server to be happy with just receiving the leaf certificates.
So, I guess it's safe to say that when you want to authenticate a client
in a multi-level hierarchical CA environment, you cannot just hand the
webserver a list of root certificates and expect things to work anymore;
you'll have to hand it the intermediary certificates as well. To do so,
you need to modify the
SSLCACertificateFile parameter in apache
configuration so that it not only contains the root certificate, but all
the intermediate certificates as well. If your list of intermediate
certificates is
rather long, it may
improve performance to use
SSLCACertificatePath and the
c_rehash
tool to create a hashed directory with certificates rather than a PEM
file, but in essense, that should work.
While this works, the problem with doing so is that now the DNs of all
the intermediate certificates are sent along with the CertificateRequest
message to the client, which (again, if your list of intermediate
certificates is rather long) may result in performance issues. The fix
for that is fairly easy: add a line like
SSLCADNRequestFile /path/to/root-certificates.pem
where the file
root-certificates.pem contains the root certificates
only. This file will tell the webserver which certificates should be
announced in the CertificateRequest message, but the
SSLCACertificateFile (or
SSLCACertificatePath) configuration item
will still be used for actual
verification of the certificates in
question. Note though that the root certificates apparently also seem to
need to be available in the
SSLCACertificatePath or
SSLCACertificateFile configuration; if you do not do so, then
authentication seems to fail, although I haven't yet found why.
I've set up a
test page for all of
those "millions" of certificates, and it seems to work for me. I've
you're trying to use one of those millions of certificates against your
own webserver or have a similar situation with a different set of certificates,
you might want to make the above changes, too.