Subject: [TLS] The case for a single stream of data

From: Colm MacCárthaigh <[email-masked]>
Date: Fri, 5 May 2017 09:28:07 -0700

I wanted to start a separate thread on this, just to make some small
aspects of replay mitigating clear, because I'd like to make a case for TLS
providing a single-stream, which is what people seem to be doing anyway.

Let's look at the DKG attack. There are two forms of the attack, one is as
follows:

"Client sends a request with a 0-RTT section. The attacker lets the server
receive it, but suppresses the server responses, so the client downgrades
and retries as a 1-RTT request over a new connection. Repeating the
request".

In this case, server-side signaling such as the X-header trick doesn't work
at all. But thankfully this attack is equivalent to an ordinary socket
interference attack. E.g. if an attacker today suppressed a server response
to a HTTP request, then the client will do its retry logic. It's the same,
and I think everything is compatible with today's behavior.

Next is the more interesting form of the attack:

"Client sends a request with a 0-RTT section. For some reason the server
can't reach the strike register or single use cache, and falls back to
1-RTT. Server accepts the request over 1-RTT.  Then a short time later, the
attacker replays the original 0-RTT section."

In this case, server side signaling to the application (such as the neat X-
header trick) also doesn't work, and is not backwards compatible or secure
by default. It doesn't work because the server application can't be made
idempotent from "outside" the application, so any signaling is
insufficient, and is equivalent to the Exactly-Once message delivery
problem in distributed systems. Since a request might be retried as in case
1, it needs an application-level idempotency key, or a delay-and-retry
strategy (but replay will break this). There's some detail on all this in
the review. End result is that a server-side application that was never
designed to reach duplicates may suddenly be getting exactly one duplicate
(that's all the attack allows, if servers reject duplicate 0-RTT).

What is actually needed here, I think, is client-side signaling. Careful
clients need to be made aware of the original 0-RTT failure.

So for example, an SDK that writes to an eventually consistent data store
may treat any 0-RTT failure as a hard failure, and *not* proceed to sending
the request over 1-RTT. Instead it might wait its retry period, do a poll,
and only then retry the request. If the TLS implementation signals the
original  0-RTT failure to the client, as if it were a connection error,
everything is backwards compatible again. Well mostly; to be properly
defensive, the client's retry time or polling interval needs to be greater
than the potential replay window, because only then can it reason about
whether the original request succeeded or not. If there is a strict maximum
replay window, then this behavior is enforceable in a TLS implementation:
by delaying the original failure notification to the client application by
that amount.

Of course browsers won't do this, and that's ok. Browsers have decided that
aggressive retries is best for their application space. But careful clients
/need/ this; and it's not just about backwards compatibility. It is a
fundamental first-principles requirement for something that uses an
eventually consistent data store. We can say don't use 0-RTT, but that's
not practical, for reasons also in the review.

So if we want to fully mitigate DKG attacks, I think it is useful to hard
cap the replay, say that it MUST be at most 10 seconds. And then worst
case, a client that needs to be careful can wait 10 seconds. Note that the
TLS implementation can do this on the client's behalf, by inserting a
delay. Of course that means that for these kinds of applications, this
means that 0-RTT delivers speed most of the time, but may occasionally slow
things down by 10 seconds. I think that's an ok trade-off to make for
backwards compatibility.

But it also has implications for middle-boxes: a TLS reverse proxy needs to
either not use 0-RTT on the "backend" side, or it needs to use it in very
careful way; accepting 0-RTT from the original client, only if the backend
also accepts a 0-RTT section from the proxy. This is to avoid the case
where the client can't reason about the potential for a replay between the
proxy and the backend. It's doable, but gnarly, and slows 0-RTT acceptance
down to the round trip between the client and the backend, via the proxy.

That's one reason why the review suggests something else too:  just lock
careful applications out, but in a mechanistic way rather than a "good
intentions" way, by having TLS implementations *intentionally* duplicate
0-RTT sections.

O.k. so all of that the above might be a bit hairy: but I want to take away
from it at this stage is that splitting the early_data and application_data
at application level isn't particularly helpful; the server-side can't
really use this information anyway, because of the Exactly-Once problem.
Client side signaling does help though, and a simple safe-by-default
mechanism there is to behave as if the connection has failed, but after
writing the first section of data. E.g. in s2n this would be ...

conn = s2n_connect(); // Client makes a connection
r = s2n_write(conn, "GET / HTTP/1.1 ... "); // Client writes some data, we
stuff it in the 0-RTT and send it. This write succeeds. From the client's
perspective, it may or may not have been received; that's normal.

/* At this point the 0-RTT data is rejected by the server, and so it might
be replayable  ... iff the server side strike-register or
   cache had a problem.

   A pedantically correct TLS library might then pause here for 10 seconds,
or if it's non-blocking, then set a timer so that nothing can happen on
conn for the next 10 seconds. Browsers could turn this behavior off, since
they retry aggressively anyway. But it's a secure default that is backwards
compatible.
*/

r = s2n_read()/s2n_write()/s2n_shutdown();  // At this point, s2n returns
failure. It's as if the connection failed. The client can implement its
retry strategy, if any, safely; the request won't be replayed at this
point.

r = s2n_connect(); // Client makes a new connection for a retry.

A slightly higher level API is probably more realistic, because there's a
potential to optimize for connection re-use. There's really no need to tear
down the whole connection and start-over. It's safe to proceed to 1-RTT if
the delay has expired. A higher level API would fix that, but this is just
the "safe by default" API I'm outlining.  Again, all I want to take away is
that all of this is doable safely with a single stream.

-- 
Colm

---

From: Ilari Liusvaara <[email-masked]>
Date: Sat, 6 May 2017 12:58:35 +0300

> providing a single-stream, which is what people seem to be doing anyway.

<Snip a long mail>

Couple points:

- AFAIK, the 10 second or so figure in existing spec is a slop margin,
  which means the replay window would be 20 seconds, not 10.

- The size of the window depends on clock error margin and transport
  delay margin. At 1s/day clock error margin, you need 14 second
  window just from clock error.

- It is not just low-power devices with really bad clocks. I have seen
  20s per day(!) clock drift on high-power device that doesn't sleep.

- So basically, the size of window is tradeoff with number of devices.

- That automatic wait on 0-RTT failure seems just the kind of feature
  that gets disabled. Furthermore, 10 second idle on connection is
  going to trigger quite a bit of connection timeouts.

- There seems to be no consideration how this interacts with 0-RTT
  exporters (probably applications that accept 0-RTT will then use
  0-RTT exporters for the entiere connection, and those exporters have
  seriously weaker properties).

-Ilari

---

From: "Salz, Rich" <[email-masked]>
Date: Sat, 6 May 2017 12:22:27 +0000

What about when *part* of a request is in the 0RTT part, and the rest of it isn’t?  I believe this will happen often for H2 initial setup.  Imagine the “fun” when initial connection data, such as login cookies, is replayed in other contexts and eventually decrypted?

--
Senior Architect, Akamai Technologies
Member, OpenSSL Dev Team
IM: [email-masked] Twitter: RichSalz

---

From: Kyle Rose <[email-masked]>
Date: Sat, 6 May 2017 09:43:55 -0400

On Sat, May 6, 2017 at 8:22 AM, Salz, Rich <[email-masked]> wrote:

>
> What about when **part** of a request is in the 0RTT part, and the rest
> of it isn’t?  I believe this will happen often for H2 initial setup.
> Imagine the “fun” when initial connection data, such as login cookies, is
> replayed in other contexts and eventually decrypted?
>

I asked this question a while back, and didn't get a satisfying answer: if
an on-path attacker replaces the early data with a replay from an earlier
connection, does the server eventually figure this out once the handshake
is complete, or is this mix-and-match impossible for the server to detect?
It would be nice if a security property of early data is that a replay
attack is eventually detected, because at least then you'll know you're
under attack.

Kyle

---

From: Ilari Liusvaara <[email-masked]>
Date: Sat, 6 May 2017 18:12:08 +0300

> attack is eventually detected, because at least then you'll know you're

Trying to replace the early data leads to fatal handshake error if the
server accepts 0-RTT (since actual deprotection failure from 0-RTT data
is fatal). If server rejects, then the substitution is silently ignored.

-Ilari

---

From: Kyle Rose <[email-masked]>
Date: Sat, 6 May 2017 19:54:41 -0400

> is fatal). If server rejects, then the substitution is silently ignored.

I'm not sure this completely answers my question, so let me propose where I
think protection lies.

If the on-path attacker replaces only the early data bytes, deprotection of
early data will fail since the early traffic secret incorporates the
ClientHello in its derivation, which includes a (presumably) fresh client
random.

If the on-path attacker replaces the entire first flight (or at least
ClientHello and the early data), the early data may be accepted but the
subsequent handshake will fail because the client and server will derive
different handshake traffic keys.

If this is accurate, then replays of partial requests don't really pose a
problem (at least for HTTP) because the remainder of the request will fail
deprotection and so the request won't actually be delivered to the
application.

Kyle

---

From: Eric Rescorla <[email-masked]>
Date: Sat, 6 May 2017 17:55:19 -0700

On Sat, May 6, 2017 at 4:54 PM, Kyle Rose <[email-masked]> wrote:

> On Sat, May 6, 2017 at 11:12 AM, Ilari Liusvaara <[email-masked]
> > wrote:
>
> I'm not sure this completely answers my question, so let me propose where
> I think protection lies.
>
> If the on-path attacker replaces only the early data bytes, deprotection
> of early data will fail since the early traffic secret incorporates the

Correct.

> different handshake traffic keys.

Correct.

-Ekr

> _______________________________________________
> TLS mailing list
> [email-masked]
> https://www.ietf.org/mailman/listinfo/tls
>
>

---

From: Benjamin Kaduk <[email-masked]>
Date: Mon, 8 May 2017 21:33:27 -0500

>   which means the replay window would be 20 seconds, not 10.

As the author of that text, I mostly intended it to be the total window,
not the slop margin.  But the main idea was to give an order of
magnitude, i.e., "shorter than minutes".

>   window just from clock error.

Or you can accept full handshakes every couple hours.  Amortized that
may not be bad, if you have lots of connections.  Or you may only have
one connection every day ever, in which case it is noticeable.  It's
pretty situation-dependent.

>   20s per day(!) clock drift on high-power device that doesn't sleep.

Is there a problem with saying that devices with bad clocks talking to
beefy web servers don't get to do 0-RTT?  I don't see a problem with it.

> - So basically, the size of window is tradeoff with number of devices.

Yes.

>   going to trigger quite a bit of connection timeouts.

I could believe that people would accept buffering data until the 1-RTT
handshake finishes (combined with rate limiting on the number of
connections with accepted 0-RTT data); I don't think people would accept
"wait the full clock skew allowance", though.

>   seriously weaker properties).

Yeah, the 0-RTT exporter feels like a footgun waiting to be used.

-Ben

---

From: Ilari Liusvaara <[email-masked]>
Date: Tue, 9 May 2017 07:45:15 +0300

> beefy web servers don't get to do 0-RTT?  I don't see a problem with it.

Also, any device that has access to semi-accurate time (relative time
in protocols has other benefits, like not having increase magnitude with
time) could do first-order compensation, which already renders the clocks
pretty accurate, even if time comparision occurs relatively rarely.

> "wait the full clock skew allowance", though.

Did I misread the thread-starter proposal for waiting the allowance?

I think the early data provisioning already has 0-RTT buffer size, for
the case the server buffers the 0-RTT data (this buffering obviously
destroys the utility, but if it doesn't occur on every connection...)

> Yeah, the 0-RTT exporter feels like a footgun waiting to be used.

Unfortunately, looks like some are planning to use it, in ways
seriously broken unless the server does full replay-caching.

-Ilari

---

From: Benjamin Kaduk <[email-masked]>
Date: Tue, 9 May 2017 00:08:05 -0500

> Did I misread the thread-starter proposal for waiting the allowance?

No, I think you read it correctly (and I agree with you).
I have a more detailed reply to the original message in a compose
window, but it will not be done tonight.

> seriously broken unless the server does full replay-caching.

:( :( :(

Clearly we should make sure their documents prominently note the
requirement for strong replay protection, but is there more we can do?

-Ben

---

From: "Salz, Rich" <[email-masked]>
Date: Tue, 9 May 2017 16:00:34 +0000

To me, the argument against this comes down to this:  The 0RTT data has different security properties than the post-handshake data, and TLS implementations should not hide that difference from applications.

---

From: Viktor Dukhovni <[email-masked]>
Date: Tue, 9 May 2017 12:26:01 -0400

> On May 9, 2017, at 12:00 PM, Salz, Rich <[email-masked]> wrote:
> 
> To me, the argument against this comes down to this:  The 0RTT data has
> different security properties than the post-handshake data, and TLS
> implementations should not hide that difference from applications.

What would a receiver API then look like?  Would it be something like:

  * Repeatedly request 0-RTT data until none-remains, if none expected
    or desired, fail if any found.

  * Then repeatedly request 1-RTT data via existing TLS APIs

  * Fail if 1-RTT data is requested and not all the 0-RTT data has yet
    been consumed?

Or did you have something else in mind?  What are your thoughts on the
sender side?  Just enable 0-RTT and have the toolkit send as much 0-RTT
data as "fits" and the rest 1-RTT, or an explicit request for a specific
0-RTT payload?

-- 
	Viktor.

---

From: Colm MacCárthaigh <[email-masked]>
Date: Tue, 9 May 2017 09:27:37 -0700

> implementations should not hide that difference from applications.

I absolutely agree with sentiment, but the first problem is that we've been
concentrating on the server side, where it's actually impossible.

It's actually impossible because a 0RTT request may be retried as a 1RTT
request and there's no way to correlate the two. So when the 1RTT request
shows up, we can't go "This might be a repeat" - for example the X- header
trick doesn't work in this case. It's subtle, which makes it even more
likely to trip on it.

I think the only approach that actually rigorously works is the client-side
one, that I outlined. That approach isn't very relevant to browsers, who
plan to retry aggressively anyway, but I think it's the only approach that
would work for a careful client. Interestingly, it doesn't really need
separate streams for the signaling to work. Though it'd be great to see a
formal proof in something like F*, Coq or TLA+.

The second problem is that middle-boxes can break any signaling. For
example a CDN or TLS accelerator may enable 0-RTT towards the back-end
origin without enabling it to the original client. In this model, the
client has *no* way to reason about retries or replay. That's really very
broken and a serious violation of the transport layer contract. I think we
should be more prescriptive here and say that if there are to be these
kinds of middle-boxes, then they need to honor maximum replay windows, and
they need to only enable 0-RTT from the client back. (e.g. client enabled,
but origin disabled is actually ok, but not the other way around).

In going through all of this, I'm assuming that the server at least has a
robust mitigation against 0-RTT replay, because not doing so is clearly
broken and I'm even entertaining how it can be supported. That stateless
form of mitigation is insecure and should die. If there's a notion that
server side signaling should be kept to facilitate stateless mitigation
(which permits millions of replays) ... we shouldn't have any time for
that, because no-one has a proposal for how the secrecy attacks can be
mitigated.

-- 
Colm

---

From: "Salz, Rich" <[email-masked]>
Date: Tue, 9 May 2017 16:31:39 +0000

> What would a receiver API then look like?  Would it be something like:
...
> sender side?  Just enable 0-RTT and have the toolkit send as much 0-RTT data
> as "fits" and the rest 1-RTT, or an explicit request for a specific 0-RTT payload?

As implemented in OpenSSL; see 
   https://www.openssl.org/docs/manmaster/man3/SSL_read_early_data.html
(which also describes the "write" part, and the query and control functions.)

---

From: "Salz, Rich" <[email-masked]>
Date: Tue, 9 May 2017 16:41:19 +0000

> It's actually impossible because a 0RTT request may be retried as a 1RTT request and there's no way to correlate the two. So when the 1RTT request shows up, we can't go "This might be a repeat" - for example the X- header trick doesn't work in this case. It's subtle, which makes it even more likely to trip on it. 

That also assumes that the 0RTT data will be a completely self-contained request.  I share the concern that Kyle and others have pointed out, that a single request will span the boundaries.

> I think the only approach that actually rigorously works is the client-side one

Attackers will not use well-behaved clients; does your approach still work?

>The second problem is that middle-boxes can break any signaling. For example a CDN or TLS accelerator may enable 0-RTT towards the back-end origin without enabling it to the original client. In this model, the client has *no* way to reason about retries or replay.

A CDN is not a middlebox.  As far as the client is concerned a CDN *is* the origin.  The agreement in-place between the CDN and the origin is out of scope here.  A TLS accelerator, which is a tool to help an origin with its *local* performance, or other lower-layer (in the L3 L5 etc sense) assist is within scope.  Does that make sense?

> That's really very broken and a serious violation of the transport layer contract.

Only if you believe CDN is a middlebox.  The transport layer contract is overridden by legal contracts or EULA :)

	/r$

---

From: Andrei Popov <[email-masked]>
Date: Tue, 9 May 2017 17:28:27 +0000

> To me, the argument against this comes down to this:  The 0RTT data has different security properties than the post-handshake data, and TLS implementations should not hide that difference from appl…
This is true, and early on there seemed to be general agreement that 0-RTT data would require explicit opt-in from the caller, e.g. via new API surface.

> Once the initial SSL_write_early_data() call has completed successfully the client may interleave calls to SSL_read_ex(3) and SSL_read(3) with calls to SSL_write_early_data() as required.
I'm curious why the client does not have to call SSL_read_early_data instead of the normal SSL_read in this case.

Cheers,

Andrei

---

From: Roland Zink <[email-masked]>
Date: Tue, 9 May 2017 19:30:40 +0200

> A CDN is not a middlebox.  As far as the client is concerned a CDN *is* the origin.  The agreement in-place between the CDN and the origin is out of scope here.  A TLS accelerator, which is a tool…

Depends on how you look on this. For TLS the CDN is the origin. For an 
end user the CDN is often a third party which means some unknown 
entities get access to potential private data. When this is on another 
country this may include foreign official entities in addition to the 
3rd party company itself. Really bad is when the user is not informed at 
all, e.g. the page URL doesn't show the CDN (or ad or tracking) company. 
For a mass surveillance just monitor a few tracking, advertising or CDN 
companies and you will get most of the URLs (refer header) and more from 
most users without breaking the TLS security. So why do TLS at all?

> Only if you believe CDN is a middlebox.  The transport layer contract is overridden by legal contracts or EULA :)
I would prefer if TLS wouldn't allow 3rd parties without user notification.

Roland

---

From: Colm MacCárthaigh <[email-masked]>
Date: Tue, 9 May 2017 10:35:37 -0700

On Tue, May 9, 2017 at 9:41 AM, Salz, Rich <[email-masked]> wrote:

> That also assumes that the 0RTT data will be a completely self-contained
> request.  I share the concern that Kyle and others have pointed out, that a
> single request will span the boundaries.
>

True; though I think the partial request case it's ok ... I think it's just
orphaned data. The mechanisms that prevent 0-RTT insertion across different
connections do seem robust.

> Attackers will not use well-behaved clients; does your approach still work?

Yes; it's about signaling to the client that "Your data may or may not have
been received". This is an ordinary failure mode for TCP and TLS and
something many clients have careful reasoning around. The difference with
0-RTT is now there's a bigger window of time during which that request may
get received, but that's it really. If the attacker replays, then it can be
received; and the attacker can use any client, but it doesn't change the
original well-behaved client's reasoning.

> client has *no* way to reason about retries or replay.
>
> A CDN is not a middlebox.  As far as the client is concerned a CDN *is*
> the origin.

No I don't think this works in transactional systems. For example; suppose
the client performs an update or write "through" the CDN, and 0-RTT is
being used on both sides. In the 0-RTT world, the CDN might be subject to
replay between the CDN and the Origin. But as defined, the actual client
gets no visibility of that. That breaks careful clients.  For example they
may get a 500 back and assume that the request failed, without knowing that
the request may be replayed any time in the next 10 seconds and therefore
succeed.

This can all be made workable though; with a careful consideration of how
the signaling propagates; that's not in the draft at this time though.

> overridden by legal contracts or EULA :)

Of course, but I think this is a predictable security problem. The
implications are very subtle and may be exploited by attackers, while
people unknowingly enable the behavior. I realize I'm in dark corner cases
here; but I think we should approach all of this with the same rigor we
treat the TLS state machine and crypto proofs.
-- 
Colm

---

From: Ilari Liusvaara <[email-masked]>
Date: Tue, 9 May 2017 21:12:27 +0300

> the request may be replayed any time in the next 10 seconds and therefore

Doesn't this imply that clients or CDN are using unsafe HTTP methods in
0-RTT data? Which is of course _seriously_ broken.

Because HTTP specification expressly forbids any and all updates and
writes using safe methods. Ignoring that causes very severe security
vulernabilities even today (e.g., causes essentially undefendable CSRF
attacks).

-Ilari

---

From: Colm MacCárthaigh <[email-masked]>
Date: Tue, 9 May 2017 11:44:14 -0700

> 0-RTT data? Which is of course _seriously_ broken.

It doesn't really. First; the CDN may be acting as a Layer 4 TLS proxy.
Some CDNs sell these as SSL accelerators that work by moving the handshake
closer to clients and do almost nothing else. For example S3 Transfer
acceleration works like that:
http://docs.aws.amazon.com/AmazonS3/latest/dev/transfer-acceleration.html .
In that case, the TLS proxy isn't necessarily even aware of the
under-lyling protocol. Will they be able to resist the easy-to-enable 0-RTT
time savings?

But secondly, in the real world, these can be GETs and that's how many APIs
are constructed. Why? because even though it ignores correct REST
principles, people can test and compose these operations more easily from
the command line (e.g. curl). We've all seen many APIs like this.

> vulernabilities even today (e.g., causes essentially undefendable CSRF

These aren't browser requests, they can be SDK and other clients making
HTTP API requests. That's much more common too, by volume. CSRF isn't an
issue in cases like that.

I think everything I'm writing applies even for a careful REST-compliant
case too. Even if the client is using PUT or POST or DELETE or whatever,
the same kind of transactional semantics apply. All it takes is a
disconnect between the settings in any of the middle transport layers and
the expectations of the underlying protocol. I just think that disconnect
is very predictable.

-- 
Colm

---

From: Benjamin Kaduk <[email-masked]>
Date: Thu, 11 May 2017 00:00:17 -0500

As Ilari says, there's a lot of stuff in here.  I'll put some thoughts
here at the top, and more inline.

First off, it seems somewhat self-evident that if we guarantee 0-RTT
non-replay, then of course it makes sense to just concatenate the
streams.  That is, if 0-RTT data is not replayable, there's not much
special about it to merit separating it off from normal application
data.  This isn't quite exactly true because of the DKG attack, but it
may be close enough to not matter.

However, I'm still not convinced that requiring strong 0-RTT non-replay
is feasible/the right thing to do.  So, I do not consider the question
of single stream vs. block-of-early-data + 1-RTT stream to be settled.

On 05/05/2017 11:28 AM, Colm MacCárthaigh wrote:
> aspects of replay mitigating clear, because I'd like to make a case
> for TLS providing a single-stream, which is what people seem to be
> doing anyway. 
>
> Let's look at the DKG attack. There are two forms of the attack, one
> is as follows:
>
> "Client sends a request with a 0-RTT section. The attacker lets the
> server receive it, but suppresses the server responses, so the client
> downgrades and retries as a 1-RTT request over a new connection.
> Repeating the request". 
>

Does the client always downgrade?  We don't require it to do so, in TLS
1.3, though of course browsers would.

> In this case, server-side signaling such as the X-header trick doesn't
> work at all. But thankfully this attack is equivalent to an ordinary
> socket interference attack. E.g. if an attacker today suppressed a
> server response to a HTTP request, then the client will do its retry
> logic. It's the same, and I think everything is compatible with
> today's behavior.
>
> "Client sends a request with a 0-RTT section. For some reason the
> server can't reach the strike register or single use cache, and falls
> back to 1-RTT. Server accepts the request over 1-RTT.  Then a short
> time later, the attacker replays the original 0-RTT section."
>
> In this case, server side signaling to the application (such as the
> neat X- header trick) also doesn't work, and is not backwards
> compatible or secure by default. It doesn't work because the server
> application can't be made idempotent from "outside" the application,
> so any signaling is insufficient, and is equivalent to the
> Exactly-Once message delivery problem in distributed systems. Since a
> request might be retried as in case 1, it needs an application-level
> idempotency key, or a delay-and-retry strategy (but replay will break
> this). There's some detail on all this in the review. End result is
> that a server-side application that was never designed to reach
> duplicates may suddenly be getting exactly one duplicate (that's all
> the attack allows, if servers reject duplicate 0-RTT). 
>

I feel like many applications that use delay-and-retry will see this and
conclude that they should just not attempt to use 0-RTT.

> What is actually needed here, I think, is client-side signaling.
> Careful clients need to be made aware of the original 0-RTT failure. 
>
> So for example, an SDK that writes to an eventually consistent data
> store may treat any 0-RTT failure as a hard failure, and *not* proceed
> to sending the request over 1-RTT. Instead it

Right; nothing *requires* the client to retry failed 0-RTT as 1-RTT; the
application can decide it's willing to take the risk or use some other
strategy.  But I'm not convinced that the TLS stack needs to decide on
its own, without application input.

> might wait its retry period, do a poll, and only then retry the
> request. If the TLS implementation signals the original  0-RTT failure
> to the client, as if it were a connection error, everything is
> backwards compatible again. Well mostly; to be properly defensive, the
> client's retry time or polling interval needs to be greater than the
> potential replay window, because only then can it reason about whether
> the original request succeeded or not. If there is a strict maximum
> replay window, then this behavior is enforceable in a TLS
> implementation: by delaying the original failure notification to the
> client application by that amount. 
>

And if there is not a strict replay window defined in the spec, then the
client must have some way of knowing what window the server is using,
etc., etc..  When I added the 10-second guidance that's currently in
there, I explicitly wanted something concrete, but was not willing to
claim that I knew enough about all possible ways in which TLS can be
deployed to make a normative requirement for all users of the protocol. 
I don't think that's changed; setting a fixed global limit seems to be
asking for trouble.  I suppose if we really needed to we could stick it
in a NST extension, but I don't think we really need to.

> Of course browsers won't do this, and that's ok. Browsers have decided
> that aggressive retries is best for their application space. But
> careful clients /need/ this; and it's not just about backwards
> compatibility. It is a fundamental first-principles requirement for
> something that uses an eventually consistent data store. We can say
> don't use 0-RTT, but that's not practical, for reasons also in the review.

Does a careful client really need the TLS stack to signal connection
error?  Surely the TLS stack will provide a mechanism to inquire as to
the connection state, and whether 0-RTT was rejected.  The application
could implement its own delay.

And I read what you wrote in the github issue about how saying "don't
use 0-RTT" is not practical, but I don't believe that it is universally
true.  Some (careful) applications will rightly consider the tradeoffs
and decide to not use it.  On the web ... there are different forces at
play, and it may well take a (security bug) name and website to cause
webapp creators and framework authors to take proper notice.  But I am
having a hard time squaring your point about careful clients with the
point about non-practicality.  If there are careful clients, they will
heed warnings; if just using warnings is not practical, then what are
the careful clients doing?

>
> So if we want to fully mitigate DKG attacks, I think it is useful to
> hard cap the replay, say that it MUST be at most 10 seconds. And then
> worst case, a client that needs to be careful can wait 10 seconds.
> Note that the TLS implementation can do this on the client's behalf,
> by inserting a delay. Of course that means that for these kinds of
> applications, this means that 0-RTT delivers speed most of the time,
> but may occasionally slow things down by 10 seconds. I think that's an
> ok trade-off to make for backwards compatibility.
>

Again, does this need to be in the TLS implementation or can the
(careful) application do it?

But more importantly, a requirement to "randomly" (i.e., unpredictably)
introduce a large (10-second) delay into processing is going to cause a
lot of frustration in large deployments.  Predictability trumps average
speed, in many use cases.  In other words, I don't think your side of
the tradeoff is the IETF consensus position.

> But it also has implications for middle-boxes: a TLS reverse proxy
> needs to either not use 0-RTT on the "backend" side, or it needs to
> use it in very careful way; accepting 0-RTT from the original client,
> only if the backend also accepts a 0-RTT section from the proxy. This
> is to avoid the case where the client can't reason about the potential
> for a replay between the proxy and the backend. It's doable, but
> gnarly, and slows 0-RTT acceptance down to the round trip between the
> client and the backend, via the proxy. 
>

Waiting to find out if the backend accepts 0-RTT from the proxy
basically negates any value that might be gained from using 0-RTT there
-- it's more consistent and safer to always use 1-RTT.  Many (though of
course not all) reverse-proxy deployments

> That's one reason why the review suggests something else too:  just
> lock careful applications out, but in a mechanistic way rather than a
> "good intentions" way, by having TLS implementations *intentionally*
> duplicate 0-RTT sections. 
>

Err, regularly locking out careful applications is supposed to be a good
thing?

> O.k. so all of that the above might be a bit hairy: but I want to take
> away from it at this stage is that splitting the early_data and
> application_data at application level isn't particularly helpful; the
> server-side can't really use this information anyway, because of the
> Exactly-Once problem. Client side signaling does help though, and a
> simple safe-by-default mechanism there is to behave as if the
> connection has failed, but after writing the first section of data.
> E.g. in s2n this would be ...
>
> r = s2n_write(conn, "GET / HTTP/1.1 ... "); // Client writes some
> data, we stuff it in the 0-RTT and send it. This write succeeds. From
> the client's perspective, it may or may not have been received; that's
> normal. 
>
> /* At this point the 0-RTT data is rejected by the server, and so it
> might be replayable  ... iff the server side strike-register or 
>    cache had a problem. 
>   
>    A pedantically correct TLS library might then pause here for 10
> seconds, or if it's non-blocking, then set a timer so that nothing can
> happen on conn for the next 10 seconds. Browsers could turn this
> behavior off, since they retry aggressively anyway. But it's a secure
> default that is backwards compatible.
> */
>
> r = s2n_read()/s2n_write()/s2n_shutdown();  // At this point, s2n
> returns failure. It's as if the connection failed. The client can
> implement its retry strategy, if any, safely; the request won't be
> replayed at this point. 
>

(Again, the mandatory 10-second delay on 0-RTT rejection introduces huge
processing time variance, which is a no-go in some environments.)

It seems like you are taking the general stance that the TLS stack is
the only thing responsible for anti-replay and it should take strenuous
measures to effect anti-replay.  I don't disagree that the TLS stack
should provide anti-replay measures, but I also don't think that it's
the only actor in the stack that should be thinking about anti-replay. 
If we assume that the application can also be involved in anti-replay
(yes, an assumption, for this purpose), then a lot of these proposed
countermeasures seem excessive.  The careful client can introduce its
delays, and the careful server can also differentiate between 0-RTT and
1-RTT requests, holding on to potentially dangerous if replayed 0-RTT
requests until the handshake completes, and dropping them without
processing otherwise.  This is more efficient, as delay is only
introduced where needed for safety, and does not force the TLS stack to
make a one-size-maybe-fits-all decision on the tradeoffs involved.

I understand the desire to produce a protocol that is safe by default. 
But we do that already: by default, you don't get to use 0-RTT and
comply with the spec!  The TLS WG cannot take sole responsibility for
the security of the internet, or even the world wide web; we can provide
tools and do a lot of the work, but other things have to contribute as
well.  Side channels are a great example; we can do all we can at the
TLS layer but the application still has a boatload of chances to
introduce side channels that leak nominally secret information.  We can
provide a lot of tools, including single-use session caches/strike
registers, and encourage their use, and that helps make everything more
secure.  But I don't think we should feel like we need to go it alone.

>
> A slightly higher level API is probably more realistic, because
> there's a potential to optimize for connection re-use. There's really
> no need to tear down the whole connection and start-over. It's safe to
> proceed to 1-RTT if the delay has expired. A higher level API would
> fix that, but this is just the "safe by default" API I'm outlining. 
> Again, all I want to take away is that all of this is doable safely
> with a single stream. 
>

By the time the delay has expired you could have set up several 1-RTT
exchanges, yes. But then it starts looking more like a network
communications library built on top of the TLS protocol than just a TLS
protocol implementation.

-Ben

---

From: Colm MacCárthaigh <[email-masked]>
Date: Wed, 10 May 2017 23:01:44 -0700

On Wed, May 10, 2017 at 10:00 PM, Benjamin Kaduk <[email-masked]> wrote:
>
> However, I'm still not convinced that requiring strong 0-RTT non-replay is
> feasible/the right thing to do.
>

[ I've cut a lot for brevity, but hopefully what I've replied with can
address this, and the rest ]

The case for requiring servers, and especially middle-boxes to provide
strong 0-RTT non-replay is really the resource exhaustion, throttle
exhaustion and cache timing attacks. I think that's where the really
dangerous bugs are, the ones that will be exploited - maybe even
inadvertently - and that are a show-stopper for the stateless mitigation.
It just doesn't work.

Thankfully it's within our capabilities to fix this and there's nothing
logically impossible about providing non-replay for the 0-RTT sections.

> 1.3, though of course browsers would.

The current draft says that clients should only use tickets once, if they
do that, then the repeat attempt (even if it's still 0-RTT, with a
different ticket) is un-correlatable.

> conclude that they should just not attempt to use 0-RTT.

Per the section of the original review about violation of layers and
separation of actors; this breaks the well-established relationship between
the layers. It's predictable that a site-administrator will enable 0-RTT
without any appreciation for the application-level impact. Vendors are
already providing 0-RTT in backwards compatible ways.

> the request over 1-RTT. Instead it
>
>
> strategy.  But I'm not convinced that the TLS stack needs to decide on its
> own, without application input.
>

I'm just describing the only backwards compatible safe-by-default scheme I
can concoct, it's definitely kludgey and awkward. For this thread, my main
take-away from it is that separating streams doesn't actually help the
application; instead, it's a timing thing.

> error?  Surely the TLS stack will provide a mechanism to inquire as to the
> connection state, and whether 0-RTT was rejected.  The application could
> implement its own delay.
>

Yep, absolutely; but it'd be client-side signaling, and it's not
backwards-compatible or safe-by-default to rely on.

> And I read what you wrote in the github issue about how saying "don't use
> 0-RTT" is not practical, but I don't believe that it is universally true.
> Some (careful) applications will rightly consider the tradeoffs and decide
> to not use it.  On the web ... there are different forces at play, and it
> may well take a (security bug) name and website to cause webapp creators
> and framework authors to take proper notice.  But I am having a hard time
> squaring your point about careful clients with the point about
> non-practicality.  If there are careful clients, they will heed warnings;
> if just using warnings is not practical, then what are the careful clients
> doing?
>

I actually share the optimism here. I think it's workable for careful
clients, and I wouldn't hold 0-RTT hostage to the DKG corner case. I think
we should ship it. All I'm trying to get at is that separate streams are
needless complexity, and they seem to be getting ignored by implementors
anyway.

Right now there's a very weird take in the draft: let the server figure it
out by marking the data as replay-able. Well actually that doesn't work; on
the server side, the sections can't always be correlated, and even if they
were an application would still need its own anti-retry strategy. It's
well-meaning; but doesn't work. We should take it out.

Here's an example. Client has two tickets: AA and BB.

T1. Client makes 0-RTT request for /foo/ with ticket AA. Servers receives
the request and marks as replayable-data, maybe uses AA to derive a
convenient idempotency/anti-replay key (like the X- header trick).

T2. Client was blocked, Tries again, doesn't re-use the ticket, per the
draft. So repeats with 0-RTT again, with ticket BB. Same as before, except
now the key is derived from BB.

T3. Client tries again, this time over 1RTT. Per the draft, there's *no*
signaling of repayable data.

What on earth is a server side application suppose to do with this sequence
of events? None of these 3 requests can be correlated, unless the
application author added their own key ... in which case they don't need
any of this TLS-layer stuff.

Some applications mitigate this kind of problem client side, what I'm
pointing out there is that to work robustly, then a client needs to 1) know
about the 0-RTT failure, 2) know for how long the 0-RTT request may be
replayed.  With that small modification; a client can actually repair
failures too.

> Again, does this need to be in the TLS implementation or can the (careful)
> application do it?
>

I don't think a careful application can do it without a hard-cap; otherwise
a zombie write could come along at any time and invalidate its state.

> Err, regularly locking out careful applications is supposed to be a good

Well, it locks out the careless ones, so yes ime, same as GREASE really :)

> processing time variance, which is a no-go in some environments.)

It needn't be mandatory; just the default. A careful client could do as
you're suggesting; handle this at a different layer.

-- 
Colm

---

From: Ilari Liusvaara <[email-masked]>
Date: Thu, 11 May 2017 11:52:18 +0300

> of single stream vs. block-of-early-data + 1-RTT stream to be settled.

There are deeper problems than that:

- Strict HTTP clients can't allow autoreplay if 0-RTT contains a POST
  (and any HTTP client that isn't strict is a lost cause anyway).
- Ensuring that there is no race causing possible data corruption if
  the server rejects 0-RTT.
- Concatenation seems to play poorly with 0-RTT exporters (which start
  to seem like an attractive nuisance...).

Also, turns out that unordered replay is just fundamential. So any
_existing_ systems that don't handle unordered replay can be broken,
and there isn't anything TLS can do about that.

Which leaves the "loads of replays" problem from lack of strong 0-RTT
replay protection.

-Ilari

---

From: Ilari Liusvaara <[email-masked]>
Date: Sat, 13 May 2017 23:24:51 +0300

> issue in cases like that.

Hope your API is actually idempotent, because the HTTP code, which sits
below the SDK code can replay requests on its own...
 
> the expectations of the underlying protocol. I just think that disconnect

PUT and DELETE are also subject to autoretry. Which means that any
PUT or DELETE in failed 0-RTT can be immediately replayed as 1-RTT by
the HTTP code.

However, POST is not subject to autoretry. Which means that any POST
in failed 0-RTT must be reported to next layer as "error: connection to
server lost before reply was received". Which is the same error if
server somehow sent a TLS close_notify with pending POST (which is
considered autenticated close!).

(In HTTP/2, if the server explicitly refuses the stream POST is on,
that does not count as fatal failure, and the http library can
autoretry).

The next layer can then decide to retry the request upon getting the
connection lost error. However, in such situation, one _always_ needs
to handle possible reordering, _regardless_ of 0-RTT or TLS version!

Unified-stream model has the following to keep in mind:

- The client TLS library APIs must not be subject to races that may
  cause data corruption if server rejects 0-RTT (_including_ selecting
  different application-layer protocol).
- The 0-RTT data may not be suitable for replay, even if application-
  layer protocol matches. And 0-RTT containing HTTP POSTs is the not
  the only case even in HTTP (e.g. what Tokbind has specified so far
  renders such replay unsafe even for GET).

Two recommendations:

- Always require strong 0-RTT anti-replay, even accross servers. The
  attacks raising from failing to do this are just too severe and
  virtually impossible to address in any other way.
- Clients shall treat any 0-RTT data sent as potentially received by
  the server, _even_ if server refuses the 0-RTT.

-Ilari