ECDSA Certificates with Apache 2.4 & Lets Encrypt

Update: Modified the configuration so instead of whitelisting TLS versions, it is blacklisting insecure TLS versions based on feedback. Thanks Johannes Pfrang.

A little while ago, I wrote a post about running dual RSA and ECDSA certificates on my websites. Since then, I’ve found that there is little to no impact of running my websites with only an ECDSA certificate. You can also get these certificates for free, since Let’s Encrypt is now signing ECDSA certificates with their RSA root.

Infrastructure Set-Up

The infrastructure supporting this all is as follows:

  1. Ubuntu 15.10
  2. Ondřej Surý’s Apache 2.4.x PPA
  3. OpenSSL 1.0.2 (via the PPA above)
  4. Let’s Encrypt Auto Script

Capturing TLS Usage Logs

One of the things that did help with deciding how to proceed with this setup was to configure a new Apache access log file to capture TLS request data.

LogFormat "%t,%h,%H,%v,%{SSL_PROTOCOL}x,%{SSL_CIPHER}x,\"%{User-agent}i\"" ssl
CustomLog /var/log/apache2/ssl.log ssl

This outputs a log file that shows the time of the request, the source IP address, the HTTP version, Apache Virtual Host, TLS Version, TLS Cipher and the Browser User Agent. Example:

[28/Feb/2016:13:42:20 +1100],1.2.3.4,HTTP/2,blog.joelj.org,TLSv1.2,ECDHE-ECDSA-AES128-GCM-SHA256,"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/48.0.2564.116 Safari/537.36"
[28/Feb/2016:13:45:31 +1100],4.5.6.7,HTTP/1.1,blog.joelj.org,TLSv1.2,ECDHE-ECDSA-AES128-GCM-SHA256,"Mozilla/5.0 (compatible; Googlebot/2.1; +http://www.google.com/bot.html)"
[28/Feb/2016:13:45:52 +1100],1.2.3.4,HTTP/2,blog.joelj.org,TLSv1.2,ECDHE-ECDSA-AES128-GCM-SHA256,"Mozilla/5.0 (Windows NT 10.0; WOW64; rv:44.0) Gecko/20100101 Firefox/44.0"
[28/Feb/2016:13:46:06 +1100],1.2.3.4,HTTP/2,blog.joelj.org,TLSv1.2,ECDHE-ECDSA-AES256-GCM-SHA384,"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/42.0.2311.135 Safari/537.36 Edge/12.10240"

In the end, I discovered it was fairly easy to run some basic queries against this data. I ended up doing the following:

  1. Using the AWS Cloudwatch Logs Agent to send the whole log file in real-time to AWS Cloudwatch Logs.
  2. Using the AWS Management Console to export all logs to Amazon S3 and then download them locally.
  3. Using q – Text as Data to run SQL queries directly the GZIP’d log data.

Once I had all the AWS data from S3 in a folder, I could run a simple query such as:

q -d"," -T "SELECT c6, count(*) FROM ..\data-q\*.gz GROUP BY c6 ORDER BY count(*) DESC"

This gives me some nice, basic statistics (percentages added in post-processing):

Ciphersuite Requests %
ECDHE-RSA-AES128-GCM-SHA256 45557 93.5
ECDHE-RSA-AES256-GCM-SHA384 1914 3.9
ECDHE-RSA-AES128-SHA 625 1.3
ECDHE-RSA-AES128-SHA256 293 0.6
ECDHE-RSA-AES256-SHA  184 0.4
ECDHE-RSA-AES256-SHA384  149 0.3

Apache TLS Hardening

I had already decided that DHE support wasn’t needed on my website. This was largely due to the fact I was running multiple SSL virtual hosts on a single IP (so lot’s of the older clients which didn’t support SNI wouldn’t work anyway). The only SNI supporting client I would lose, according to the Qualys SSL Labs Server test, was OpenSSL 0.9.8. Given it’s not a typical user-facing client, I decided this wasn’t a major loss.

Therefore, the move to ECDSA certificates had no noticeable impact on my client compatibility. The ciphersuites (and order) I landed on are as follows:

  1. ECDHE-ECDSA-AES256-GCM-SHA384
  2. ECDHE-ECDSA-CHACHA20-POLY1305
  3. ECDHE-ECDSA-AES128-GCM-SHA256
  4. ECDHE-ECDSA-AES256-SHA384
  5. ECDHE-ECDSA-AES256-SHA
  6. ECDHE-ECDSA-AES128-SHA256
  7. ECDHE-ECDSA-AES128-SHA

Note: ChaCha20-Poly1305 is in there for a bit of future proofing once OpenSSL 1.1 is released.

In the end, all the following SSL hardening (which I’ve adapted from the CIS Apache Server Benchmark, Mozilla Server-Side TLS recommendations and various online sources), ends up looking like this:

# Disable insecure renegotiation.
SSLInsecureRenegotiation Off

# Disable Compression.
SSLCompression Off

# Set up OCSP stapling.
SSLUseStapling On
SSLStaplingResponderTimeout 5
SSLStaplingReturnResponderErrors off
SSLStaplingCache shmcb:logs/ssl_stapling(32768)

# Disable Session Tickets.
SSLSessionTickets Off

# Turn on TLS.
SSLEngine on

# Disallow insecure protocols, allow current and future protocols. (TLS 1.0 is arguably still required.)
SSLProtocol -SSLv2 -SSLv3

# Ask the client to prefer the order of support ciphers advertised by the server.
SSLHonorCipherOrder On

# The actual ciphersuite and order to use.
SSLCipherSuite "ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA:ECDHE-RSA-AES256-SHA:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES128-SHA:!aNULL:!eNULL:!EXPORT:!RC4:!DES:!SSLv2:!MD5:!SSLV3:!3DES:!PSK:!aECDH:!EDH-DSS-DES-CBC3-SHA:!EDH-RSA-DES-CBC3-SHA:KRB5-DES-CBC3-SHA:"

# Bump up the strength of the ECDHE key exchange.
SSLOpenSSLConfCmd ECDHParameters secp384r1
SSLOpenSSLConfCmd Curves secp384r1

This pretty much sums up all of the TLS related hardening. The only thing I can think of that’s missing would be to support Certificate Transparency via providing Signed Certificate Timestamps (SCT)’s. It looks like there isn’t a way to do this on Apache 2.4. The current Apache module (mod_ssl_ct) seems to require you to compile a development/trunk version of Apache (and exists in the documentation as an unreleased Apache 2.5).

Manually Generating Signed ECDSA Certificates from Let’s Encrypt

I’m sure an option to generate ECDSA certificates is on its way with the automatic Let’s Encrypt tool, but here is how you can manually generate certificates for now:

# Create the private key.
openssl ecparam -genkey -name secp384r1 > "/letsencrypt/ecdsa/blog.joelj.org/privkey.pem"

# Create OpenSSL configuration file.
cat /etc/ssl/openssl.cnf > "/letsencrypt/ecdsa/blog.joelj.org/openssl.cnf"
echo "[SAN]" >> "/letsencrypt/ecdsa/blog.joelj.org/openssl.cnf"

# Pick one based on whether its a root domain or not. E.G:
echo "subjectAltName=DNS:blog.joelj.org" >> "/letsencrypt/ecdsa/blog.joelj.org/openssl.cnf"
OR
echo "subjectAltName=DNS.1:joelj.org,DNS.2:www.joelj.org" >> "/letsencrypt/ecdsa/joelj.org/openssl.cnf"	
	
# Create Certificate Signing Request.
openssl req -new -sha256 -key "/letsencrypt/ecdsa/blog.joelj.org/privkey.pem" -nodes -out "/letsencrypt/ecdsa/blog.joelj.org/request.csr" -outform pem -subj "/O=blog.joelj.org/emailAddress=someone@joelj.org/CN=blog.joelj.org" -reqexts SAN -config "/letsencrypt/ecdsa/blog.joelj.org/openssl.cnf"

# Get signed certificate.
/letsencrypt/letsencrypt-auto certonly --webroot -w /var/www/blog.joelj.org/ -d blog.joelj.org --email "someone@joelj.org" --csr "/letsencrypt/ecdsa/blog.joelj.org/request.csr"

If this is the first time you’ve done this, all you need to do now is point to the latest certificate chain and private key in your relevant Apache configuration location.

SSLCertificateFile /letsencrypt/ecdsa/blog.joelj.org/0001_chain.pem
SSLCertificateKeyFile /letsencrypt/ecdsa/blog.joelj.org/privkey.pem

Qualys SSL Labs Testing

I was fairly satisified with the range of flexibility and client support this configuration allowed for!

ecdsa-blog-score

ecdsa-blog-client-simulation

6 thoughts on “ECDSA Certificates with Apache 2.4 & Lets Encrypt”

  1. Hi Joel,

    if you’re already future-proofing your server for ChaCha20-Poly1305 you should also future-proof it for TLS 1.3.

    SSLProtocol +TLSv1 +TLSv1.1 +TLSv1.2 # doesn’t enable TLS1.3
    SSLProtocol -SSLv2 -SSLv3 # enables TLS1.3, if supported

    Best Regards
    Johannes

  2. Could you give me a little more help with the Let’s Encrypt part? I have a domain that we’ll call example.com. But I also have a bunch of subdomains like hostname.example.com, webmail.example.com, whm.example.com, etc.

    How would I generate a valid certificate for not only example.com and http://www.example.com, but for all the subdomains as well?

    Thanks!

    1. I should probably add that my current Let’s Encrypt command that gets run to generate a new certificate looks a bit like this:

      /usr/bin/letsencrypt certonly –keep-until-expiring –non-interactive –staple-ocsp –must-staple –hsts –redirect –rsa-key-size 2048 –uir –webroot -w /home/username/public_html -d http://www.example.com -d example.com -w /home/username/public_html/cpanel -d cpanel.example.com …

      Thanks!

Leave a Reply

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