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

Benjamin Kaduk <bkaduk@akamai.com> Thu, 11 May 2017 05:00 UTC

Return-Path: <bkaduk@akamai.com>
X-Original-To: tls@ietfa.amsl.com
Delivered-To: tls@ietfa.amsl.com
Received: from localhost (localhost [127.0.0.1]) by ietfa.amsl.com (Postfix) with ESMTP id 3C5A412945E for <tls@ietfa.amsl.com>; Wed, 10 May 2017 22:00:21 -0700 (PDT)
X-Virus-Scanned: amavisd-new at amsl.com
X-Spam-Flag: NO
X-Spam-Score: -2.701
X-Spam-Level:
X-Spam-Status: No, score=-2.701 tagged_above=-999 required=5 tests=[BAYES_00=-1.9, DKIM_SIGNED=0.1, DKIM_VALID=-0.1, DKIM_VALID_AU=-0.1, HTML_MESSAGE=0.001, RCVD_IN_DNSWL_LOW=-0.7, RP_MATCHES_RCVD=-0.001, SPF_PASS=-0.001] autolearn=ham autolearn_force=no
Authentication-Results: ietfa.amsl.com (amavisd-new); dkim=pass (1024-bit key) header.d=akamai.com
Received: from mail.ietf.org ([4.31.198.44]) by localhost (ietfa.amsl.com [127.0.0.1]) (amavisd-new, port 10024) with ESMTP id CkcEnZfu_bOJ for <tls@ietfa.amsl.com>; Wed, 10 May 2017 22:00:19 -0700 (PDT)
Received: from prod-mail-xrelay07.akamai.com (prod-mail-xrelay07.akamai.com [23.79.238.175]) by ietfa.amsl.com (Postfix) with ESMTP id BE1AF126BF6 for <tls@ietf.org>; Wed, 10 May 2017 22:00:18 -0700 (PDT)
Received: from prod-mail-xrelay07.akamai.com (localhost.localdomain [127.0.0.1]) by postfix.imss70 (Postfix) with ESMTP id C57C743341D; Thu, 11 May 2017 05:00:17 +0000 (GMT)
Received: from prod-mail-relay10.akamai.com (prod-mail-relay10.akamai.com [172.27.118.251]) by prod-mail-xrelay07.akamai.com (Postfix) with ESMTP id A49D2433404; Thu, 11 May 2017 05:00:17 +0000 (GMT)
DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=akamai.com; s=a1; t=1494478817; bh=HRNPBKul+yjifRzdQYgnXaODVSmBYJI0Lx/sFhQMwSg=; l=28703; h=From:To:References:Date:In-Reply-To:From; b=VIe5uo/CDf8e1d2kfAyDUhTIympefFdMXCjjutEZwJ7jMZ4tUZG9WGpTgWUpyfj+W hqDhgaA+AHp3dEEfFr0yRiJE5Zz/0AhqJKRCH0tw9FpFJC9foi6MMvg80pjwWZxPeb 9uJj72ESIcnBSHXYDbmooB2WJWBxYROpykfcmtRo=
Received: from [172.19.17.86] (bos-lpczi.kendall.corp.akamai.com [172.19.17.86]) by prod-mail-relay10.akamai.com (Postfix) with ESMTP id 61F2D1FC03; Thu, 11 May 2017 05:00:17 +0000 (GMT)
From: Benjamin Kaduk <bkaduk@akamai.com>
To: Colm MacCárthaigh <colm@allcosts.net>, "tls@ietf.org" <tls@ietf.org>
References: <CAAF6GDfm=voTt_=JrdGtiaYby1JG8ySU2s6myjjpHKeGvi0bMg@mail.gmail.com>
X-Enigmail-Draft-Status: N1110
Message-ID: <c94ff8ac-583c-7bf4-83a3-5757e677b9c6@akamai.com>
Date: Thu, 11 May 2017 00:00:17 -0500
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:45.0) Gecko/20100101 Thunderbird/45.8.0
MIME-Version: 1.0
In-Reply-To: <CAAF6GDfm=voTt_=JrdGtiaYby1JG8ySU2s6myjjpHKeGvi0bMg@mail.gmail.com>
Content-Type: multipart/alternative; boundary="------------A8688FCAE9DFBFC6788D668E"
Archived-At: <https://mailarchive.ietf.org/arch/msg/tls/wvKyMwACJ_sgzyq0as1sQXantjQ>
Subject: Re: [TLS] The case for a single stream of data
X-BeenThere: tls@ietf.org
X-Mailman-Version: 2.1.22
Precedence: list
List-Id: "This is the mailing list for the Transport Layer Security working group of the IETF." <tls.ietf.org>
List-Unsubscribe: <https://www.ietf.org/mailman/options/tls>, <mailto:tls-request@ietf.org?subject=unsubscribe>
List-Archive: <https://mailarchive.ietf.org/arch/browse/tls/>
List-Post: <mailto:tls@ietf.org>
List-Help: <mailto:tls-request@ietf.org?subject=help>
List-Subscribe: <https://www.ietf.org/mailman/listinfo/tls>, <mailto:tls-request@ietf.org?subject=subscribe>
X-List-Received-Date: Thu, 11 May 2017 05:00:21 -0000

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:
>
> 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". 
>

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.
>
> 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). 
>

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 ...
>
> 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. 
>

(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