Achieving a Perfect SSL Labs Score with Go

Go does a great job of getting a good SSL Labs Score out of the box, however I thought it would be interesting to see what would be needed to achieve the perfect score, getting 100% in all categories and not just a boring A.

There are plenty of guides around that demonstrate how to do this with NGINX or Apache, but there seems little that explains what is required for a typical Go app.

Qualys have a detailed guide that is very clear about how you can improve your score. I’ll refer to various sections, however you can check it out yourself here.

First of all, let’s see what Go scores with default configuration.

Standard Go Server With TLS

and the score…

Standard Go Server - SSL Labs Result

OK, not bad. An A is a pretty good score. Let’s see if we can get an A+.

Getting an A+ with HSTS

The SSL Rating Guide states:

New grade A+ is introduced for servers with exceptional configurations. At the moment, this grade is awarded to servers with good configuration, no warnings, and HTTP Strict Transport Security support with a max-age of at least 6 months.

The basic server already has a good configuration and no warnings, so we just need to add HSTS (with a long max-age).

HSTS is simply a HTTP header (Strict-Transport-Security) that instructs the browser to change all http:// requests to https://.

Excellent. We now get an A+.

HSTS Support- SSL Labs Result

You should probably stop here. Continuing to attempt to achieve a perfect score will result in reduced client compatibility. This means that many users will not be able to access your site.

Protocol Support

Protocol support is already at 90%, so there shouldn’t be much to do.

Getting 100% requires that the server only supports TLS 1.2 or above. This is easily changed by manually configuring a &tls.Config{} struct rather than allowing the server to use the default configuration.

Re-running the test…

Protocol Support- SSL Labs Result

Great. 100% on protocol support. Onto key exchange.

Key Exchange

From the SSL Rating Guide

Key or DH parameter strength >= 4096 bits (e.g., 4096)

Looking at my private key, the default size provided by Let’s Encrypt is 2048 bit. Re-generating a new one was easy. I use an excellent third party ACME client called Lego. This tool is also written in Go and is very easy to use. I generated a new key and retrieved a new certificate with the following command.

lego -d -m [email protected] --rsa-key-size=4096 run

In addition, I was required to change the CurvePreferences, to prioritise using the elliptic curve algorithms with an approximate comparable symmetric key size of >=4096 bit.

I simply took the default configuration from the go source and moved the lower equivalent key size to the end.

Re-running the test…

Key Exchange - SSL Labs Result

Whoop! 100% on key exchange. Just one more.

Cipher Strength

From the SSL Rating Guide

  1. Start with the score of the strongest cipher.
  2. Add the score of the weakest cipher.
  3. Divide the total by 2.

>= 256 bits (e.g., 256) = 100%

This means I need to take the default CipherSuites and simply remove any that use a cipher smaller than 256bit.

No HTTP/2 for you! - HTTP/2 was enabled by default in go 1.6, however HTTP/2 mandates the support of the cipher suite TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256. As this is a 128bit cipher, it needs to be removed. The only way to achieve a perfect score now is to disable HTTP/2.

This is not as obvious as you might think.

from the go 1.6 release notes

Programs that must disable HTTP/2 can do so by setting Transport.TLSNextProto (for clients) or Server.TLSNextProto (for servers) to a non-nil, empty map.

Adding this to our code…

Disabling HTTP/2 also disables the server cipher preference which results the following warning:

The server does not support Forward Secrecy with the reference browsers. Grade reduced to A-.

This can be corrected by setting the PreferServerCipherSuites option in tls.Config.

One last test…

Cipher Strength - SSL Labs Result


Putting it all together