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

Reply via email to