[TLS] KeyUpdate and unbounded write obligations

David Benjamin <davidben@chromium.org> Thu, 18 August 2016 04:29 UTC

Return-Path: <davidben@google.com>
X-Original-To: tls@ietfa.amsl.com
Delivered-To: tls@ietfa.amsl.com
Received: from localhost (localhost []) by ietfa.amsl.com (Postfix) with ESMTP id B660F12D176 for <tls@ietfa.amsl.com>; Wed, 17 Aug 2016 21:29:34 -0700 (PDT)
X-Virus-Scanned: amavisd-new at amsl.com
X-Spam-Flag: NO
X-Spam-Score: -3.946
X-Spam-Status: No, score=-3.946 tagged_above=-999 required=5 tests=[BAYES_00=-1.9, DKIM_SIGNED=0.1, DKIM_VALID=-0.1, DKIM_VALID_AU=-0.1, HEADER_FROM_DIFFERENT_DOMAINS=0.001, HTML_MESSAGE=0.001, RCVD_IN_DNSWL_LOW=-0.7, RP_MATCHES_RCVD=-1.247, SPF_PASS=-0.001] autolearn=ham autolearn_force=no
Authentication-Results: ietfa.amsl.com (amavisd-new); dkim=pass (1024-bit key) header.d=chromium.org
Received: from mail.ietf.org ([]) by localhost (ietfa.amsl.com []) (amavisd-new, port 10024) with ESMTP id 3tmypHfSoRq5 for <tls@ietfa.amsl.com>; Wed, 17 Aug 2016 21:29:32 -0700 (PDT)
Received: from mail-it0-x233.google.com (mail-it0-x233.google.com [IPv6:2607:f8b0:4001:c0b::233]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by ietfa.amsl.com (Postfix) with ESMTPS id 3F03912B00B for <tls@ietf.org>; Wed, 17 Aug 2016 21:29:32 -0700 (PDT)
Received: by mail-it0-x233.google.com with SMTP id x131so17509750ite.0 for <tls@ietf.org>; Wed, 17 Aug 2016 21:29:32 -0700 (PDT)
DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=chromium.org; s=google; h=mime-version:from:date:message-id:subject:to; bh=WBCrkeCUlAzPR/6qWbsCZKAlsytEyB3EKXA+fw/CgCE=; b=ise4/qryKieNeHkS3lgh1hrsB4E0fPtcOM0HjncKXhh21t/Yj+/A11VAsDNZwMXJB+ IE/WNnKukLIY4471H/JdNN7cdqOgHGm7xj2Vm9uU++taexYW8/WEgdk156c4TbwrmEPQ Tq9m/OKqp/MY5qW1WiT1LtgOWAsH40hPxzsB4=
X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20130820; h=x-gm-message-state:mime-version:from:date:message-id:subject:to; bh=WBCrkeCUlAzPR/6qWbsCZKAlsytEyB3EKXA+fw/CgCE=; b=Gm88rDozsoF0IXCRk0bXpatCU5/kmhexMAyR841KKBpxRV708bASENaB/W3Ed6YlPg tmhZdAnUwCC0ugSqp3PW6Urvq6Ni2wNyWTEQVBiRznmYlVQYdpg9iEo2XGn4vDa0aN0e YNzV3V5xBcyEX8kqEY71ukOmQNaU1wE1/m/IEdJpEXOhbzUPh0q/tFFxEAiwFW0OSasQ BLFEGFk/Z6xx5g5X3wgIkvx0XPSbSIeCKUAG7J3kEYpl2yUwcvzSMjeJArIOBC1I0A0E 3n9gtDRSPOOi8/2QEZ4nQCmmdCJHOHNv7Ur/ULcmg+/RC8qIsw/+QCkCkZBPRwl45paq LH5w==
X-Gm-Message-State: AEkoousKKvZoI3OW+AAd/DtJWXAcs5axfSkVhM0LvAJfUru6YHjbVjmN8vvFaAzveqejqHUhjAyjUxTrxJpM+u+a
X-Received: by with SMTP id x137mr880382ite.18.1471494571157; Wed, 17 Aug 2016 21:29:31 -0700 (PDT)
MIME-Version: 1.0
From: David Benjamin <davidben@chromium.org>
Date: Thu, 18 Aug 2016 04:29:19 +0000
Message-ID: <CAF8qwaDgGHGmuBwhZEz9-=Ss2bfzNAYWfmnbMqQDxTQnMUpH7g@mail.gmail.com>
To: "tls@ietf.org" <tls@ietf.org>
Content-Type: multipart/alternative; boundary="94eb2c19c026344761053a5107ac"
Archived-At: <https://mailarchive.ietf.org/arch/msg/tls/cfw4paCGxI7Fj8QNmj6k1I66VII>
Subject: [TLS] KeyUpdate and unbounded write obligations
X-BeenThere: tls@ietf.org
X-Mailman-Version: 2.1.17
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, 18 Aug 2016 04:29:35 -0000

Amusingly, Keith and I appear to have crossed mid-air in our respective
requests to change KeyUpdate, so this will be a fun collision. Mine is a
tad larger of a change I'm afraid.

We've currently implemented KeyUpdate in our in-progress TLS 1.3 code, but
we've for now ignored the requirement for the receiver to send KeyUpdates
to catch up with the sender. It appears that honoring that will cause
significant problems.

A TLS stack does not know much of the read/write patterns of the
higher-level application. It must handle all kinds of flows, so a dumb
filter is ideal. Some TLS APIs are even structured around this. Rather than
consume and provide a socket interface, they encrypt and decrypt buffers,
leaving transport business to the consumer. (And socket-based APIs are
often used as if they were filters using buffers for sockets.)

We may then have transport reads progress faster than transport writes, or
the write channel may not even be driven most of the time. (This isn't that
contrived. Unpipelined HTTP/1.1 never reads and writes in parallel. HTTP/2
may not have any response to write. Or perhaps it is processing a request
while an earlier response is still being written.) The receiver can then
accrue KeyUpdate obligations unboundedly. When the write channel is active
again, it has to dump all those out as unbounded write overhead. This is
rather a mess. There should be a tight bound (one, if not zero) on
KeyUpdate obligations.

I'm unclear on exactly what parts of KeyUpdate the WG finds important, so I
haven't written a concrete PR yet. But here is roughly the fix I have in

Sending one KeyUpdate and 1,000 in a row morally should be the same. But
both ends share a KeyUpdate track, so any forward secrecy goals require
matching generations. So branch client-write and server-write first and
have two KeyUpdate ladders starting at client_traffic_secret_0 and
server_traffic_secret_0.  (Note bidi KeyUpdate is asynchronous, so
implementations had to store separate read and write traffic secrets
anyway.) As a small bonus, we can lose the client/server split from traffic
key calculations (7.3), having branched at traffic secret already.

However, we lose the "free" (really at the cost of this unboundedness
problem) generation-based bidirectional KeyUpdate sync. Depending on what
we want out of KeyUpdate, I can imagine a few options:

- Don't. KeyUpdates are unilateral. Recommend in the spec to KeyUpdate
every N records or so and leave it at that. (I think this is the best
option on grounds of simplicity, assuming it meets the primary needs of

- If you receive a KeyUpdate and didn't send one in the last N minutes
(where N minutes >>> 1 RTT), (optionally?) flag your next write to be
preceded by KeyUpdate. This is simple but needs an ad-hoc timeout to
prevent ping-ponging.

- Variations on sticking a please_echo boolean in the KeyUpdate message,
generation counts, etc., to get synchronization with coalescing if we need
it. I would much prefer the simpler options unless we truly need
this. (KeyUpdate is right now a *required* feature, so simplicity should be
a priority. Rare use cases are what extensions are for.)



PS: We've seen this before with renego (I've seen many OpenSSL consumers
which lock up if the peer renegotiation), error alerts triggered on reads
(often they don't get sent), and close_notify (also don't get sent in
practice). Deviations from dumb filters are expensive.

PPS: I'm wary of over-extending post-handshake auth for the same reason,
though I haven't had time to look carefully at the latest proposal yet.
Still, having TLS specify and analyze the crypto while lifting the actual
messages into application-level framing would be preferable for these sorts
of side protocols. The application gets to make assumptions about
read/write flows and knows in which contexts what is and isn't allowed.