The CloudFlare London office hosts weekly internal Tech Talks (with free lunch picked by the speaker). My recent one was an explanation of the latest version of TLS, 1.3, how it works and why it's faster and safer.
You can watch the complete talk below or just read my summarized transcript.
Update: you might want to watch my more recent and extended 33c3 talk instead.
The Q&A session is open! Send us your questions about TLS 1.3 at [email protected] or leave them in the Disqus comments below and I'll answer them in an upcoming blog post.
To understand why TLS 1.3 is awesome, we need to take a step back and look at how TLS 1.2 works. In particular we will look at modern TLS 1.2, the kind that a recent browser would use when connecting to the CloudFlare edge.
The client starts by sending a message called the
ClientHello that essentially says "hey, I want to speak TLS 1.2, with one of these cipher suites".
The server receives that and answers with a
ServerHello that says "sure, let's speak TLS 1.2, and I pick this cipher suite".
Along with that the server sends its key share. The specifics of this key share change based on what cipher suite was selected. When using ECDHE, key shares are mixed with the Elliptic Curve Diffie Hellman algorithm.
The important part to understand is that for the client and server to agree on a cryptographic key, they need to receive each other's portion, or share.
Finally, the server sends the website certificate (signed by the CA) and a signature on portions of
ServerHello, including the key share, so that the client knows that those are authentic.
The client receives all that, and then generates its own key share, mixes it with the server key share, and thus generates the encryption keys for the session.
Finally, the client sends the server its key share, enables encryption and sends a
Finished message (which is a hash of a transcript of what happened so far). The server does the same: it mixes the key shares to get the key and sends its own
At that point we are done, and we can finally send useful data encrypted on the connection.
Notice that this takes two round-trips between the client and the server before the HTTP request can be transferred. And round-trips on the Internet can be slow.
Enter TLS 1.3. While TLS 1.0, 1.1 and 1.2 are not that different, 1.3 is a big jump.
Most importantly, establishing a TLS 1.3 connection takes one less round-trip.
In TLS 1.3 a client starts by sending not only the
ClientHello and the list of supported ciphers, but it also makes a guess as to which key agreement algorithm the server will choose, and sends a key share for that.
(Note: the video calls the key agreement algorithm "cipher suite". In the meantime the specification has been changed to disjoin supported cipher suites like AES-GCM-SHA256 and supported key agreements like ECDHE P-256.)
And that saves us a round trip, because as soon as the server selects the cipher suite and key agreement algorithm, it's ready to generate the key, as it already has the client key share. So it can switch to encrypted packets one whole round-trip in advance.
So the server sends the
ServerHello, its key share, the certificate (now encrypted, since it has a key!), and already the
The client receives all that, generates the keys using the key share, checks the certificate and
Finished, and it's immediately ready to send the HTTP request, after only one round-trip. Which can be hundreds of milliseconds.
One existing way to speed up TLS connections is called resumption. It's what happens when the client has connected to that server before, and uses what they remember from the last time to cut short the handshake.
How this worked in TLS 1.2 is that servers would send the client either a Session ID or a Session Ticket. The former is just a reference number that the server can trace back to a session, while the latter is an encrypted serialized session which allows the server not to keep state.
The next time the client would connect, it would send the Session ID or Ticket in the
ClientHello, and the server would go like "hey, I know you, we have agreed on a key already", skip the whole key shares dance, and jump straight to
Finished, saving a round-trip.
So, we have a way to do 1-RTT connections in 1.2 if the client has connected before, which is very common. Then what does 1.3 gain us? When resumption is available, 1.3 allows us to do 0-RTT connections, again saving one round trip and ending up with no round trip at all.
If you have connected to a 1.3 server before you can immediately start sending encrypted data, like an HTTP request, without any round-trip at all, making TLS essentially zero overhead.
When a 1.3 client connects to a 1.3 server they agree on a resumption key (or PSK, pre-shared key), and the server gives the client a Session Ticket that will help it remember it. The Ticket can be an encrypted copy of the PSK—to avoid state—or a reference number.
The next time the client connects, it sends the Session Ticket in the
ClientHello and then immediately, without waiting for any round trip, sends the HTTP request encrypted with the PSK. The server figures out the PSK from the Session Ticket and uses that to decrypt the 0-RTT data.
The client also sends a key share, so that client and server can switch to a new fresh key for the actual HTTP response and the rest of the connection.
0-RTT comes with a couple of caveats.
Since the PSK is not agreed upon with a fresh round of Diffie Hellman, it does not provide Forward Secrecy against a compromise of the Session Ticket key. That is, if in a year an attacker somehow obtains the Session Ticket key, it can decrypt the Session Ticket, obtain the PSK and decrypt the 0-RTT data the client sent (but not the rest of the connection).
This is why it's important to rotate often and not persist Session Ticket keys (CloudFlare rotates these keys hourly).
TLS 1.2 has never provided any Forward Secrecy against a compromise of the Session Ticket key at all, so even with 0-RTT 1.3 is an improvement upon 1.2.
More problematic are replay attacks.
Since with Session Tickets servers are stateless, they have no way to know if a packet of 0-RTT data was already sent before.
Imagine that the 0-RTT data a client sent is not an HTTP GET ("hey, send me this page") but instead an HTTP POST executing a transaction like "hey, send Filippo 50$". If I'm in the middle I can intercept that
ClientHello+0-RTT packet, and then re-send it to the server 100 times. No need to know any key. I now have 5000$.
Every time the server will see a Session Ticket, unwrap it to find the PSK, use the PSK to decrypt the 0-RTT data and find the HTTP POST inside, with no way to know something is fishy.
The solution is that servers must not execute operations that are not idempotent received in 0-RTT data. Instead in those cases they should force the client to perform a full 1-RTT handshake. That protects from replay since each
ServerHello come with a Random value and connections have sequence numbers, so there's no way to replay recorded traffic verbatim.
Thankfully, most times the first request a client sends is not a state-changing transaction, but something idempotent like a GET.
TLS 1.3 is not only good for cutting a round-trip. It's also better, more robust crypto all around.
Most importantly, many things were removed. 1.3 marked a shift in the design approach: it used to be the case that the TLS committee would accept any proposal that made sense, and implementations like OpenSSL would add support for it. Think for example Heartbeats, the rarely used feature that cause Heartbleed.
In 1.3, everything was scrutinized for being really necessary and secure, and scrapped otherwise. A lot of things are gone:
- the old static RSA handshake without Diffie Hellman, which doesn't offer Forward Secrecy
- the CBC MAC-then-Encrypt modes, which were responsible for Vaudenay, Lucky13, POODLE, LuckyMinus20... replaced by AEADs
- weak primitives like RC4, SHA1, MD5
- custom FFDHE groups
- RSA PKCS#1v1.5
- explicit nonces
We'll go over these in more detail in future blog posts.
Some of these were not necessarily broken by design, but they were dangerous, hard to implement correctly and easy to get wrong. The new excellent trend of TLS 1.3 and cryptography in general is to make mistakes less likely at the design stage, since humans are not perfect.
A new version of a protocol obviously can't dictate how older implementations behave and 1.3 can't improve the security of 1.2 systems. So how do you make sure that if tomorrow TLS 1.2 is completely broken, a client and server that both support 1.2 and 1.3 can't be tricked into using 1.2 by a proxy?
A MitM could change the
ClientHello to say "I want to talk at most TLS 1.2", and then use whichever attack it discovered to make the 1.2 connection succeed even if it tampered with a piece of the handshake.
1.3 has a clever solution to this: if a 1.3 server has to use 1.2 because it looks like the client doesn't support 1.3, it will "hide a message" in the Server Random value. A real 1.2 will completely ignore it, but a client that supports 1.3 would know to look for it, and would discover that it's being tricked into downgrading to 1.2.
The Server Random is signed with the certificate in 1.2, so it's impossible to fake even if pieces of 1.2 are broken. This is very important because it will allow us to keep supporting 1.2 in the future even if it's found to be weaker, unlike we had to do with SSLv3 and POODLE. With 1.3 we will know for sure that clients that can do any better are not being put at risk, allowing us to make sure the Internet is for Everyone.
So this is TLS 1.3. Meant to be a solid, safe, robust, simple, essential foundation for Internet encryption for the years to come. And it's faster, so that no one will have performance reasons not to implement it.
TLS 1.3 is still a draft and it might change before being finalized, but at CloudFlare we are actively developing a 1.3 stack compatible with current experimental browsers, so everyone can get it today.
The TLS 1.3 spec is on GitHub, so anyone can contribute. Just while making the slides for this presentation I noticed I was having a hard time understanding a system because a diagram was missing some details, so I submitted a PR to fix it. How easy is that!?
Like any talk, at the end there's the Q&A. Send your questions to [email protected] or leave them in the Disqus comments below and I'll answer them in an upcoming blog post!