I often have to talk people off a cliff because of their website’s (sometimes perceived) vulnerabilities on Qualys’ SSL Labs testing site. They have received an A- score for their site after running the tests, and see a few things in yellow. Suddenly, they begin to believe that every villain on the web is now running amok with their data. The truth of the matter is that if your SSL configuration is rating at an A-, that configuration is usually just fine. Out of curiosity, I recently spent a few hours tweaking and managed to upgrade from an A- to an A+ for this domain. I wanted to share that process so that anyone else can know what it might take to get an A+.

SSL Labs A+ Rating
A+ Rating for network-notes.com on Qualys SSL Labs Testing

Caveats and Advice

The things that get you to an A+, are mostly semantic and not always the most obvious and open vulnerabilities. The SSL Labs scoring process, while open and based on common sense/best practices, is simply one entity’s opinion of your SSL configuration. In addition to the somewhat arbitrary nature of any benchmarking site, such as SSL Labs, remember that a configuration that gets an A+ today is one new vulnerability away from an F. Your security posture on any system must be continuously evolving, along with the threats you’re defending against.

These caveats also hold for any configurations listed here. These are not guarantees that your site will be secure, or even recommendations. They merely reflect the Apache and mod_ssl configurations that suited my needs, in my circumstances, at the time of this writing. Please use common sense when establishing an SSL policy for your website.

The tweaks boiled down to either explicitly defining things in my Apache VirtualHost configuration that I assumed didn’t matter, or bending my strict configuration a little bit to meet the requirements of older browsers. For example, I was initially only going to enable TLSv1.2. If you can’t support TLSv1.2, I wasn’t really worried if the site was unavailable to you. However, as I thought about it, I realized that it was a pointless stance to take. Why isolate this site from an unfortunately large, albeit less secure, portion of the web when I don’t have to? I’m not hosting any sensitive information on these servers; I am not asking users to make credit card transactions or give me their Social Security numbers. I eventually walked my SSL configuration backward and enabled TLSv1.0, TLSv1.1, and TLSv1.2 as part of my remediation efforts to get the elusive A+ from SSL Labs.

How to Get an A- Without Really Trying

Below is an example of the Apache VirtualHost configuration I had in place before beginning this process. It was graded at an A- by SSL Labs and is pretty standard. Most of it was pulled directly from the default-ssl.conf file in Apache 2.4.


<IfModule mod_ssl.c>
    <VirtualHost *:443>
        ServerName network-notes.com
        
        # SSL Configuration
        SSLEngine on
        SSLProtocol +TLSv1.1 +TLSv1.2
        SSLCipherSuite HIGH:!aNULL:!MD5
        SSLCertificateFile      network-notes.com.pem
        SSLCertificateKeyFile   network-notes.com.key
        SSLCertificateChainFile network-notes.com-cabundle

        # Fixes for IE being, well, IE...
        BrowserMatch "MSIE [2-6]" nokeepalive ssl-unclean-shutdown 
                \downgrade-1.0 force-response-1.0
        BrowserMatch "MSIE [17-9]" ssl-unclean-shutdown
        
        # Browser caching for static content
        <filesMatch ".(js|css|png|jpeg|jpg|gif|ico|swf|flv|pdf|zip)$">
                Header set Cache-Control "max-age=1209600, public"
        </filesMatch>

        # Redirect "/" to "/blog/"
        <If "%{REQUEST_URI} == '/'">
                Redirect "/" "/blog/"
        </If>

    </VirtualHost>
</IfModule>

Now, here’s what the good folks at Qualys SSL Labs had to say about that configuration:

  1. “The server does not support Forward Secrecy with the reference browsers. Grade reduced to A-. MORE INFO»
    1. “Forward Secrecy - With some browsers (more info)”
  2. “Incorrect SNI Alerts - www.network-notes.com
  3. In addition, unless a browser was one of the newest versions available, there was also a slew of “Protocol or cipher suite mismatch”.

Working Towards an A

The easiest fix was enabling TLSv1.o as I described earlier. By simply adding +TLSv1 to the SSLProtocol directive, I opened up a much larger range of possible Protocol/Cipher Suite combinations:

        SSLProtocol +TLSv1 +TLSv1.1 +TLSv1.2

Unfortunately, this change resulted in no movement whatsoever of my A- rating. At the very least, issue #3 was resolved; the wide majority of browsers could negotiate a connection. The question remains, however, why would Forward Secrecy only work with some browsers? I have configured all of the DHE and ECDHE ciphers needed and was able to confirm this fact because many browser simulations can connect with an ECDHE cipher. Upon further investigation of the output from SSL Labs, I found the following, “Cipher Suites (sorted by strength as the server has no preference)”. The Forward Secrecy enabled ciphers are available to all browsers, but I wondered if SSL Labs was indicating that some might not pick them because they weren’t offered first.

I always assumed cipher suite order was generally arbitrary, but to appease SSL Labs I sorted them by strength. I added @strength to the end of my SSLCipherSuite directive, and also added the SSLHonorCipherOrder directive. The SSLHonorCipherOrder directive was because, if I’m going to sort my cipher suite, your browser better obey.

        SSLCipherSuite HIGH:!aNULL:!MD5:@STRENGTH
        SSLHonorCipherOrder on

Upon testing again I had… Success! I was upgraded to an A!

From SSL Labs: “Forward Secrecy - Yes (with most browsers) ROBUST (more info)” There is now only one more issue in yellow, so I assumed that fixing the SNI mixup should get me to an A+.

SNI-ed Remarks

The SNI error given was not exactly verbose: “Incorrect SNI Alerts - www.network-notes.com”. This was a little puzzling, as I’m not using SNI for this site; there’s only one domain hosted on the server. After Googling for a few minutes, I found myself at a Qualys Community post from August 2014 detailing this issue. Most helpful it gave a quick and dirty test to validate the issue yourself using OpenSSL (openssl), which I tweaked as shown below to look for unrecognized name SSL alerts. You can see in the first example using network-notes.com, that there was no issue. However, when using www.network-notes.com, the server was indeed giving back an unrecognized name error:

$ SERVER=network-notes.com; echo -e "HEAD / HTTP/1.0\r\nHost: $SERVER\r\n\r\n" | openssl s_client -servername $SERVER -connect $SERVER:443 -state 2>&1 | grep unrecognized
$ 

$ SERVER=www.network-notes.com; echo -e "HEAD / HTTP/1.0\r\nHost: $SERVER\r\n\r\n" | openssl s_client -servername $SERVER -connect $SERVER:443 -state 2>&1 | grep unrecognized
SSL3 alert read:warning:unrecognized name
$ 

This is a perfect example of the minutia you have to wade through in search of an A+. My DNS for this domain is configured with www.network-notes.com as a CNAME for network-notes.com, so no one should ever wind up at www.network-notes.com. However, since it is a SAN in my SSL certificate, according to Qualys I need to specify it as a ServerAlias in my Apache VirtualHost configuration:

        ServerAlias www.network-notes.com

This change cleared up the SNI error on SSL Labs, and in my testing, however, my grade is still (only?) an A. Now it’s time to think this through and re-read the output from SSL Labs.

Headed for an A+

One of the lines indicated that I did not have the HSTS (HTTP Strict Transport Security) enabled. There’s a great summary of HSTS, including sample configurations at Raymii.org, but essentially it is an HTTP header that is returned to your browser on the first visit. This header tells your browser to always use HTTPS for any subsequent visits to your domain. Since I have all traffic redirecting to HTTPS already, I figured this would be a simple addition. After all, at this point, I was grasping at straws and I added the following configuration:

        Header always set Strict-Transport-Security "max-age=63072000; includeSubdomains; preload"

And finally, lo and behold, the elusive A+!

Quote from the test: “This server supports HTTP Strict Transport Security with long duration. Grade set to A+.”

Final Results

Now that we have the A+, I figured I should go ahead and complete the other task that I was considering as a possibility, configuring OSCP Stapling. This was a simple enough configuration consisting of two additional lines, one defining where the OSCP Stapling cache would be and another turning the feature on. While this change didn’t upgrade my SSL Labs score any further (A++ anyone?), it did green up another field. After all this time working on the configuration was still fairly satisfying.

Below is an example of what my SSL VirtualHosts file looked like after all of these contortions. I hope you all find it useful, and please let me know if you all have any recommendations, corrections, or your own stories of optimizing SSL.


<IfModule mod_ssl.c>
    SSLStaplingCache shmcb:/tmp/stapling_cache(128000)
    <VirtualHost *:443>

        ServerName network-notes.com
        ServerAlias www.network-notes.com
        
        # SSL Configuration
        SSLEngine on
        SSLProtocol +TLSv1 +TLSv1.1 +TLSv1.2
        SSLCipherSuite HIGH:!aNULL:!MD5:@STRENGTH
        SSLHonorCipherOrder on
        SSLCertificateFile      network-notes.com.pem
        SSLCertificateKeyFile   network-notes.com.key
        SSLCertificateChainFile network-notes.com-cabundle
        SSLUseStapling on

        # HSTS Configuration
        Header always set Strict-Transport-Security "max-age=63072000; includeSubdomains; preload"

        # Fixes for IE being, well, IE...
        BrowserMatch "MSIE [2-6]" nokeepalive ssl-unclean-shutdown 
                \downgrade-1.0 force-response-1.0
        BrowserMatch "MSIE [17-9]" ssl-unclean-shutdown
        
        # Browser caching for static content
        <filesMatch ".(js|css|png|jpeg|jpg|gif|ico|swf|flv|pdf|zip)$">
                Header set Cache-Control "max-age=1209600, public"
        </filesMatch>

        # Redirect "/" to "/blog/"
        <If "%{REQUEST_URI} == '/'">
                Redirect "/" "/blog/"
        </If>

    </VirtualHost>
</IfModule>