Ssl – Enforcing client verification in Apache just for a specific client certificate

apache-2.4sslssl-certificateverification

I want to make my Apache web server accept SSL connections ONLY IF the client presents itself with a specific SSL client certificate.
In other words, only ONE client is allowed and it MUST use a specific client certificate (that I also have).

This client certificate (let's call it "MyClientCertificate") is a normal PEM certificate emitted by a CA (let's call it "MyCA") for which I also have in turn its certificate.

This is how I configured my Apache virtual host for this purpose (I'm reporting only the options related to the SSL client verification):

SSLVerifyClient require
SSLCACertificatePath /etc/ssl/certs
SSLCACertificateFile /etc/ssl/client/MyClientCertificate.pem
SSLCADNRequestFile /etc/ssl/client/MyClientCertificate.pem
SSLVerifyDepth 1

Explanations of the options (or of my intentions at least…):

  • with "SSLVerifyClient require" I enforce Apache to always perform client verification and reject the connection if it fails
  • /etc/ssl/certs contains the PEM certificates of MyCA (properly installed in the system with the required hashing+symlinking process), as long as those of all the other well-known CAs Apache may recognize
  • with SSLCACertificateFile I'm adding the MyClientCertificate certificate to the list of CA certificates in /etc/ssl/certs, as a trusted certificate by itself, and with SSLCADNRequestFile I'm saying that my server should request just that certificate (and only that) to the client, not the whole list of CA certificates in SSLCACertificatePath+SSLCACertificateFile
  • with "SSLVerifyDepth 1" I'm saying that the allowed client certificate is signed by a CA which is among those recognized by the server (in SSLCACertificatePath)

This seemed to work: Apache requires the client certificate, refuses the connection if the specified client certificate is not provided and accept it if it is.
However, recently the client (which is a supplier for me) has decided to change its client certificate, with a new one (let's call it MyNewClientCertificate.pem) with 2048 bit encryption instead of the 1024 bit depth of the old one. The problem is this:

  • the supplier says it has changed its client certificate and that it is now using the new certificate to authenticate
  • the supplier says for sure it's presenting itself with JUST the new certificate and not with both the new and the old one
  • I have not yet changed my Apache configuration (hence, my Apache is still configured to request the old certificate)
  • BUT my Apache is accepting the supplier client connections without any problem!! I would have expected it to start to fail until I change its configuration to replace MyClientCertificate.pem references with MyNewClientCertificate.pem

The new certificate is emitted by the same CA of the old one. The supplier suggests that my Apache configuration is probably accepting all connections from clients that present a certificate emitted by that CA, instead of performing a match on the client certificate itself.

If this is the case, what am I doing wrong in my configuration? Should I lower the SSLVerifyDepth to 0? (but isn't this saying that the certificate must be self-signed?) Or do I have to change something in the SSLCACertificateFile/SSLCADNRequestFile directives?

Since this system is in production, I don't have the ability to perform all the tests I can think of, but I should try to make focused changes to get to the correct result as quickly as possible. This is why I would appreciate any help on this topic.

Best Answer

You can match a specific client certificate by using the SSLRequire directive to match against the complete Subject DN or just the CN part from the client's certificate:

SSLRequire %{SSL_CLIENT_S_DN} eq "C=AU, ST=Some-State, L=Springfield, O=ServerFault.com, OU=Moderators, CN=HBruijn/emailAddress=hbruijn@serverfault.com"   

SSLRequire %{SSL_CLIENT_S_DN_CN} eq "HBruijn/emailAddress=hbruijn@serverfault.com"

Use openssl x509 -in client.crt -text to display the Subject string.

A more complete config would be:

SSLVerifyClient      none
SSLCACertificateFile conf/ssl.crt/ca.crt
SSLCACertificatePath conf/ssl.crt

<Directory /usr/local/apache2/htdocs/secure/area>
  SSLVerifyClient      require
  SSLVerifyDepth       5
  SSLOptions           +FakeBasicAuth
  SSLRequireSSL
  SSLRequire           %{SSL_CLIENT_S_DN_CN} eq "HBruijn/emailAddress=hbruijn@serverfault.com"
</Directory>