Rotors of M-209 cipher machine.

Running TLS 1.3 with OpenSSL and Nginx

High Performance and Security

In mid-2018 my web server was running TLS 1.0. Should it? The 1.0 version of the Transport Layer Security protocol has security problems. The Payment Card Industry Data Security Standard (PCI-DSS) mandated that all sites handling credit and debit card information must drop TLS 1.0 by July 2018. A few months later, the major browsers all announced that they would drop support for TLS 1.0 in early 2020, probably around March.

However, my site doesn't handle sensitive data. Would I lose a significant number of readers, and thus revenue, if I turned off TLS 1.0? I analyzed the traffic distribution and verified that turning off TLS 1.0 would have little impact.

Meanwhile, it was not running TLS 1.3. That protocol had finally been formally defined just a month before. It has better security and performance than all earlier versions.

It was not yet a trivial matter of simply turning on TLS 1.3. But, as I explain here, it wasn't difficult to build the needed software packages and configure a secure TLS-1.3-based web server.

At the time I did this, TLS 1.3 wasn't possible with the stock version of Apache. I needed to use Nginx along with a custom installation of the latest OpenSSL cryptographic libraries.

First, I installed the very latest OpenSSL cryptographic libraries in parallel with the standard libraries included with the operating system.

Next, I built Nginx from source code, using those cutting-edge libraries.

Once I had a functioning web server, I customized its configuration to get a high score from Qualys and other online security analyzers.

However, before adding a new protocol, the first question was whether I should retain the oldest one my server supported.

If you're ready to get started, click the below button to jump to the description of how to build the software and get TLS 1.3 running on your system. The rest of this page shows you how to analyze your logs to see the distribution of TLS versions, and provides background on the security and performance advantages of TLS.

Should I Drop TLS 1.0?

That was my first question. My site doesn't contain sensitive information, and there's no way to submit passwords or other sensitive user data. However, two reasons compel me to run a secure server:

Search engines and browsers prefer better security. Google includes HTTPS support in its ranking algorithm. They say very little about their algorithms, to keep webmasters from gaming the system. But, by 2017 Google was saying that HTTPS support provides a (then very minor) advantage in ranking. In fact, when they announced this, they said that it already had been doing so for some time. In mid 2018, Chrome began displaying HTTP URLs as "Not secure". When Chrome adds a security feature, other browsers typically follow.

I should provide a good example. The certificates are free, and it's not a lot of work to set this up correctly. Plus, once I've done it, and documented it on a web page, I have notes on how to do it for a client.

So, yes, I want to set things up securely. TLS 1.0 includes several ciphers now considered weak. The thing is, I have Google AdSense ads on my pages, along with Amazon Affiliate ads. More cautious security settings mean that old clients can't connect, meaning fewer page views, fewer ad views and clicks, and therefore less ad revenue for me.

Would dropping TLS 1.0 support cost me in page views, and therefore ad revenue?

How Many Clients Use TLS 1.0?

I had no idea! I don't log the User-Agent value, the browser and version reported by the client. It makes the logs significantly larger and you can't trust the data. If you could trust that data, it would only tell you what the client could support, but not what it really used.

The solution is to ask the web server to log the TLS protocol for each connection.

Add these two lines to the Apache configuration file:

RequestHeader set X-SSL-Protocol %{SSL_PROTOCOL}s
RequestHeader set X-SSL-Cipher %{SSL_CIPHER}s 

Then, within the <IfModule log_config_module> stanza, modify LogFormat:

<IfModule log_config_module>
  [... lines deleted ...]

  LogFormat "%h %l %u %t \"%r\" %>s %b \"%{Referer}i\" %{SSL_PROTOCOL}x %{SSL_CIPHER}x" combined
  SetEnvIf Request_Method "^OPTIONS$" no_log
  SetEnvIf Request_URI ".(jpg|jpeg|png|css|gif|ico|js|ttf)" no_log

  CustomLog "/usr/local/www/logs/httpd-access.log" combined env=!no_log

</IfModule> 

The resulting log file entries will look like the following. See the Apache documentation for further details, and read about the seldom-used identd daemon if you're curious about %l, the remote user name.

40.77.167.107 - - [08/Sep/2018:17:01:47 +0000] "GET /travel/ HTTP/1.1" 200 5036 "-" TLSv1.2 ECDHE-ECDSA-AES256-GCM-SHA384
     ^        ^ ^               ^                ^      ^       ^       ^   ^    ^     ^                  ^
     |        | |               |                |      |       |       |   |    |     |                  |
%h = client   | |  %t= date and time stamp       |   resource   |       |   |    |  TLS protocol        cipher
IP address    | |                                |              |       |   |    |
              | |            %r = request, starting   protocol version  |   |  "%{Referer}i" = refering URI, often empty
    %l = remote |            with command (GET,                         |  %b = bytes transferred
    name, empty |            HEAD, POST, etc)                         %>s = final status, 200 = "success"
               %u = user name, if authenticated (always empty on my server)

Then, restart the web server and see what's being recorded.

$ tail -f /usr/local/www/logs/httpd-access.log
207.46.13.230 - - [08/Sep/2018:16:59:05 +0000] "GET /open-source/google-freebsd-tls/tls-certificate.html HTTP/1.1" 200 18529 "-"
54.36.148.255 - - [08/Sep/2018:16:59:24 +0000] "GET /open-source/performance-tuning/ HTTP/1.1" 301 259 "-"
40.77.167.107 - - [08/Sep/2018:16:59:47 +0000] "GET /travel/italy/amalfi/ HTTP/1.1" 301 293 "-"
139.162.119.197 - - [08/Sep/2018:17:00:15 +0000] "GET / HTTP/1.1" 301 230 "-" - -
172.250.253.195 - - [08/Sep/2018:17:00:29 +0000] "GET /open-source/raspberry-pi/hardware.html HTTP/2.0" 200 14474 "https://www.google.com/" TLSv1.2 ECDHE-ECDSA-AES256-GCM-SHA384
40.77.167.107 - - [08/Sep/2018:17:01:47 +0000] "GET /travel/china/ HTTP/1.1" 200 5036 "-" TLSv1.2 ECDHE-ECDSA-AES256-GCM-SHA384
189.68.148.109 - - [08/Sep/2018:17:01:54 +0000] "GET / HTTP/1.1" 301 233 "-" - -
148.64.56.70 - - [08/Sep/2018:17:02:19 +0000] "GET /technical/wma-or-flac-to-mp3.html HTTP/1.1" 200 6415 "-" TLSv1.2 ECDHE-ECDSA-AES256-GCM-SHA384
40.77.167.107 - - [08/Sep/2018:17:02:33 +0000] "GET /travel/japan/ HTTP/1.1" 200 18423 "-" TLSv1.2 ECDHE-ECDSA-AES256-GCM-SHA384
...  

There it is, it started at 17:00:29!

Why isn't it shown for the entry at 17:01:54? That client connected via HTTP. My server sent it a code 301 redirect to the equivalent HTTPS URL, but the client ignored the redirect. That client is just one of the many bots scanning for web servers.

Here are some entries just a few minutes later:

...
72.14.199.121 - - [08/Sep/2018:17:06:08 +0000] "GET /open-source/google-freebsd-tls/https-headers.html HTTP/1.1" 200 8464 "-" TLSv1.2 ECDHE-ECDSA-AES256-GCM-SHA384
62.16.77.249 - - [08/Sep/2018:17:06:26 +0000] "GET /travel/egypt/ HTTP/2.0" 200 5281 "https://toilet-guru.com/north-africa.php" TLSv1.2 ECDHE-ECDSA-AES256-GCM-SHA384
180.248.19.54 - - [08/Sep/2018:17:06:40 +0000] "GET /wp-login.php HTTP/1.1" 301 246 "-" - -
180.248.19.54 - - [08/Sep/2018:17:06:41 +0000] "GET /wp-login.php HTTP/1.1" 404 13849 "-" TLSv1 ECDHE-ECDSA-AES256-SHA 
...

At 17:06:40 an automated bot connected. It was looking for WordPress sites to attack. It connected via HTTP (no TLS or cipher, just "- -"), received a 301 code redirect to https://cromwell-intl.com/wp-login.php, and it immediately tried that.

Working on this type of project quickly shows you that you need to filter out more of the automated bots...

Moving on, three more successful redirects quickly followed. The colors match the pairs of insecure and secure requests:

...
66.249.79.19 - - [08/Sep/2018:17:06:41 +0000] "GET /cybersecurity/monitoring.html HTTP/1.1" 301 263 "-" - -
180.248.19.54 - - [08/Sep/2018:17:06:42 +0000] "GET / HTTP/1.1" 301 234 "-" - -
180.248.19.54 - - [08/Sep/2018:17:06:42 +0000] "GET / HTTP/1.1" 200 15557 "-" TLSv1 ECDHE-ECDSA-AES256-SHA
66.249.79.23 - - [08/Sep/2018:17:06:42 +0000] "GET /cybersecurity/monitoring.html HTTP/1.1" 200 16136 "-" TLSv1.2 ECDHE-ECDSA-AES256-GCM-SHA384
95.135.32.47 - - [08/Sep/2018:17:07:35 +0000] "GET /turkish/ HTTP/1.1" 301 242 "http://petitepolyglot.com/turkish30/" - -
95.135.32.47 - - [08/Sep/2018:17:07:36 +0000] "GET /turkish/ HTTP/1.1" 200 7179 "http://petitepolyglot.com/turkish30/" TLSv1.2 ECDHE-RSA-AES256-SHA384
148.64.56.74 - - [08/Sep/2018:17:07:44 +0000] "GET /technical/dsl/ HTTP/2.0" 200 12263 "-" TLSv1.2 ECDHE-ECDSA-AES256-GCM-SHA384
...

Good, the data is being collected. Let it run for a few days...

Now let's check the numbers!

$ cp /usr/local/www/logs/httpd-access.log /tmp
$ awk '/GET.* TLSv1/ {print $(NF-1)}' /tmp/httpd-access.log | sort | uniq -c
   4198 TLSv1
     17 TLSv1.1
  68497 TLSv1.2 

I did not expect this! I have almost no TLS v1.1 clients. But, 4,198 or just over 6% of the clients run TLS 1.0 (which appears as "TLSv1" in the logs). This was typical. Cloudflare reported in late 2017 that they were seeing 88.20% TLS 1.2, 11.36% TLS 1.0, and 0.38% TLS 1.1.

I wouldn't want to take a 6% cut in ad revenue. However...

How many of the TLS 1.0 clients are legitimate? Let's find the top 10 requests made by TLS 1.0 clients:

$ awk '/GET.* TLSv1 / {print $7}' /tmp/httpd-access.log | sort | uniq -c | sort -n | tail
     18 /utility/convert/data/config.inc.php
     19 /open-source/torrent-magnet-links.html
     20 /contact.html
     25 /technical/dsl/
     31 /user.php
     35 /robots.txt
    103 /radio/tv-antenna.html
    361 /wp-login.php
    408 /radio/copper-wire/
    477 / 

The highlighted requests are from hostile automated bots looking for WordPress and other attack targets:

config.inc.php
Used by phpMyAdmin, a free software tool used to remotely administer MySQL databases.

/user.php
Used by WordPress.

/robots.txt
This file tells search engines what existing URLs should not be indexed. Some sites attempt to use that for security, effectively telling search engines "These pages contain sensitive data so don't index them." They haven't realized that this will just attract attackers, who also can read /robots.txt.

/wp-login.php
Used by WordPress.

As for the main page, "/", there's very little content on that page. Google wouldn't suggest it unless you submitted a weirdly specific search request:
bob cromwell's web site
Almost all the requests for that page come from automated bots.

What about the two legitimate pages with over 100 requests? Let's see who was asking:

$ awk '/radio\/tv-antenna.html.* TLSv1 / {print $1}' /tmp/httpd-access.log | sort | uniq -c | sort -n
      1 174.110.146.23
      1 182.183.15.178
      1 23.237.4.26
      1 75.98.9.249
      1 83.234.104.146
      3 178.165.56.235
      4 23.101.169.3
     91 185.203.241.169

$ awk '/radio\/copper-wire.* TLSv1 / {print $1}' /tmp/httpd-access.log | sort | uniq -c | sort -n | tail
     15 183.1.252.182
     15 183.14.78.45
     15 203.100.83.62
     15 39.152.105.9
     15 49.89.112.141
     15 58.21.125.192
     15 58.212.136.171
     23 113.118.15.106
     27 1.57.119.117
     28 60.26.66.164 

Not many people look at my do-it-yourself TV antenna page! Its requests were dominated by one strangely obsessed client. The last time I checked, 185.203.241.169 resolved to vm439502.had.su, which suspiciously is in the .su top-level domain. That was assigned to the Soviet Union in September 1990, just 15 months before the USSR dissolved in late 1991. The WHOIS record claims that it's in a small block of 128 addresses, 185.203.241.128/25, physically located in the Netherlands but belonging to someone in the Seychelles islands in the Indian Ocean.

The page about copper wire as a more natural distribution, and a larger number of page views. But still, it isn't a major part of my site's traffic.

What protocols are used by those clients looking for copper wire details, and how does that compare to more popular areas of my site?

$ awk '/radio\/copper-wire.*TLSv1/ {print $(NF-1)}' httpd-access.log | sort | uniq -c
    408 TLSv1
    157 TLSv1.2
$ awk '/travel\/france.*TLSv1/ {print $(NF-1)}' httpd-access.log | sort | uniq -c
     89 TLSv1
   4424 TLSv1.2
$ awk '/open-source.*TLSv1/ {print $(NF-1)}' httpd-access.log | sort | uniq -c
    222 TLSv1
      5 TLSv1.1
  17502 TLSv1.2
$ awk '/technical\/dsl.*TLSv1/ {print $(NF-1)}' httpd-access.log | sort | uniq -c
     26 TLSv1
      2 TLSv1.1
   2563 TLSv1.2 

This is interesting! The majority of people looking at the copper wire page run much older browsers. Google's AdSense interface shows me that the majority of my clients come from North America. After that, it's largely other countries where English is spoken (UK, India, Canada, Australia) and countries for which I have several travel pages (UK again, Italy, China, France, Japan, Greece), all of which are also home to people interested in Linux and cybersecurity.

So, as with my page explaining how you can build your own oscilloscope probes, I think the page with copper wire tables is of more interest to people in parts of the world where you have to build your own equipment. Their computers are more likely to run an old operating system and applications. And, thinking of ad revenue, those people are probably less likely to investigate things they can't afford.

Clients of cromwell-intl.com, December 2017 through September 2018

Google AdSense report of the clients of cromwell-intl.com, for the first 9 months of 2018.

What about the TLS 1.0 clients overall? The traffic is dominated by a few obsessive clients, including that USSR/Seychelles oddity. The top 9 clients made 2,219 (or 52.9%) of the 4,198 TLS 1.0 requests.

$ awk '/GET.* TLSv1 / {print $1}' /tmp/httpd-access.log | sort | uniq -c | sort -n | tail -20
     16 178.165.56.235
     16 46.118.155.165
     17 61.177.20.67
     18 174.122.154.124
     19 195.154.146.17
     19 94.65.227.125
     20 212.106.81.31
     21 91.209.51.22
     23 113.118.15.106
     27 1.57.119.117
     28 60.26.66.164
     67 87.252.208.68
     71 125.121.52.54
     78 217.182.252.214
     91 185.203.241.169
    105 218.93.207.12
    117 75.98.9.249
    186 23.101.169.3
    673 23.237.4.26
    831 107.151.148.75 

I have no pages named "*.php" or "*.asp", and no directories or files with names starting "wp*". Those would be requested by bots looking for PHP, ASP, and WordPress vulnerabilities. I will count those, plus the bot requests for /robots.txt, the TV antenna and copper wire obsessives, and the highly suspicious requests for "/".

$ find /var/log -name '*.php' -o -name '*.asp' -o -name 'wp*'    # look, no output!
$ grep ' TLSv1 ' httpd-access.log | egrep -c '\.php|\.asp|/wp|?.*=|/robots.txt|/radio/tv-antenna.html|/radio/copper-wire/|GET / '
2789 

Out of the 4,198 TLS 1.0 client requests during that measurement period, 2,789 (or 66.4%) of them were obviously probes.

Sorry, all you people using Windows XP to look at my page about copper wire. I'm dropping support for TLS 1.0.

While I'm easily analyzing my log file with powerful command-line tools including awk, sort, uniq, head, and tail, let's see what ciphers people are using.

$ awk '/ TLSv1 / {print $NF}' /tmp/httpd-access.log | sort | uniq -c | sort -n
      1 DHE-RSA-AES128-SHA
     11 ECDHE-ECDSA-AES128-SHA
     29 ECDHE-RSA-AES256-SHA
     60 AES256-SHA
    162 DHE-RSA-AES256-SHA
    211 DES-CBC3-SHA
   3758 ECDHE-ECDSA-AES256-SHA

$ awk '/ TLSv1.1 / {print $NF}' /tmp/httpd-access.log | sort | uniq -c | sort -n
      7 ECDHE-RSA-AES256-SHA
     28 ECDHE-ECDSA-AES256-SHA

$ awk '/ TLSv1.2 / {print $NF}' /tmp/httpd-access.log | sort | uniq -c | sort -n
      1 AES256-SHA
      1 DHE-RSA-AES256-SHA
      2 AES256-GCM-SHA384
      5 AES256-SHA256
      6 DHE-RSA-AES256-SHA256
      7 AES128-GCM-SHA256
     20 ECDHE-RSA-AES256-SHA384
     33 ECDHE-ECDSA-AES256-SHA
     44 ECDHE-ECDSA-AES128-SHA
     99 ECDHE-RSA-AES128-GCM-SHA256
    140 DHE-RSA-AES256-GCM-SHA384
    244 ECDHE-RSA-AES256-GCM-SHA384
    523 ECDHE-ECDSA-AES128-SHA256
    633 ECDHE-ECDSA-AES256-SHA384
   3097 ECDHE-ECDSA-AES128-GCM-SHA256
  65925 ECDHE-ECDSA-AES256-GCM-SHA384

Cutting Off TLS 1.0

This is easy. In the Apache configuration file httpd.conf, add a parameter to disable TLSv1 (meaning 1.0) in addition to the already disabled SSLv2 and SSLv3.

[... many lines deleted ...]
SSLProtocol all -SSLv2 -SSLv3 -TLSv1
[... many lines deleted ...]

Signal the server to reload its configuration or restart, and then let's run a test. I'll try to connect using TLS 1.0. This should fail:

$ openssl s_client -connect cromwell-intl.com:443 -tls1
CONNECTED(00000003)
139907967088280:error:1409442E:SSL routines:ssl3_read_bytes:tlsv1 alert protocol version:s3_pkt.c:1498:SSL alert number 70
139907967088280:error:1409E0E5:SSL routines:ssl3_write_bytes:ssl handshake failure:s3_pkt.c:659:
---
no peer certificate available
---
No client certificate CA names sent
---
SSL handshake has read 7 bytes and written 0 bytes
---
New, (NONE), Cipher is (NONE)
Secure Renegotiation IS NOT supported
Compression: NONE
Expansion: NONE
No ALPN negotiated
SSL-Session:
    Protocol  : TLSv1
    Cipher    : 0000
    Session-ID: 
    Session-ID-ctx: 
    Master-Key: 
    Key-Arg   : None
    PSK identity: None
    PSK identity hint: None
    SRP username: None
    Start Time: 1538082337
    Timeout   : 7200 (sec)
    Verify return code: 0 (ok)
---

Excellent! The "CONNECTED" in the first line means that it established a TCP connection, but then there's an SSL handshake failure. That's exactly what we wanted. Now press Control-C to disconnect.

Compare that to what happens when you specify TLS 1.2 instead.

$ openssl s_client -connect cromwell-intl.com:443 -tls1_2
CONNECTED(00000003)
depth=2 O = Digital Signature Trust Co., CN = DST Root CA X3
verify return:1
depth=1 C = US, O = Let's Encrypt, CN = Let's Encrypt Authority X3
verify return:1
depth=0 CN = cromwell-intl.com
verify return:1
---
Certificate chain
 0 s:/CN=cromwell-intl.com
   i:/C=US/O=Let's Encrypt/CN=Let's Encrypt Authority X3
 1 s:/C=US/O=Let's Encrypt/CN=Let's Encrypt Authority X3
   i:/O=Digital Signature Trust Co./CN=DST Root CA X3
---
Server certificate
-----BEGIN CERTIFICATE-----
MIIFdzCCBF+gAwIBAgISA7fGqL6mRybYIxbSvUXi+8EpMA0GCSqGSIb3DQEBCwUA
MEoxCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1MZXQncyBFbmNyeXB0MSMwIQYDVQQD
[... lines deleted ...]
pRmOvK7sjZYGFxlybgC94yHztSxXNaGA1u4L+h+yZuOznc9ajjJV8F/B5p3zmtI3
v6d5aGQFRQZR4R4=
-----END CERTIFICATE-----
subject=/CN=cromwell-intl.com
issuer=/C=US/O=Let's Encrypt/CN=Let's Encrypt Authority X3
---
No client certificate CA names sent
Peer signing digest: SHA256
Server Temp Key: ECDH, P-521, 521 bits
---
SSL handshake has read 3165 bytes and written 500 bytes
---
New, TLSv1/SSLv3, Cipher is ECDHE-ECDSA-AES256-GCM-SHA384
Server public key is 384 bit
Secure Renegotiation IS supported
Compression: NONE
Expansion: NONE
No ALPN negotiated
SSL-Session:
    Protocol  : TLSv1.2
    Cipher    : ECDHE-ECDSA-AES256-GCM-SHA384
    Session-ID: BEB08D2981D98F3585A5346FD78784EAD41591ED45EDC674D7C3E11CE8120090
    Session-ID-ctx:
    Master-Key: 3D0037DAD6CBAB31322FC682926F323C51100D33EB53E427F3E8CEC64D4128DA34AED1C694C4ED1CE0811D3B2D499EF7
    Key-Arg   : None
    PSK identity: None
    PSK identity hint: None
    SRP username: None
    TLS session ticket lifetime hint: 300 (seconds)
    TLS session ticket:
    0000 - 8e 6f 0f 0f 24 e0 71 94-04 84 ff 6d 69 f9 11 0e   .o..$.q....mi...
    0010 - cf df 66 55 70 85 30 bb-17 35 74 22 c3 ca 75 45   ..fUp.0..5t"..uE
    0020 - d2 83 db b5 3c 80 e1 43-ab 2b 52 83 9b 92 4f ac   ....<..C.+R...O.
    0030 - 70 1a 4b c5 f5 73 0e cd-07 2b d6 e8 c0 28 5f 58   p.K..s...+...(_X
    0040 - 79 2f 4f 47 42 38 fa 5a-9d 4e 3c 93 9b 34 fc cb   y/OGB8.Z.N<..4..
    0050 - 7b 8f cc af 07 c2 88 f3-32 54 46 39 ad 05 2d 0a   {.......2TF9..-.
    0060 - 70 0c fe 0d 8a b2 27 76-13 3e 08 c4 5c a1 c2 64   p.....'v.>..\..d
    0070 - cf 28 db e8 0c 85 41 ef-6b d2 4e 59 dc 22 51 30   .(....A.k.NY."Q0
    0080 - b4 7d e5 51 fd 99 46 6e-c8 53 e0 62 2b 18 03 dd   .}.Q..Fn.S.b+...
    0090 - d5 9c 84 1e b3 9d 4e 72-c9 d7 04 e8 c1 5e a0 90   ......Nr.....^..
    00a0 - e9 20 d9 26 91 34 28 17-b5 19 f3 29 46 81 3d 61   . .&.4(....)F.=a

    Start Time: 1538082407
    Timeout   : 7200 (sec)
    Verify return code: 0 (ok)
--- 

Now test your server with the Qualys SSL Labs scanner. Make sure it runs TLS 1.1 and 1.2, but not 1.0.

Now I'm caught up to major providers, at least in terms of what I don't support. A DigiCert report showed that many major hosting companies dropped both 1.0 and 1.1 in March through October 2018.

Ready to get started?

If you want to dive right into the details of setting up TLS 1.3, click the below button. The rest of the page provides a little more background on the advantages of the TLS 1.3 protocol.

Why Go To TLS 1.3?

Security and performance! Version 1.3 ends a long series of gradual improvements and workarounds. It has its own stronger, much smaller, cipher suite. It also significantly improves performance.

SSL/TLS Timeline
Protocol Published
SSL 1.0 never
SSL 2.0 1995
SSL 3.0 1996
TLS 1.0 1999
TLS 1.1 2006
TLS 1.2 2008
TLS 1.3 2018

SSL came out of Netscape labs. Yes, this goes back to the era when NCSA Mosaic had been the state-of-the-art web browser, and then Netscape Navigator came along! Netscape developed the SSL protocol to serve as a secure wrapper for HTTP.

Netscape never released the original SSL 1.0, it was their lab development version. SSL 2.0 was published in 1995. It had serious security problems, so SSL 3.0 was published in 1996.

CompTIA had questions about 1996 security issues in their Security+ question pool until July 2018. You had to memorize the ancient history that SSL 2.0 had a serious man-in-the-middle vulnerability and you should upgrade at least to SSL 3.0. In 2018 you had to know something that was out of date in 1999!

TLS 1.0 was published in 1999. It served the same purpose as SSL 3.0, and you might consider it as an upgraded SSL 3.0. However, the negotiation logic was significantly different, SSL and TLS were not intercompatible.

TLS 1.1 appeared in 2006, fixing some cryptographic weaknesses. It still used the block ciphers DES, 3DES, and IDEA, plus the stream cipher RC4.

TLS 1.2 appeared in 2008. It significantly upgraded the cryptography. The MD5 hash function was dropped and SHA-256 added. The AES block cipher was added, and Galois/Counter Mode and CCM (Counter with CBC-MAC) Mode were added, providing authenticated encryption. However, weaker hash and cipher algorithms were still included, making for a large cryptographic suite.

No new protocol came out for 10 years. That doesn't mean that everything was quiet and safe. Many prominent vulnerabilities appeared, including various renegotiation attacks in which one end is misled into downgrading security. Also, researchers found weaknesses in older algorithms like SHA-1 and RC4.

There were numerous workarounds for the vulnerabilities and cryptographic weaknesses.

After a long 10 years, TLS 1.3 was finalized in August 2018. It simply prohibits older and weaker ciphers and hash functions, along with negotiation logic that had led to problems in past versions. It added stronger cryptography: the ChaCha20 stream cipher used along with the Poly1305 message authentication code, the Ed25519 and Ed448 elliptic-curve digital signature algorithms, and the x25519 and x448 key exchange protocols.

The cryptographic suite is much simpler, making TLS 1.3 easier to implement and to configure. This is important — When something is overly complicated, confusion can lead to configurations that appear to work but have security holes. Simpler is better, as long as it's built from strong pieces.

Performance Also Improves

TLS 1.3 greatly simplifies the handshake that sets up the connection. Up through TLS 1.2, there were two round trips of messages, with a lot of information leakage. In 1.3, there's just a single round trip with most information encrypted.

Browser Support for TLS 1.3

The shared libraries hold us back. The Google Chrome browser added support with Chrome 65, but it can't do TLS 1.3 on its own. It needs help from a shared library.

For example, let's look at the Chrome browser on Mint. My laptop had Linux Mint 18.3. The chromium-browser program appears to be in /usr/bin/. However, that is the launcher, a shell script. The actual binary is in /usr/lib/chromium-browser/. Let's see what cryptographic library it uses, and what version it is.

$ ldd /usr/lib/chromium-browser/chromium-browser | egrep 'ssl|tls'
        libgnutls.so.30 => /usr/lib/x86_64-linux-gnu/libgnutls.so.30 (0x00007fdf47e1c000)
$ dpkg -l | grep gnutls
ii  libcurl3-gnutls:amd64      7.47.0-1ubuntu2.9   amd64   easy-to-use client-side URL transfer library (GnuTLS flavour)
ii  libgnutls-openssl27:amd64  3.4.10-4ubuntu1.4   amd64   GNU TLS library - OpenSSL wrapper
ii  libgnutls30:amd64          3.4.10-4ubuntu1.4   amd64   GNU TLS library - main runtime library
ii  libneon27-gnutls:amd64     0.30.1-3build1      amd64   HTTP and WebDAV client library (GnuTLS enabled)

There's the problem. Version 3.6.3 of the GnuTLS library supports TLS 1.3. But version 3.4.10, which is what my system had, does not.

I soon upgraded my laptop to Linux Mint 19, the latest version of that distribution at the time. It has newer packages, but still not the latest.

$ ldd /usr/lib/chromium-browser/chromium-browser | egrep 'ssl|tls'
        libgnutls.so.30 => /usr/lib/x86_64-linux-gnu/libgnutls.so.30 (0x00007fa0d6f3d000)
$ dpkg -l | grep gnutls
ii  libcurl3-gnutls:amd64     7.58.0-2ubuntu3.3     amd64     easy-to-use client-side URL transfer library (GnuTLS flavour)
ii  libgnutls30:amd64         3.5.18-1ubuntu1       amd64     GNU TLS library - main runtime library
ii  libneon27-gnutls:amd64    0.30.2-2build1        amd64     HTTP and WebDAV client library (GnuTLS enabled)

On my desktop with Mageia Linux 6, the latest Mageia at the time, I was similarly close but not quite there.

$ ldd /usr/lib64/chromium-browser/chrome | egrep -i 'ssl|tls'
        libgnutls.so.30 => /lib64/libgnutls.so.30 (0x00007f03a62d8000)
        libssl.so.1.0.0 => /lib64/libssl.so.1.0.0 (0x00007f039e450000)
$ rpm -qf /lib64/libgnutls.so.30 /lib64/libssl.so.1.0.0
lib64gnutls30-3.5.13-1.mga6
lib64openssl1.0.0-1.0.2p-1.mga6 

It's time to get started!

If you made it this far, you're the cautious type who likes plenty of background. But by now you should be convinced that TLS 1.3 has both security and performance advantages. Let's make it happen!