Skip to content

Comment on Implementing a toy version of TLS 1.3

Comments

This is wonderful.

I was super excited to dive in an find the RSA code so I could preen about Bleichenbacher's vulnerability, but she neatly sidestepped that by doing ECDH. Then I thought, well, maybe it's P-curve ECDH and I can preen about invalid curve attacks on static-ephemeral ECDH. But nope, X25519! My point here, apart from making fun of myself for being the kind of person who would write this stuff on a message board, is TLS 1.3 is pretty solid.

The "block thing" that's kind of weird is, I assume, the TLS Record Layer. TLS runs (ordinarily) over TCP, which provides a non-demarcated stream of bytes. TLS breaks that stream up into records, and runs its handshake messages over one type of record, (say) HTTPS over another, and "alerts" over a third. The Record Layer also interacts, I think, with TLS's misbegotten compression system?

In the same vein as this project (but with different goals) is Trevor Perrin's tlslite, which is implemented in pure Python: https://github.com/trevp/tlslite

Seems like you ought to know TLS 1.3 famously doesn't have a way to do RSA key exchange. There's a fun video where some presumably American EDCO people are like, "surely if we all turn up in person we've got more votes so we can get RSA back into TLS 1.3" and er, no, the IETF isn't a democracy.

Looking at GCM or any other authenticated encryption without Sum Types makes me sad. If decrypting the message fails, there is no plaintext and I feel like an API should be able to actually express that properly not return a plaintext [even if it's empty] and a flag saying "Don't use the plaintext". :(

The "block thing" is a necessary choice if we're going to have authenticated encryption, because authenticating costs us overhead. If we wanted to reliably authenticate individual bytes (so that we can have what naively feels like a stream of bytes) we'd be paying that overhead for each byte. That's pretty outrageous. On the other hand if I'm downloading a 10MB file with my 24kbps modem and none of that was authenticated yet because the authentication only happens once at the end of the encrypted data, I'm going to get very anxious about my download counter correctly showing 0% successfully downloaded for so long. So TLS authenticates whole records.

Yes, the point was how much better 1.3 is than what preceded it.

You don't need the TLS Record Layer in order to do authenticated encryption.

Agreed. Basically all you need is:

1. Something to delimit the boundaries of the message. 2. Some way to determine the nonce.

OTOH, the TLS 1.3 record layer is barely more than authenticated encryption,

I know that tptacek knows this, but for those who don't, we took data compression out of TLS 1.3!

tlslite

"PEP 543 – A Unified TLS API for Python" #interfaces (-2016) https://peps.python.org/pep-0543/#interfaces

Yep, when digging through the TLS 1.3 RFC (after learning 1.2) the thing that jumped out at me is how many _fewer_ options there are, and how deliberately chosen they seemed to be.

- suites (as far as covering cipher + exchange + mac) are gone as a concept

- only 5 supported cipher algorithms (all AEAD, and all but one built on AES)

- only non-PSK key exchange is (EC)DHE

- no compression supported

- all the various key derivations are built around a single new primitive function, HKDF

I'm sure that given enough time the above options will balloon like we had with latter-day TLS 1.2 but at this point it feels so clean.

The Cloudflare blog entries on TLS 1.3 have some commentary (and link to more commentary from other sources) about this topic. Besides just getting rid of legacy cruft, simplifying the protocol helps limit downgrade attacks, reduces maintenance burden, and limits the observable behavior that various implementations might end up inadvertently depending on.

EDIT: https://www.imperialviolet.org/2016/05/16/agility.html is a good post about this.

I'm sure the options will balloon a bit. After all, if someone develops a new good cipher/etc, we'll want to use it in TLS. But it's nice that as of now, there's no bad choices available (other than potentially allowing downgrading).

It's entirely possible we will never need to replace AES (or indeed ChaCha20). AES 128 GCM is mandatory to implement, and implementers are recommended to offer ChaCha20 too. If you don't have AES hardware, ChaCha20 looks very attractive and you'd be a fool not to.

So while you can get code points for a vanity cipher, there not only isn't an appetite from the working group to endorse such ciphers, there is unlikely to ever be a scenario where a client or server would actually pick it unless you control both and choose to prefer the vanity cipher. Whereupon why bother?

The downgrade prevention in TLS 1.3 is pretty effective, so an attack which needs a downgrade also needs to defeat the downgrade protection, an additional hurdle. Obviously people should get rid of TLS 1.0 and TLS 1.1 but meanwhile I think the anti-downgrade story is the best it has ever been.

We may never need to replace AES 256, but Grover's quantum search algorithm can break AES 128 with a work factor of 2^64. AES 256 GCM and a post-quantum authenticated key agreement algorithm may be all we ever need.

While it's true what you say about Grover's algorithm, I remain sceptical that this can actually be made practical. And if it does become practical as you point out the post-quantum kex is the big must-have anyway. As this toy implementation shows, TLS has at least four symmetric keys for each session, whereas you'd only need to break one kex to get them all.

So I'm comfortable with my claim as it stood, we might never even need AES 256 in practice (but like ChaCha20 it's a SHOULD in the RFC for TLS 1.3).

Everyone likes to bikeshed, but I'm a bit disappointed that TLS 1.3 kept the ability to use pre-shared-keys without (EC)DHE.

I get that the intended use case is not paying the overhead of asymmetric crypto for embedded applications. However, that use case is already covered by something like WolfSSL stripped down to only support TLS 1.2 PSK, and they can still even use modern AEAD cihper suites like AES 256 GCM in TLS 1.2.

All of the TLS 1.3 key agreement mechanisms should have forward secrecy, even when pre-shared-keys are used. In some applications, there's too much temptation to get rid of that safety net for efficiency. We shouldn't clutter up the TLS 1.3 spec and implementations to cover that use case. Furthermore, engineers who go that route should have to explain to their boss solid engineering reasons why they're staying back at TLS 1.2. There are a small number of valid use cases for symmetric-only key exchange, but there are way more use cases where an engineer just thinks the reduced overhead is worth the risk, and their manager sees the TLS 1.3 box checked and thinks they're fine.

We can't force people to do security. There definitely are (hopefully not publicly exposed) TLS 1.3 server implementations which always pick the same private key to do ECDHE, even though this means they lose forward secrecy - because they did not want actual security and we can't make them.

i guess TLS needs records because it wants to mix signalling data with the data stream (stuff like alerts, keyupdate, renegotiation for tls1.2). but i guess also they want to use block ciphers and for stream ciphers they want some kind of authentication so i'm not sure how you would do this without some form of framing. there is also some more strangeness with the TLS record layer because handshake messages and potentially other messages are allowed to be fragmented over multiple TLS records or you can have multiple handshake messages in the same TLS record (i think there are some restrictions on TLS1.3 about mixing different message types). this also might be a way to mess with TLS censorship middle boxes because they might not be robustly coded. i have a plugin for mitmproxy that does TLS interception using knowledge of the shared key but it doesn't handle fragmentation of handshake messages and a bunch of other quirks correctly.

The primary reason you need records is that AEAD algorithms run on blocks so you need to know where the end of one block is and the start of the next. And because you are running over TCP, you don't get that framing information from TLS.

As you say, it's also the case that you have different types of data and so the record type indicates what you are processing, but you'd need records even if you didn't do that.

AboutSource Built by g1lg1l

Hackerly is an independent reader for Hacker News, built on the public HN API. Not affiliated with Y Combinator.