Subject: Re: [TLS] KeyUpdate and unbounded write obligations

From: Eric Rescorla <[email-masked]>
Date: Thu, 1 Sep 2016 12:55:29 -0700

I have created a PR for this at:
https://github.com/tlswg/tls13-spec/pull/611

As it seems there was rough consensus for this change, I will merge this
weekend
absent some violent objections or direction to the contrary from the chairs.

-Ekr

On Mon, Aug 29, 2016 at 3:06 PM, Russ Housley <[email-masked]> wrote:

> Eric:
>
> > Yes, I agree separate ladders would fix this. I don't necessarily object
> to this
> > change, but I'm not sure it's that big a deal either, because you really
> only get
> > into this case when there's a big asymmetry in sending rate, so much that
> > one side wants to send multiple updates before the other side has sent
> > any data at all.
>
> I can think of many situations where one side sends a lot more data than
> the other.
>
> > Note: it's also possible to avoid the rollback problem with the existing
> > single-ladder system: when you send a key update you compute:
> >
> > traffic_secret_N+1
> > read_key_N+1
> > write_key_N+1
> >
> > You then discard traffic_secret N, write_key_N, but key read_key_N, so
> if you
> > are M updates ahead of the other side, you have M read keys outstanding,
> > but these cannot be used to produce the write keys. However, this
> probably
> > isn't simpler than just running two ladders if we think this is
> important.
>
> That seems to work.  However, I think that splitting the ladders seems to
> marry well with the many situations where one side sends a lot more than
> the other.  So, I suggest that we split the ladders.
>
> Russ
>
>

---

From: Ilari Liusvaara <[email-masked]>
Date: Fri, 2 Sep 2016 00:09:52 +0300

> absent some violent objections or direction to the contrary from the chairs.

I tried to implement this, and discovered an issue:

The client handshake traffic secret is needed for deriving client
Finished, and client application traffic secret is only needed after
that point. However, the derivation of client application traffic
secret uses handshake hash post Server Finished.

So you either need to buffer the handshake hash value, or have two
overlapping client traffic secrets.

Changing the client application traffic secret context to extend
up to Client Finished would solve the issue.

Additionally:
- That would make the implementation look nicely symmetric
- Would also prevent having "not yet" keys. One can derive keys
  from present state and install them immediately.

-Ilari

---

From: Keith Winstein <[email-masked]>
Date: Thu, 1 Sep 2016 14:11:13 -0700

I think we have to oppose a change to KeyUpdate that adds P4 (bounded
write obligations) but not P3 (ability to learn that a KeyUpdate was
read by other side). These are orthogonal and easily achievable with a
pretty small tweak. Here are four options that would work for us:

1. No change to message structures. Forbid implementations from
sending an update_not_requested KeyUpdate unless it is triggered by an
update_requested KeyUpdate.

2. Add a uint8 field to KeyUpdate representing the sender's current
receive generation (lower 8 bits).

3. Keep request_update an 8-bit field (same width as it is in the PR),
and define the high bit as representing the update_requested boolean,
and the remaining bits represent the lower 7 bits of the sender's
current receive generation

4. Keep request_update an 8-bit field (same width as it is in the PR),
define the high bit as representing the update_requested boolean, and
the remaining bits represent a 7-bit opaque value that must be echoed
in the response. (If multiple update_requested KeyUpdates arrive, any
of their values can be the one that gets echoed.)

Approach 1 seems like it might be generally acceptable here, even
understanding the concerns of the list. I am happy to draft sample
text if helpful.

-Keith

On Thu, Sep 1, 2016 at 12:55 PM, Eric Rescorla <[email-masked]> wrote:
>> marry well with the many situations where one side sends a lot more than the
>> other.  So, I suggest that we split the ladders.
>>
>> Russ
>>
>
>
> _______________________________________________
> TLS mailing list
> [email-masked]
> https://www.ietf.org/mailman/listinfo/tls
>

---

From: Eric Rescorla <[email-masked]>
Date: Thu, 1 Sep 2016 14:20:02 -0700

> up to Client Finished would solve the issue.

Hmm.... having the keys in one direction cover the client's certificate and
certverify
and the keys in the other direction not seems pretty sketchy.

As I understand this, it's just an aesthetic issue, right?

-Ekr

---

From: Ilari Liusvaara <[email-masked]>
Date: Fri, 2 Sep 2016 00:29:23 +0300

> As I understand this, it's just an aesthetic issue, right?

I don't think this is just aesthetic issue:

- If you derive the traffic secret at the moment the right hash is
  available, you need to stash the traffic secret for later (one
  can't overwrite the existing one, since you will need that for
  ClientFinished).
- If you derive the traffic secret just before use, you need to
  have saved the hash value used for derivation, since the
  ClientFinished (and possibly Certificate(Verify)) has altered it.

-Ilari

---

From: Eric Rescorla <[email-masked]>
Date: Thu, 1 Sep 2016 14:32:41 -0700

> I don't think this is just aesthetic issue:

Perhaps I should have said "minor implementation inconvenience"

>   can't overwrite the existing one, since you will need that for

Yes, but this was already true for handshake_secret, no?

-Ekr

---

From: David Benjamin <[email-masked]>
Date: Thu, 01 Sep 2016 21:35:46 +0000

>   ClientFinished (and possibly Certificate(Verify)) has altered it.

I do rather like never splitting the DeriveTrafficKey() and UseTrafficKey()
operations. We have to hold on to an extra copy of traffic_secret_0 today
in the single-ladder version for very similar reasons. That only one set of
keys covesr the client certificate is certainly kind of weird. Although I
assume we can just analyze as if it didn't since we were clearly okay with
not including it before.

David

---

From: Ilari Liusvaara <[email-masked]>
Date: Fri, 2 Sep 2016 11:05:37 +0300

> Perhaps I should have said "minor implementation inconvenience"

Yeah, turns out the easiest way is to save the hash (used a new field
instead of just trashing client_random/server_random with this value,
because that is less effort to do).

-Ilari

---

From: Ilari Liusvaara <[email-masked]>
Date: Fri, 2 Sep 2016 11:42:43 +0300

> pretty small tweak. Here are four options that would work for us:

Unfortunately, one can get into situations like this (saw this
in testing):

1) Client send KeyUpdate[req=true] (and continues blasting data)
2) Server receives the KeyUpdate[req=true]
3) Server queues a KeyUpdate[req=false], it gets stuck.
4) Client send KeyUpdate[req=true] (and continues blasting data)
5) Server receives the KeyUpdate[req=true]
6) Server quenches the KeyUpdate, since it has fresh keys.
7) Client send KeyUpdate[req=true] (and continues blasting data)
8) Server receives the KeyUpdate[req=true]
9) Server quenches the KeyUpdate, since it has fresh keys.
10) Server sends something, the KeyUpdate gets unstuck.
11) The client receives the KeyUpdate[req=false]

Considering those KeyUpdates are triggered every ~5min (by volume
trigger[1]), effective RTT was something like ~10-15min (despite sub-
millisecond transport latencies). And the 1 server-sent KeyUpdate
was in reaction to first client KeyUpdate, not the last...

[1] Set at 10,000,000 records (even for 0x1303, which can handle
far far more).

-Ilari

---

From: Keith Winstein <[email-masked]>
Date: Fri, 2 Sep 2016 02:23:46 -0700

You seem to be raising a complaint with my option #1, the "1-bit" option.

You're correct -- in that scheme, a node can only assume that the
other side has read as many KeyUpdates as it has gotten in response.
If the client decides to send KeyUpdates every 5 minutes, but the
other side is only writing every 10 minutes and coalescing its
responses, the client's knowledge that its KeyUpdates have been read
will get further and further behind. In the interests of compromise, I
think we would say that in situations where the other side is only
writing very infrequently, we would just give up on trying to get
assurance that a KeyUpdate got read, since it was probably not very
useful anyway. (The other side might never write to its socket ever
again! Then it really is hopeless under any scheme.)

Obviously if we had our choice, we'd prefer a 7- or 8-bit field
(options #2, #3, and #4) or a 64-bit field (as in PR #426/580), all of
which address your concern. But, trying to get to consensus here.

-Keith