Using Let’s Encrypt with Certificate Based Authentication

For one of my sites I wanted to use TLS client authentication. It's easy enough to setup in Apache:

apache2.conf
<VirtualHost *:80>

RewriteEngine On
RewriteRule (.*) https://%{SERVER_NAME}$1 [R=301,L]
</VirtualHost>

<VirtualHost *:443>

SSLEngine on
SSLCertificateFile /etc/letsencrypt/live/example.com/cert.pem
SSLCertificateKeyFile /etc/letsencrypt/live/example.com/privkey.pem
SSLCertificateChainFile /etc/letsencrypt/live/example.com/chain.pem
SSLVerifyClient require
SSLVerifyDepth 1
SSLCACertificateFile /srv/apache/data/root.crt
SSLRequire (%{SSL_CLIENT_S_DN_CN} == "Me") \
|| (%{SSL_CLIENT_S_DN_CN} == "Myself") \
|| (%{SSL_CLIENT_S_DN_CN} == "Irene")
SSLUserName SSL_CLIENT_S_DN_CN
</VirtualHost>

And this worked just fine for 90 days or so. More precisely, it worked until CertBot had to update my Let's Encrypt certificate.

Guess what? Let's Encrypt doesn't have knowledge of my client certificate and thus handshake fails. Error message is not really helpful as "tls: unexpected message" doesn't really point you to the correct path. Fortunately, I actually remembered my certificate shenanigans and thus was able to debug it quite quickly. Issue verification was as easy as dropping certificate requirements made my renewal work again.

However, dropping certificates every month or two would not work for me. I wanted something that would work the same as automatic renewal for other Let's Encrypt certificates. And no, you cannot set .well-known directory to use different validation. With TLS 1.3, you cannot change client requirements once connection is established. You'll just get "Cannot perform Post-Handshake Authentication" error.

But, you know where you can play with locations to your heart's content? In HTTP section. Instead of just redirecting to HTTPS, you want to carve small hole for CertBot verification.

apache2.conf
<VirtualHost *:80>

RewriteEngine On
RewriteRule (.*) https://%{SERVER_NAME}$1 [R=301,L]
<Location "/.well-known/">
RewriteEngine Off
</Location>
</VirtualHost>

Now Let's Encrypt verifies renewal requests using HTTP which is not really a security issue as verification file is completely random and generated anew each time.

Leave a Reply

Your email address will not be published. Required fields are marked *