Subject: [TLS] ESNI robustness and GREASE PRs

From: David Benjamin <[email-masked]>
Date: Mon, 17 Dec 2018 17:17:37 -0600

Hi folks,

We[*] wrote up some proposed changes for draft-ietf-tls-esni that we'd like
the group's thoughts on. The goal is to make ESNI more robust and eliminate
a bunch of deployment risks. The PRs are linked below:

https://github.com/tlswg/draft-ietf-tls-esni/pull/124
https://github.com/tlswg/draft-ietf-tls-esni/pull/125

The first tries to make ESNI more robust. It introduces the notion of a
"public name" which gives the client an authenticated signal to retry with
new keys or without ESNI at all. This mitigates DNS/server mismatches (a
concern on each key rotation), and partial rollouts or rollbacks (a concern
when first enabling it, plus some scenarios with TLS-terminating
middleboxes).

The second recommends clients to send GREASE ESNI extensions when they do
not have cached ESNIKeys available. This better meets the "Do not stick
out" goal. The server behavior in the first PR gives us this for free.

Details are in the PRs.

(The two PRs were originally written up together. I split them in two based
on some feedback, but since they touch the same text, the GREASE PR
includes the robustness PR. If the WG wishes to go with one but not the
other, the text and details can be adjusted accordingly.)

Thoughts?

David

[*] Steven and I wrote the text itself, with input from Adam, Ben, and
Brad, all CC'd.

---

From: Stephen Farrell <[email-masked]>
Date: Tue, 18 Dec 2018 01:58:15 +0000

Hiya,

> https://github.com/tlswg/draft-ietf-tls-esni/pull/124

I like chunks of that. I'm unsure about others 'till I grok
'em better.

I like the bits that add detail of required behaviour, e.g.
handling resumption, which has been puzzling me:-)

The idea of a "public name" is possibly good, but I'll need
to ponder it more. (Being slow, as I am:-)

Would it be fair to say that that concept isolates ESNI from
a likely DNS ops failure mode at the expense of adding another
way in which ESNI could be defeated via abuses of the Web PKI?
If so, is there any way to make using a "public name" require
that the entity who can authenticate as the "public name" also
has the private part of the public value published in DNS?
(Apologies if your scheme does that already and I missed it, or
if that's exactly the sync breakage you're trying to mitigate;-)

At first sight though, it does look like there's an interesting
overlap between this and what Nico/Viktor were proposing about
using HRRs. (In the sense that maybe you're all heading towards
a not-bad idea, modulo what I think is ekr's winning argument
that always requiring an extra RTT would be disqualifying.)

[On a different point, changes like these (or other breaking
changes) may be easier if we do that "pick an implementation
draft" thing that's been done in other more complex cases. I'm
not sure if ESNI justifies that, but given my coding resources,
(and abilities:-) it'd help me if people wanted to do something
like that. E.g. to decide we noodle about with a -03 and try
code up to a -04 or similar (or -05 or whatever version is
appropriate). For example, there's another kind of change that
I think we want before doing interop on a later version: I'd
argue the value published in DNS needs to be DNS/zonefile friendly
and not primarily TLS-presentation-layer friendly - we're not
there now and getting there (or deciding that's not needed) may
require discussion of published drafts and not just issues/PRs.]

> https://github.com/tlswg/draft-ietf-tls-esni/pull/125

Greasing like this seems like a fine plan. I'm not sure if
greasing is still quite the appropriate term, as this is doing
more than that, but I like it lots - without something like
this ESNI will stick out a lot. I do wonder if people will
be willing to emit so many useless bytes though?

A possible alternative way to get a similar "don't stand out"
effect might be to re-do the CH changes for ESNI so that they're
encoded to look like a real key-share (maybe with some other
bits of the CH serving dual purposes) - I think that might be
just about doable, but a) likely ends up as a total hack, b) is
pretty limited (e.g. for looooong names),  and c) requires
servers to guess if a CH does or doesn't contain ESNI stuff.
Could be fun if it worked though, but has anyone looked at
that alternative way to not stand out?

Cheers,
S.

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

---

From: David Fifield <[email-masked]>
Date: Mon, 17 Dec 2018 21:42:51 -0700

On Mon, Dec 17, 2018 at 05:17:37PM -0600, David Benjamin wrote:
> We[*] wrote up some proposed changes for draft-ietf-tls-esni that we'd like the
> group's thoughts on. The goal is to make ESNI more robust and eliminate a bunch
> of deployment risks. The PRs are linked below:
> 

Under this design, the ESNIKeys structure in DNS gains a public_name
field, which is the name against which the client is supposed to verify
handshake, in the event that the server cannot descript the ESNI and
returns a esni_retry_request response. The esni_retry_request structure
contains alternative ESNIKeys for the client to use in its next
connection attempt.

I started to think: what if the alternative ESNIKeys were a standard TLS
extension on their own? The server could tell the client, on each
connection, what ESNIKeys to use for its next connection. The client
would not have to consult the DNS for ESNIKeys for any connection but
the first, as long as the connections were frequent enough that the
ESNIKeys didn't expire.

But then I reflected: this would enable precise tracking of clients. The
server could give different keys to every client--effectively a
cookie--and thereby link past and future connections.

I think similar tracking concerns apply to the public_name design. A TLS
server could publish one global ESNIKeys in DNS. This one, because it is
used by all clients, would not help tracking (within the anonymity set
of ESNI-using clients). But the server could, whenever it receives an
encrypted_server_name extension encrypted with the global ESNIKeys,
pretend that it is unable to decrypt and reply with esni_retry_request
and a unique "cookie" ESNIKeys, which that client, and no other, would
use for future connections.

A unique ESNIKeys per client may not be feasible, if the server has to
do trial decryption using the private component of all the ESNIKeys
cookies it has handed out. There may be a more clever way to do it than
trial description. But anyway a server could define some budget, say,
256 trial decryptions per connection, and instead of a unique ESNIKeys
per client, choose one randomly from a pool of 256. Clients would then
be partitioned into 256 buckets for the lifetime of the ESNIKeys--which
other tracking technique could of course refine further.

I suppose something like this is possible even in the current draft as
written, if the TLS server and DNS server collude. The DNS server could
give different ESNIKeys to different clients, enabling not only the
linking of a DNS request and TLS connection, but the linking of several
TLS connections together.

---

From: Kazuho Oku <[email-masked]>
Date: Tue, 18 Dec 2018 14:25:30 +0900

Hi David and others involved in the work,

Thank you for the PR.

2018年12月18日(火) 8:18 David Benjamin <[email-masked]>:
>
> Hi folks,
>
> We[*] wrote up some proposed changes for draft-ietf-tls-esni that we'd like the group's thoughts on. The goal is to make ESNI more robust and eliminate a bunch of deployment risks. The PRs are linked below:
>
> The first tries to make ESNI more robust. It introduces the notion of a "public name" which gives the client an authenticated signal to retry with new keys or without ESNI at all. This mitigates DNS/server mismatches (a concern on each key rotation), and partial rollouts or rollbacks (a concern when first enabling it, plus some scenarios with TLS-terminating middleboxes).

I think that this is a logical solution, under the premise that we
need to the fix issue.

It provides an authenticated signal, thereby protecting the
server-name from even the active attackers. That makes the SNI
exchange as secure as DoH or DoT. DNS lookup and SNI are the two
moments that we need to protect the server name, and having secure
exchange for the both is IMO the correct path.

Also, I actually like the fact that there is a non-negligible amount
of overhead in the fallback path, because that would encourage people
to deploy ESNI correctly.

>
> The second recommends clients to send GREASE ESNI extensions when they do not have cached ESNIKeys available. This better meets the "Do not stick out" goal. The server behavior in the first PR gives us this for free.

I agree that having ESNI extension everywhere is an improvement. We
should do this regardless of if we take PR 124.

>
> Details are in the PRs.
>
> (The two PRs were originally written up together. I split them in two based on some feedback, but since they touch the same text, the GREASE PR includes the robustness PR. If the WG wishes to go with one but not the other, the text and details can be adjusted accordingly.)
>
> Thoughts?
>
> David
>
> [*] Steven and I wrote the text itself, with input from Adam, Ben, and Brad, all CC'd.
>

-- 
Kazuho Oku

---

From: David Benjamin <[email-masked]>
Date: Tue, 18 Dec 2018 00:45:22 -0600

Thanks for the comment! The PR did try to touch on this, but perhaps I did
a poor job of wording it:
https://github.com/tlswg/draft-ietf-tls-esni/pull/124/files#diff-4d2dc9df336bea8e17f5eb4ed7cb1107R511

The intent is you use the retry keys just for that one retry. Subsequent
connection attempts revert to the DNS-provided ones. Then the server could
correlate the initial connection and the immediately-following retry, but
that initial "connection" was discarded. It's like saying the server can
correlate the client's ClientHello and Finished. An earlier iteration even
placed the retry on the same connection, which makes the analog clearer.
(Doing it in the same connection is rather a mess, so we bounce to a new
one.)

Another possibility might be to require clients treat these like session
identifiers w.r.t. scoping and lifetime, reducing to something existing,
but that is more complex, so the simple solution seemed a better starting
point.

Does that address the concern, or have I missed something?

David

---

From: David Fifield <[email-masked]>
Date: Tue, 18 Dec 2018 00:14:08 -0700

On Tue, Dec 18, 2018 at 12:45:22AM -0600, David Benjamin wrote:
> Thanks for the comment! The PR did try to touch on this, but perhaps I did a
> poor job of wording it:
> correlate the initial connection and the immediately-following retry, but that
> initial "connection" was discarded. It's like saying the server can correlate
> the client's ClientHello and Finished. An earlier iteration even placed the
> retry on the same connection, which makes the analog clearer. (Doing it in the
> same connection is rather a mess, so we bounce to a new one.)
> 
> identifiers w.r.t. scoping and lifetime, reducing to something existing, but
> that is more complex, so the simple solution seemed a better starting point.
> 

Oh thanks -- I completely missed that.

---

From: Viktor Dukhovni <[email-masked]>
Date: Tue, 18 Dec 2018 02:27:27 -0500

On Tue, Dec 18, 2018 at 12:45:22AM -0600, David Benjamin wrote:

> An earlier iteration even placed the retry on the same connection, which
> makes the analog clearer.  (Doing it in the same connection is rather a
> mess, so we bounce to a new one.)

Any concern about the possibility that the reason the key did not
work was that the particular server had unexpected keys, but
reconnecting might land on a different server, with yet another set
of keys?  (Which is to say that I am concerned, but perhaps you're
not?).

Also connection re-establishment has considerable cost, additional
TCP roundtrips on top of the extra TLS roundtrips.

Is the HRR idea being explored in the parallel thread not viable?
[ That'd be fine by me, one less thing to worry about, but it did
seem worth exploring at first blush...  The suggestion of using it
as a fallback when there are either no keys in DNS, or they don't
work also seems like it could be viable. ]

-- 
	Viktor.

---

From: Ilari Liusvaara <[email-masked]>
Date: Tue, 18 Dec 2018 11:00:31 +0200

> out" goal. The server behavior in the first PR gives us this for free.

It seems to me that if server thinks it has ESNI enabled, but
the client does not get ESNI keys for it, then all handshakes fall
back to full handshake and session resumption will not work (as
the server is required to reject the resumption)?

Also, randomly generating the ESNI key handle does stick out, as
normally the ESNI key is releatively static (DNS caching!) across whole
group of domains and servers.

-Ilari

---

From: David Benjamin <[email-masked]>
Date: Tue, 18 Dec 2018 14:27:10 -0600

> the server is required to reject the resumption)?

It's possible I didn't word this correctly. If the client did not get ESNI
keys for the server, the client is presumably offering a non-ESNI session,
which has no resumption restrictions. The case we want to avoid is an ESNI
session being resumed at anything other than esni_accept. That's to cut out
the resumption case in {{verify-public-name}}. In particular, if we want to
tolerate partial rollouts where some servers don't support ESNI at all
while others don't at all, this scenario is a concern:

1. Server A does ESNI. Server B does not.
2. Client gets ESNIKeys from DNS and talks to server A. ESNI works out and
all. It gets a session for origin-name.
3. Client connects again and this time load balances to server B. Or maybe
server B is server A at a later time. Perhaps the ESNI implementation had a
show-stopping bug and folks had to revert for now. It will send
SNI=public-name, ESNI=origin-name, PSKs=ticket-for-origin-name.

Since server B has no clue what's going on, it'll ignore the ESNI
extension, and then process SNI + PSKs. This runs into the mess of
cross-name resumption stuff. In particular, RFC 8446 says:

   Clients MUST only resume if the new SNI value is valid for the server
   certificate presented in the original session and SHOULD only resume
   if the SNI value matches the one used in the original session.  The
   latter is a performance optimization: normally, there is no reason to
   expect that different servers covered by a single certificate would
   be able to accept each other's tickets; hence, attempting resumption
   in that case would waste a single-use ticket.  If such an indication
   is provided (externally or by any other means), clients MAY resume
   with a different SNI value.

It is not possible for the client to honor that first sentence because it
is effectively offering two names at once, so it does not know which name
the server will use to handle the ticket. This is kind of a headache. The
ESNI bit in the session disambiguates by preventing the server from
resuming under the SNI name. The semantics are: this session is associated
with the ESNI name, not the SNI name. If you don't know what that means, or
if you can't decrypt the ESNI name, please ignore it.

Without this case (and GREASE---see remark), we could just say servers MUST
NOT resume if they send esni_retry_request.

> group of domains and servers.

This is true. I don't see a clear way around that one, short of taking the
record_digest parameter out and requiring the server do a public-key
trial-decrypt for each unexpired ESNI key. (Perhaps with key mismatch
tolerance, it's no longer necessary for servers to be quite so paranoid
about keeping all their old keys around, but the trial-decrypt still seems
poor.)

I think this is still worth doing, especially as it's basically free if you
want to support rollbacks. (Making GREASE work and tolerating ESNI-clueless
servers are very strongly related. GREASE requires that servers
more-or-less ignore ESNI on key mismatch.) You also need to either observe
multiple connections or know something about the server to do this.

David

---

From: David Benjamin <[email-masked]>
Date: Tue, 18 Dec 2018 15:01:07 -0600

> of keys?  (Which is to say that I am concerned, but perhaps you're

That's a good point. This probably means the client needs to allow for a
handful of iterations before giving up, which is a bit of a nuisance.

> TCP roundtrips on top of the extra TLS roundtrips.

Agreed. The other cost is that it cannot be implemented entirely within the
TLS stack, since most TLS stacks do not establish connections. (Though ESNI
already needs caller involvement anyway to get the ESNI keys out of DNS,
and the perf overhead can be fine if you believe it's rare.)

> work also seems like it could be viable. ]

I actually still need to fully digest in that thread. I hadn't even
realized when I sent this how much the two had in common, or I would have
acknowledged it in the mail. Sorry about that. I imagine my email probably
looked odd to you. I'm glad to see we had similar ideas!

There might be some version that works? We never came up with a formulation
we really liked, and the single-connection version made a lot of things
simpler. Some of the nuisances with doing it together:

- Sticking two full handshakes on the same connection introduces several
new key changes, which will frustrate QUIC and DTLS.

- Joining two handshakes at the ServerHello or so frustrates existing
analysis, split mode, and any servers with SNI-dependent cipher parameters.
(Everyone seems to keep asking their hosting providers for silly little
checkboxes to configure TLS parameters.)

David

---

From: Ilari Liusvaara <[email-masked]>
Date: Tue, 18 Dec 2018 23:06:07 +0200

> while others don't at all, this scenario is a concern:

"The server MUST NOT resume any sessions offered by the client that
were established without ESNI.". This holds even if client sent a
dummy ESNI value.

> NOT resume if they send esni_retry_request.

This case the server should at least know if it has ESNI keys or not.
 
> about keeping all their old keys around, but the trial-decrypt still seems

Especially with asymmetric cryptography, as even the fastest ops are
about 100kcyc at least (outside some very rare pieces of laptop
hardware).

> multiple connections or know something about the server to do this.

And then there might be fun with timing attacks. This is whole
asymmetric operation, so timing signal should be rather large.

-Ilari

---

From: David Benjamin <[email-masked]>
Date: Tue, 18 Dec 2018 15:32:58 -0600

On Tue, Dec 18, 2018 at 3:06 PM Ilari Liusvaara <[email-masked]>
wrote:

> > > > We[*] wrote up some proposed changes for draft-ietf-tls-esni that
> we'd
> > > like

Sorry, that was really confusing. By "this" I meant GREASE, not trial
decryption schemes. :-)

---

From: David Benjamin <[email-masked]>
Date: Tue, 18 Dec 2018 15:39:15 -0600

(Hit send too early)

>> were established without ESNI.". This holds even if client sent a
Oops, yes, that was a typo. It should have said:

The server MUST NOT resume any sessions offered by the client that were
established with ESNI.

I'll fix that. Thanks for catching that!

---

From: Ilari Liusvaara <[email-masked]>
Date: Tue, 18 Dec 2018 23:40:05 +0200

> and the perf overhead can be fine if you believe it's rare.)

Looks like the missed ESNI penalty with this proposal is 2RTT (1 RTT
from new TCP setup, 1 RTT from unusable handshake; Yes I know that
both TCP setup and handshake are three-way, but seems like some flights
can be merged). 

> checkboxes to configure TLS parameters.)

I had idea of joining at server Finished (with key state left as-is),
which would be only 1RTT penalty (unuseable handshake), but this has
the same issues with QUIC and DTLS. And trying to make it compatible
with dummy ESNI does some rather annoying things to the TLS state
machine.

-Ilari