Subject: [TLS] Interaction between session resumption and negotiated protocol version

From: Fabrice Gautier <[email-masked]>
Date: Fri, 3 Apr 2015 14:37:55 -0700

Hi all,

It's not clear to me what should be the protocol version be selected
when trying to resume sessions.

For the cipher_suite in the ServerHello, the TLS 1.2 RFC explicitly says:

      For resumed sessions, this field is the value from the state of
the session being resumed.

But for the protocol_version it does not say anything specific about
resumption, so, its just:

      This field will contain the lower of that suggested by the
client in the client hello and the highest supported by the server.

I also note that in Section 7, the definition of a Session does NOT
include the protocol version.

Given that, it seems that it's acceptable for a server to attempt to
resume a session with TLS 1.2 that was originally established with TLS
1.0, or vice-versa.

That seems wrong, but I also guess this is a very plausible scenario
given that many implementation do version fallback if something goes
wrong.

So what should be the behavior of a server when trying to resume but
there is a version mismatch ?

(1) Cache the old ClientHello.version and  do not resume a session if
the new ClientHello.version does not match the cached one.
(2) Cache the old ServerHello.version and do not resume if the new
ClientHello.version does not allow to negotiate the same
ServeHello.version. Allow resuming at lower version even if both side
support higher version.
(3) Cache the old ServerHello.version and do not resume unless the
cached version is the max of ClientHello.version and max supported
Server version. (e.g.: Do full handshake at best version instead of
downgrading to a lower cached version)
(4) Don't cache any version, always attempt to resume with the max of
ClientHello.version and max supported Server version

My guts tell me that (3) is the right thing to do.

What do other think ?

Thanks

-- Fabrice

---

From: Yuhong Bao <[email-masked]>
Date: Fri, 3 Apr 2015 14:42:22 -0700

I am thinking that the introduction of the TLS session hash extension is a good time to clarify this matter.

----------------------------------------
> From: [email-masked]
> Date: Fri, 3 Apr 2015 14:37:55 -0700
> To: [email-masked]
> Subject: [TLS] Interaction between session resumption and negotiated protocol version
>
> Hi all,
>
> _______________________________________________
> TLS mailing list
> [email-masked]
> https://www.ietf.org/mailman/listinfo/tls

---

From: David Benjamin <[email-masked]>
Date: Fri, 03 Apr 2015 23:06:19 +0000

Conveniently, I spent a lot of time wrestling with this a while back and
testing client implementations:
https://github.com/tlswg/tls13-spec/issues/136

#4 turns out to be incompatible with basically every client implementation
out there. They all reject cross-version resumptions. It's not even clear
what this should mean if a future TLS version were to, say, change the size
of the master secret or how resumption works.

I agree #3 is the most natural. The behavior for a server is then:
1. Based on client_version, select a protocol version.
2. Look up session based on session_id or ticket.
3. If the version the session was established at does not match the
negotiated version, pretend the lookup failed.
4. Do a full or abbreviated handshake based on whether other properties of
the session check out (cipher suite still allowed, etc.).

This is equivalent to saying that the notion of a "session" or "session
cache" is a version-specific thing. A TLS 1.0 session cache is a different
beast from a TLS 1.1 session cache, so a cross-version lookup should fail,
and lookup isn't even defined until you've picked a version. This also
works best with future versions of TLS changing how session resumption
works. But no one, to my knowledge, has separate 1.0 and 1.1 session cache
implementations, so this should be spelled out explicitly.

Otherwise, #1 probably works too, though OpenSSL doesn't store the
client_version. I think I mostly see that as the client's responsibility;
session resumption skips negotiation of lots of parameters, so you already
have to be aware of handling this when your half of preferences change. A
server SHOULD flush its session cache and invalidate tickets when changing
cipher preferences, a client SHOULD shard the session cache by fallback
type, etc.

David

---

From: Dave Garrett <[email-masked]>
Date: Fri, 3 Apr 2015 19:33:30 -0400

> https://github.com/tlswg/tls13-spec/issues/136

Just to note, the current draft still has an equivalent "SHOULD" in the
updated backwards compatibility section.

I dropped the "already knows" part, as that was not a particularly safe
assumption to keep. (both the "knowing" part & the fact that things
could of course change) The loose requirement for resumption is still in
there, just worded to be just for resumption.

TLS 1.2 has:

Whenever a client already knows the highest protocol version known to
a server (for example, when resuming a session), it SHOULD initiate
the connection in that native protocol.

TLS 1.3 currently has:

A client resuming a session SHOULD initiate the connection using the
version that was previously negotiated.

Dave

---

From: Brian Smith <[email-masked]>
Date: Fri, 3 Apr 2015 13:48:57 -1000

> resume a session with TLS 1.2 that was originally established with TLS

Perhaps one could construct a logical argument for that. However, is
it really worth the complication for the server to try to resume a
session that was established at a different version than it would
otherwise create a new session? I doubt any potential benefit would
outweigh the likely harm of doing so.

> given that many implementation do version fallback if something goes

Two wrongs don't make a right.

> downgrading to a lower cached version)

<snip>

> My guts tell me that (3) is the right thing to do.

Yes!

Here's another way of thinking about it: Given
ClientHello.client_version and the max version the server supports,
calculate ServerHello.server_version. Then, look in the session cache
for an entry that has the *exact same* ServerHello.server_version. If
you find one, resume that session. Otherwise, do a full handshake.

If you want to be really careful, also calculate cipher_suite before
resuming, and only resume a session that has the same cipher_suite.

Cheers,
Brian

---

From: Brian Smith <[email-masked]>
Date: Fri, 3 Apr 2015 13:52:54 -1000

> version that was previously negotiated.

These are both bad. The best way for the client to behave is to
calculate ClientHello.client_version independently of the session
cache.

For any full handshake, the client_version should be the best version
the client supports. Since the server is allowed to respond to any
ClientHello by initiating a full handshake (e.g. because it blew away
its session cache), ClientHello.client_version shouldn't be different
for ClientHellos that include a session ID or session ticket.

Cheers,
Brian

---

From: David Benjamin <[email-masked]>
Date: Fri, 03 Apr 2015 23:53:19 +0000

> version that was previously negotiated.

That new version is also unwise and the entire text just be dropped. It
means you get stuck at that version even if the server gets upgraded later.

It also tends to compound itself with other problems. Maybe you implement a
version fallback, but fail to shard the session cache by fallback type (you
really really should do that if you have a fallback). Now any spurious
callback is sticky and there's no way to get back to the original version
unless the session expires on the client. (Expiring on the server doesn't
help you any. The damage is done before ServerHello.)

I cannot stress enough how much pain and suffering that one little sentence
can cause. :-) I ended up completely redoing the version negotiation logic
in BoringSSL because of how far OpenSSL takes that advice on the client and
how much it breaks.

David

---

From: Martin Thomson <[email-masked]>
Date: Fri, 3 Apr 2015 17:37:07 -0700

> means you get stuck at that version even if the server gets upgraded later.

Yep.  I recently landed a change in NSS that does exactly this.

That risks badness with servers that didn't do what Brian suggested
(your option 3), but our assessment (advised by telemetry) was that
the probability of that was low enough.

---

From: Dave Garrett <[email-masked]>
Date: Fri, 3 Apr 2015 20:45:52 -0400

> means you get stuck at that version even if the server gets upgraded later.

I'd certainly support dropping it and putting in very clear resumption
instructions with no SHOULDs like this. I just left it in as it was generally outside
the scope of the updates I was doing, and there's an open issue filed for it.

Dave

---

From: Brian Smith <[email-masked]>
Date: Fri, 3 Apr 2015 16:54:36 -1000

<snip>

> have to be aware of handling this when your half of preferences change.

Agreed.

> A server SHOULD flush its session cache and invalidate tickets when
> changing cipher preferences

I think implementing your step #4 above is better than relying on such
flushing, because in a setup where multiple servers are sharing a
session cache, the flushing would likely be racy, and/or the
individual servers may be configured differently--either temporarily
as a setting change propagates across a set of servers, or permanently
due to some (mis)configuration issue.

> a client SHOULD shard the session cache by fallback
> type, etc.

Alternatively, the client should avoid doing any fallback at all. If
it has to do fallback for some reason, it could also avoid the issue
by avoiding session resumption during fallback.

Cheers,
Brian

---

From: David Benjamin <[email-masked]>
Date: Sat, 04 Apr 2015 03:24:04 +0000

> by avoiding session resumption during fallback.

Well, yes. Fallback was just the first example that popped into my head,
hence the "etc". Mentally substitute a different one if you really prefer.
:-P More generally, connections made with different configurations should
use separate session caches (on either server or the client).

(And, yeah, if your server is multi-homed or whatever when you'd have to
care about races, you'd need to treat it as separate session caches.
Flushing is a special case of that when only one cache is active at a time.)

David