Strong TLS configuration for Apache

Strong TLS settings for a website on Apache

Goal

SSL Labs A+ Rating - and the good feeling of having an up-to-date TLS configuration.
You can get the rating for your website here: https://www.ssllabs.com/ssltest/

What is needed - In a nutshell:

  • 4096 bit RSA private key and a certificate from a publicly trusted CA
  • Only modern TLS versions: TLS 1.2 and TLS 1.3
  • Only strong cipher suites
  • HTTP strict transport security (HSTS)
  • CAA DNS record

This resource describes the requirements in great detail:
https://github.com/ssllabs/research/wiki/SSL-and-TLS-Deployment-Best-Practices

1. 4096 bit key + certificate

The 4096 bit key was a gotcha for me, as I used 2048 bit initially. Requiring 4096 bit seemed a bit strict to me but I am sure there are some reasons.

After creating the key I needed to create a certificate signing request and obtain a certificate from a public CA. I use Let’s Encrypt and Certbot.
Certbot has really good documentation here: https://certbot.eff.org/
I use it in “certonly” mode as I want to do the Apache configuration myself.

2. Modern TLS versions:

Turn off everything older than TLS version 1.2.
For Apache:

SSLProtocol         -all +TLSv1.2 +TLSv1.3

I am specifying this configuration option in an extra file for the TLS configuration. Then I use the “Include” directive to reference it in my virtual host config for port 443.

Note: For TLS 1.3 you should run OpenSSL 1.1.1 or higher.

3. Strong cipher suites:

If I would only enable TLS 1.3, I would not worry about a specific cipher suite configuration. The allowed cipher suites in TLS 1.3 are all considered strong.
But at the moment it is probably required to support TLS 1.2 as well for compatibility reasons. I might turn it off in the future.

Criteria for strong cipher suites:

  • AES or CHACHA20 as symmetric cipher. Everything else like 3DES or even DES is outdated.
  • AEAD cipher suites which offer authenticated encryption: AES-GCM or CHACHA20
  • Strong hash functions like SHA256 or higher. There is no practical attack against SHA1 in TLS, but all at least somewhat modern clients support SHA256 (yes even Internet Explorer). So there is no reason to keep it.
  • Diffie-Hellman as key exchange: “ECDHE” or “DHE” in the cipher suite name.
    DHE stands for Diffie-Hellman ephemeral key exchange. The advantage compared to the RSA key exchange is perfect forward secrecy. Even if the server’s private key would get compromised, previously recorded communication cannot be decrypted with it. The “EC” stands for “elliptic curve”, which is a performance improvement compared to traditional Diffie-Hellman (DHE).

For Apache I use the following line (in the same file as above for the TLS versions):

SSLCipherSuite      SSL ECDHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256

The “SSL” directive describes that this cipher suite configuration should be used for all protocol versions up to TLS 1.2. I don’t restrict cipher suites defined in TLS 1.3.

Lastly, I set these three related options:

SSLHonorCipherOrder     on
SSLCompression          off
SSLSessionTickets       off

Why these settings?

  • SSLHonorCipherOrder: During the TLS handshake, the client will specify all cipher suites it supports. The server will then choose one of them. SSLHonorCipherOrder tells the server, to adhere to the specified cipher suite order when searching for a match. In my case, the server will first check, if the client supports ECDHE-RSA-AES256-GCM-SHA384, then ECDHE-RSA-CHACHA20-POLY1305 and so on.
  • SSLCompression: Turn off compression, as it can be an attack vector - as it was for example in the CRIME attack: https://en.wikipedia.org/wiki/CRIME
    Modern browsers will probably not support compression anyway for security reasons, but it can’t hurt to make sure by just disabling it on your end.
  • SSLSessionTickets: Turn this off as well, as recommended in the Apache documentation unless the server is restarted often. According to the mod_ssl documentation (https://httpd.apache.org/docs/current/mod/mod_ssl.html#sslsessionticketkeyfile), the problem seems to be that Apache generates a key to encrypt the tickets at startup. However, the only way to invalidate a ticket is to rollover this key - meaning restarting Apache. As long as there is no other key rollover automation in place, using session tickets is not recommended. TLS session resumption via caching session IDs on the server side will still be possible.

You can find the relevant parts of the Apache documentation here:
https://httpd.apache.org/docs/trunk/ssl/ssl_howto.html

4. HTTP strict transport security (HSTS)

HSTS specifies that a website should only be accessed via HTTPS. The first time a client’s browser connects to the website, it will store the HSTS header. After this first connection, it will know to only use HTTPS from now on, even if someone enters HTTP.
You could also include your website on a “preload” list to avoid this initial potential for a plaintext connection. I don’t do that as I experiment with this website sometimes and I might need to connect via HTTP temporarily in the future.

I configured my HSTS setting like this in my virtual host config for port 443:

Header always set Strict-Transport-Security "max-age=31536000;"

The max-age parameter tells the browser for how many seconds to store this entry. You need a high value to get an A+ rating - 1 year is a typical recommendation that I found in various sources.
That also makes sense, as HSTS is only effective when the browser has already stored the header for the website. Refreshing it too often would decrease the security benefit.
For testing this configuration I would use a lower value though. If there is a configuration mistake and the site is not available via HTTPS, visitors might have trouble connecting until the HSTS max-age expired (or the HTTPS setup is fixed).

To make the HSTS configuration complete, I redirect all HTTP requests to the website to HTTPS instead.
I specify that in my virtual host config for port 80:

 Redirect permanent / https://<yourdomain.com>/

The “permanent” redirect in combination with HSTS results in the following behavior:

  1. A browser connects for the first time, maybe via HTTP
  2. If the request was sent via HTTP, the server sends a 301 redirect to the HTTPS site.
    Once the client connects via HTTPS, the server also includes the HSTS header in its reply. The browser stores the HSTS header for the site.
  3. From now on this browser will connect via HTTPS until the time set in max-age. Then this cycle repeats.

5. CAA Record

This is an easy one. I only needed to add a DNS record of type “CAA” for my domain. The CAA record type is described here in detail: https://tools.ietf.org/html/rfc8659 but the core principle is not that complicated:
When you set a CAA record, you tell all participating certificate authorities (CAs), which CA is allowed to issue certificates for your domain.
All common CAs are checking the CAA record during certificate issuance, as it is a MUST in the CA Browser Forum’s baseline requirements. These requirements are the “ground-rules” for all public CAs in the relevant browser trust stores.

My CAA record looks like this, specifying that only Let’s Encrypt may issue certificates for my domain:

marionsaeckel.de.	1798	IN	CAA	0 issue "letsencrypt.org"

Finished!

One note about backwards compatibility:
I think a rather strict TLS configuration should be no issue when targeting browsers as clients. In a server-to-server environment with older systems in the mix, I would maybe be more careful.
The configuration above should be supported in all browsers in use today - except maybe the ones on some very very old Android phones (< 4.4) or ancient Internet Explorer versions (below 11).
This site is a good resource to check - here for example for HSTS: https://caniuse.com/?search=HSTS

Now after all of that configuration - get your SSL Labs rating and hopefully get rewarded with an A+