Hi all, We ran into an interesting interaction between 0-RTT in TLS 1.3 and TCP resets that I wanted to pass along for general awareness. If you recall an earlier message, https://mailarchive.ietf.org/arch/msg/tls/hymweZ66b2C8nnYyXF8cwj7qopc/, this is a similar interaction to those.
Recall that the 0-RTT flow in TLS 1.3 looks like this: ClientHello (Application Data*) ----> ServerHello ... {Finished} <---- [Application Data*] (EndOfEarlyData) {Finished} ----> [Application Data] <---> [Application Data] Now, there’s a lot of hiding behind this picture, because it doesn’t capture some things that happen in parallel, and the different orders that each side reads. Some points of note: - We draw this as if the client sends one record of early data, but in reality early data is streamed. The client might not have early data to send immediately, or the client may continuously send early data until EndOfEarlyData and then immediately transition to sending 1-RTT data. - When the server reads ClientHello, before it reads any early data, the server sends ServerHello..Finished. Then the server starts consuming early data and sending half-RTT data as is appropriate in the protocol. This too is streamed. - When the client reads ServerHello..Finished, the client transitions to 1-RTT data and sends EndOfEarlyData..Finished. It does this whether or not it would otherwise have something to send in the application protocol. These come from the following constraints: - TLS needs to work for arbitrary stream-based application protocols, and the application protocol may have data flow arbitrarily. The WG designed early data to work with that, rather than only working for an atomic chunk of data. - The TLS client needs to send EndOfEarlyData..Finished immediately because we allow the server to wait for handshake completion to respond. See option 2 in https://www.rfc-editor.org/rfc/rfc8470.html#section-3. If the TLS client waited for the application protocol to have more to send, it would deadlock RFC 8470 servers. This leads to an interesting interaction with TCP resets and more simplistic application protocols. Suppose you have a very, very simple protocol that sends a single request and response pair. (E.g. HTTP/1.x without connection reuse.) 1. Client sends request 2. Server reads request 3. Server sends response 4. Server closes connection 5. Client reads response 6. Client closes connection If you naively thread this into 0-RTT, you would get the following sequence: 1. Client sends ClientHello 2. Client sends request 3. Server reads ClientHello, sends ServerHello..Finished 4. The next two I/O flows happen in parallel (with synchronization between 5b and 6c) 5. On the server: 1. Server reads request 2. Server sends response 3. Server closes connection 6. On the client: 1. Client reads ServerHello..Finished 2. Client sends EndOfEarlyData..Finished 3. Client reads response 4. Client closes connection Now, TCP resets connections if it receives data and the connection is closed, because it considers this condition an application protocol error. This is exactly what happens here: the server never reads EndOfEarlyData..Finished because it closes the connection first. Once the server has both received EndOfEarlyData..Finished and closed the connection, the server will reset the TCP connection. A reset connection will not reliably deliver data, so the client will not get the response in a few of scenarios: - If part of the response was lost, the server won't retransmit it after the reset - If the client gets the reset packet before it gets the whole response, the response won't come in - Some OSes will dump receive buffers on reset too, so the client may lose the response if it's received it but just hadn't returned it to the application yet HTTPS has grown far from its single request/response days, and even HTTP/1.1 connection reuse would avoid this situation. Still, this can happen in automated tests where one often runs against simplistic servers. But TLS is also more than HTTPS, so other application protocols that follow this pattern can be impacted when using 0-RTT. With NewSessionTicket, we found that deferring to the first application data write was a good default for other TCP issues ( https://mailarchive.ietf.org/arch/msg/tls/hymweZ66b2C8nnYyXF8cwj7qopc/). However, the client TLS stack cannot defer EndOfEarlyData..Finished or it will deadlock RFC 8470 servers that wait before processing. So I think the cleanest way out of this is to say that even if the server has nothing more to read or write on the connection at the application level, the server MUST still drive the handshake to completion in full or the response may not go through. This is a bit awkward as the server’s application stack may not be aware of this case and know to keep a socket alive, drive it to completion, give up on it under load, etc. But 0-RTT is opt-in and such simplistic protocols are rare outside tests, so this is hopefully not too onerous. Still, this is not obvious and tricky to diagnose, so worth noting somewhere. David
_______________________________________________ TLS mailing list -- tls@ietf.org To unsubscribe send an email to tls-le...@ietf.org