Subject: QUIC Version Negotiation Extension

From: David Schinazi <[email-masked]>
Date: Mon, 4 Nov 2019 12:29:41 -0800

Hi everyone,

>From the recent discussion on multiple PRs, it appears that there might be
a need for downgrade-safe QUIC version negotiation, earlier than we
expected.

EKR and I updated our extension to hopefully suit these needs:
https://tools.ietf.org/html/draft-schinazi-quic-version-negotiation-02

Comments welcome here or on the GitHub (link in draft).

Thanks,
David

---

From: Mikkel Fahnøe Jørgensen <[email-masked]>
Date: Mon, 4 Nov 2019 22:47:29 +0100

(reposting on proper topic)

Some random observations reading through the document

- Is the order relevant in the receiver version list?

- It is tempting to just hash the received version list, but that requires
agreeing on an algorithm, unless the algorithm is stated to be specific to
that version, which is complicated.

- VERSION_NEGOTIATION_ERROR vs drop - I’m not sure it is a good idea to
close the connection. The initials are public so it is possible to inject
false versions. There are probably many other similar attacks we don’t
bother with, but still …

- Checking that a transport parameter field is the same as the long header
version seems redundant - why have those fields? Is it because the Initial
header fields are not sufficiently protected via TLS magic (or similar)?

- We generally use varint, but why have varints mixed with 32-bit version
lists? I suggest making the list length 32-bit for easier processing.

- Downgrade - I’m a bit worried about state management and server
redeployments. A server could reject a valid packet because an Initial was
routed to a new server. (Reading further, I see this is addressed). This is
probably a pragmatic solution, but it has an assumption about eventual
global coordination. I suspect something could be done here with tokens or
CID routing, but it is not trivial.

- Security Considerations - perhaps it is worth noting the transport
parameters need additional protection beyond the Initial packet protection?
This follows from TLS, but if TLS is not being used, this can version
negotiation even if other parts of the protocol version is not sensitive to
this in that particular version.

- Greasing 0x?a?a?a?a - I’m not really sure about the point of this.
Especially if we get salted versions in separate proposal.

- It is not immediately obvious if version negotiation can go on and on, or
if it settles after at most one roundtrip one way or the other. This might
depend on the client QUIC version, but that is a fuzzy term in this context.

- Improve discussion of Previously Attempted Version. While the
requirements are readable, the purpose of doing this check is less obvious.
Presumably this deals with downgrade attacks, but more explanation would be
appreciated.

Mikkel

On 4 November 2019 at 21.30.10, David Schinazi ([email-masked])
wrote:

Hi everyone,

Thanks,
David

---

From: David Schinazi <[email-masked]>
Date: Mon, 4 Nov 2019 14:09:20 -0800

Thanks for reading Mikkel! Responses inline.

> - Is the order relevant in the receiver version list?

Yes all lists of versions are ordered.

> that version, which is complicated.

Which list are you referring to? For all the ones in the draft, the peer
needs access to all the elements so I don't think a hash would help.

> false versions. There are probably many other similar attacks we don’t

Once we're this far in the handshake, we cannot recover from this error.
Dropping the packet will only make things worse.

> header fields are not sufficiently protected via TLS magic (or similar)?

Yes, this makes sure an attacker cannot spoof the version field in initial
packets.

> lists? I suggest making the list length 32-bit for easier processing.

QUICv1 uses varints pretty much everywhere, we're sticking to that design
here.

> CID routing, but it is not trivial.

Sounds interesting. Do you have a concrete proposal?

> this in that particular version.

draft-ietf-quic-transport section 7 requires transport parameters to be
authenticated.

> Especially if we get salted versions in separate proposal.

We follow the QUIC reserved version design here.

> - It is not immediately obvious if version negotiation can go on and on,
> or if it settles after at most one roundtrip one way or the other. This
> might depend on the client QUIC version, but that is a fuzzy term in this
> context.
>

It can only happen once, though it might be a good idea to make that
clearer.

> Presumably this deals with downgrade attacks, but more explanation would be

The downgrade prevention section discusses the purpose of this field. What
details do you think we should add?

Mikkel

---

From: Christian Huitema <[email-masked]>
Date: Mon, 4 Nov 2019 14:31:25 -0800

On 11/4/2019 2:09 PM, David Schinazi wrote:
> On Mon, Nov 4, 2019 at 1:47 PM Mikkel Fahnøe Jørgensen
> <[email-masked] <mailto:[email-masked]>> wrote:
>
>     - It is tempting to just hash the received version list, but that
>     requires agreeing on an algorithm, unless the algorithm is stated
>     to be specific to that version, which is complicated.
>
>
> Which list are you referring to? For all the ones in the draft, the
> peer needs access to all the elements so I don't think a hash would help.
>  
>
>     - VERSION_NEGOTIATION_ERROR vs drop - I’m not sure it is a good
>     idea to close the connection. The initials are public so it is
>     possible to inject false versions. There are probably many other
>     similar attacks we don’t bother with, but still …
>
>
> Once we're this far in the handshake, we cannot recover from this
> error. Dropping the packet will only make things worse..

Can we think about that some more?

It is true that there are many ways to wreck a connection if the
attacker can freely tweak bits in the client hello. There is no need to
mess with transport parameters -- just changing the value of the client
Random will do that. The server will create a connection context and
send a reply, but the client won't compute the proper handshake key and
the connection will fail.

An attacker on path can kill all packets and close the connection, but
an attacker on the side can only inject "magic" packets. The VN could
become one such magic packet. Can the man on the side send the VN
quickly, so the client sends a "VN inspired" client hello? What would it
take to ensure that the connection is robust against such attacks?

-- Christian Huitema

---

From: Mikkel Fahnøe Jørgensen <[email-masked]>
Date: Mon, 4 Nov 2019 23:35:22 +0100

Response also inline

I might have missed it, but otherwise I think this should be mentioned
explicitly.

I am referring the received list. The client receives the list. The server
(modulo redeployment/routing) knows its own list and does not need to read
it, it just needs proof that the response matches what it initially sent.
This could be done with a hash. (If I understand correctly). But maybe not
because the list is filtered by clients input so that would be many hashes
to track.

It is that a given across all versions, without mentioning it explicitly
here? This links to the at most one roundtrip.

Not off hand, but the idea is that the server encodes some context in a
token, that an upgraded server can understand such that it can either
impersonate the old version or reject the handshake. As for CID routing: (I
don’t recall if new initials with new version must carry same original CID,
and this might be version specific) - but if the a second Initial routes
the same place, similar to 0-RTT, you can limit the impact of server
deployment coordination to the one or the few servers affected by that CID.
If a version negotiation packet decides a new CID via the destination CID
field, a server farm is also able to route traffic to a segment with a
predictable version scope - for example by never updating some servers on
even hours and always redirect vneg to a subcluster that is not upgrading
and which therefor has a predictable version scope.

but that is for QUIC v1. QUIC v3 might be for constrained devices that do
not use TLS and which do not migrate and which only has two non-hardcoded
transport parameters which are verified in 1-RTT in order to achieve a
light handshake. What would vneg do then? Notably such a version would
depend heavily on vneg because it really needs the one version that works
with its hardware accelerator, or whatever.

Generally QUIC does not argue why something is done, although sometimes
providing motivation (like you do here - downgrade prevention), but I’d
still like to better understand why this prevents a downgrade and why a
downgrade is such a bad thing, and how such an attack could happen, and why
comparing the previous version would prevent that from happening. Because
this is such a core element if the entire document.

---

From: David Schinazi <[email-masked]>
Date: Mon, 4 Nov 2019 14:45:35 -0800

On Mon, Nov 4, 2019 at 2:33 PM Christian Huitema <[email-masked]>
wrote:

> Dropping the packet will only make things worse..
>
> It is true that there are many ways to wreck a connection if the attacker
> can freely tweak bits in the client hello. There is no need to mess with
> transport parameters -- just changing the value of the client Random will
> do that. The server will create a connection context and send a reply, but
> the client won't compute the proper handshake key and the connection will
> fail.
>
> An attacker on path can kill all packets and close the connection, but an
> attacker on the side can only inject "magic" packets. The VN could become
> one such magic packet. Can the man on the side send the VN quickly, so the
> client sends a "VN inspired" client hello? What would it take to ensure
> that the connection is robust against such attacks?
>
While I agree it would be nice to prevent these attacks, I don't think it's
an achievable goal in QUICv1 at this point. The injection-only attacker can
already break the connection by sending a forged ServerHello, Version
Negotiation doesn't really change that. I'm not coming up with an easy way
to remove this issue.

---

From: David Schinazi <[email-masked]>
Date: Mon, 4 Nov 2019 14:55:57 -0800

> I might have missed it, but otherwise I think this should be mentioned
Isn't it mentioned in the definitions of these fields?

> because the list is filtered by clients input so that would be many hashes
I don't know what the received list is, there's nothing with that name in
the draft.

> here? This links to the at most one roundtrip.
That's a given across all current versions. New versions can override the
text in this spec.

> and which therefor has a predictable version scope.
The current design of connection IDs does not guarantee consistent routing
on client-generated server connection IDs, so I don't think we can rely on
it.

> with its hardware accelerator, or whatever.
I think QUICv3 should also provide this property, otherwise it'll be likely
to be unsafe. If a future version doesn't have authenticated transport
parameters then we'll have to create a separate downgrade prevention
mechanism. But we can cross that bridge when we get there.

> this is such a core element if the entire document.
It sounds like we could be clearer. Would you be able to send us a PR?

---

From: Mikkel Fahnøe Jørgensen <[email-masked]>
Date: Tue, 5 Nov 2019 00:07:01 +0100

Yes, rereading that passage, it does mention order.

>> requires agreeing on an algorithm, unless the algorithm is stated to be
>> specific to that version, which is complicated.
>>
>

Received Negotiation Version Count + following element

So this is where I’m not 100% on how it works, but VNEG packet does, at
least in principle, allow for a return destination CID, so the next Initial
packet can use that destination instead of a random original CID. And
likewise, if the response is not via VNEG, the servers initial can the same
(on known versions).

Sure, but perhaps mention in Security Considerations, that the design
depends on this, if it is not considered blatantly obvious.

I’ll consider, but I’m stretched a bit thin as it is.

---

From: Mikkel Fahnøe Jørgensen <[email-masked]>
Date: Tue, 5 Nov 2019 00:11:38 +0100

Below

On 5 November 2019 at 00.07.01, Mikkel Fahnøe Jørgensen ([email-masked])
wrote:

I came to think that because VNEG is open, an attacker could inject a VNEG
with a specific desitination CID to multiple clients and those trigger an
indirect flooding attack by concentrating traffic via previously aquired
destination CIDs. Because the servers router would/should randomize
unauthenticated CIDs an attacker cannot just inject a random CID.

---

From: David Schinazi <[email-masked]>
Date: Mon, 4 Nov 2019 15:13:15 -0800

On Mon, Nov 4, 2019 at 3:07 PM Mikkel Fahnøe Jørgensen <[email-masked]>
wrote:

>> I am referring the received list. The client receives the list. The
>> server (modulo redeployment/routing) knows its own list and does not need
>> to read it, it just needs proof that the response matches what it initially
>> sent. This could be done with a hash. (If I understand correctly). But
>> maybe not because the list is filtered by clients input so that would be
>> many hashes to track.
>>
Those are received by the server. And the server needs to access all the
the versions there in case it randomizes the reserved versions or has
different versions on different servers in the fleet.

> likewise, if the response is not via VNEG, the servers initial can the same
Version Negotiation packets MUST use the same CIDs as the client's initial.

> I think QUICv3 should also provide this property, otherwise it'll be
> likely to be unsafe. If a future version doesn't have authenticated
> transport parameters then we'll have to create a separate downgrade
> prevention mechanism. But we can cross that bridge when we get there.
>
I'll admit I thought this was obvious, since it relies on transport
parameters. We'll take a PR though.

---

From: "Martin Thomson" <[email-masked]>
Date: Tue, 05 Nov 2019 10:16:01 +1100

Hi David,

I think that this is broadly the right mechanism.  It would be good to validate it more thoroughly.  

The model and framing could use a little work.

I struggled a little with the framing here because the split between incompatible and compatible versions is very important, but also unclear.  The definition of "compatible" is left for Section 7, but it seems like it should be right up front.

The framing I would choose puts the model up front.  The model that you have have two layers that are applied progressively:

1. incompatible version negotiation, where the server sends a Version Negotiation packet in response to a version it does not understand.

2. compatible version negotiation, where the server can generate interpret a version X Initial packet and continue with version Y.

Regarding the model, the definition of "compatible" needs work.

I observe that you only need a mapping from "Initial"-equivalent packets in X to the same in Y - and maybe the ability to generate a Retry in version X (more below) - in order for this to work.  That's where there is a clear functional mapping from a version X packets that initiate the connection to something that fills the same purpose in version Y.  

But the definition you have for "compatible" implies that the mapping is bijective, which I don't think is necessary.  As the draft says, version Y might define (and allow) new frame types in its Initial-equivalent packet(s), but as long as those are either optional or can be synthesized from X, that's OK.  It isn't necessary that every Y have a functional representation in X.  We might not want to define a way to negotiate an older version from a newer one (or at least not for every case).

The ability to generate a Retry stretches this definition a little.  Can you explain why you can't generate a version Y Retry?  I have an inkling, but it's not fully formed.  It complicates the model more than I'd like to have it this way around.

On Tue, Nov 5, 2019, at 07:29, David Schinazi wrote:
> Hi everyone,
> 
> From the recent discussion on multiple PRs, it appears that there might 
> be a need for downgrade-safe QUIC version negotiation, earlier than we 
> expected.
>

---

From: Mike Bishop <[email-masked]>
Date: Wed, 6 Nov 2019 18:29:31 +0000

Some notes:

  *   Section 3:  “Version negotiation takes place after the retry cycle is over.”  This is only possible for compatible versions; if the server does not support the version the client attempted, it obviously cannot send a Retry packet in that version, because Retry packets are version-specific.
  *   Section 3:  “Servers must retain the ability to process the Initial packet from older versions….”  No, it means that if the server wants to avoid a VN packet, it needs to be able to process the Initial packet from the version the client tried.
  *   Section 4:  Rather than putting a count-prefixed list of versions, perhaps consider just embedding the VN packet payload?  It’s equivalent, but easier to explain.
  *   Section 7:  Clearly it is possible to possible to add new frame types to Initial packets in a future version, I think what you mean to say is that this is a change that would break compatibility between versions.

As a general statement on the document, you assume throughout that all versions have packet types, contain packet types called Initial and Retry, and that Initial packets contain frames.  The odds of those things being true across QUIC versions is likely high, but they’re not invariants, so you can’t assume them here.  You need to state general principles, and then use concepts from QUICv1 as examples of the principle.

For example, rather than discussing version compatibility in terms of Initial packets, you might want to say that versions A and B are “compatible” if the first flight of packets sent by the client in version A carries enough information for the server to generate the corresponding flight in version B, the server speaks version B, and is able to parse at least the first flight of version A.

Also not discussed is the case where the server speaks version(s) which are compatible with what the client offered, but doesn’t speak the version the client offered, even enough to generate the response.  With the right definition of “compatible,” this just falls into the “incompatible” path and works for the server, but you need to call out the fact that the client shouldn’t consider it an error if the server uses the incompatible path between versions that the client thinks are compatible.

From: QUIC <[email-masked]> On Behalf Of David Schinazi
Sent: Monday, November 4, 2019 3:30 PM
To: QUIC <[email-masked]>
Subject: QUIC Version Negotiation Extension

Hi everyone,

From the recent discussion on multiple PRs, it appears that there might be a need for downgrade-safe QUIC version negotiation, earlier than we expected.

Thanks,
David

---

From: David Schinazi <[email-masked]>
Date: Wed, 6 Nov 2019 11:17:32 -0800

Thanks Mike and Martin.

I agree with your comments. The current draft evolved from the previous
revision that only supported compatible versions, and I agree that more of
the text needs to be refactored or rewritten. I've filed GitHub issues to
make sure we don't lose track of your specific comments:
https://github.com/ekr/draft-schinazi-quic-version-negotiation/issues/4
https://github.com/ekr/draft-schinazi-quic-version-negotiation/issues/5

David

On Wed, Nov 6, 2019 at 10:29 AM Mike Bishop <[email-masked]> wrote:

> Some notes:
>
>    - *Section 3:  *“Version negotiation takes place after the retry cycle
>    is over.”  This is only possible for compatible versions; if the server
>    does not support the version the client attempted, it obviously cannot send
>    a Retry packet in that version, because Retry packets are version-specific.
>    - *Section 3:  *“Servers must retain the ability to process the
>    Initial packet from older versions….”  No, it means that *if the
>    server wants to avoid a VN packet*, it needs to be able to process the
>    Initial packet from the version the client tried.
>    - *Section 4:  *Rather than putting a count-prefixed list of versions,
>    perhaps consider just embedding the VN packet payload?  It’s equivalent,
>    but easier to explain.
>    - *Section 7:  *Clearly it is possible to possible to add new frame
>    types to Initial packets in a future version, I think what you mean to say
>    is that this is a change that would break compatibility between versions.
>
>
>
> As a general statement on the document, you assume throughout that all
> versions have packet types, contain packet types called Initial and Retry,
> and that Initial packets contain frames.  The odds of those things being
> true across QUIC versions is likely high, but they’re not invariants, so
> you can’t assume them here.  You need to state general principles, and then
> use concepts from QUICv1 as examples of the principle.
>
>
>
> For example, rather than discussing version compatibility in terms of
> Initial packets, you might want to say that versions A and B are
> “compatible” if the first flight of packets sent by the client in version A
> carries enough information for the server to generate the corresponding
> flight in version B, the server speaks version B, and is able to parse at
> least the first flight of version A.
>
>
>
> Also not discussed is the case where the server speaks version(s) which
> are compatible with what the client offered, but doesn’t speak the version
> the client offered, even enough to generate the response.  With the right
> definition of “compatible,” this just falls into the “incompatible” path
> and works for the server, but you need to call out the fact that the client
> shouldn’t consider it an error if the server uses the incompatible path
> between versions that the client thinks are compatible.
>
>
>
> *From:* QUIC <[email-masked]> *On Behalf Of * David Schinazi
> *Sent:* Monday, November 4, 2019 3:30 PM
> *To:* QUIC <[email-masked]>
> *Subject:* QUIC Version Negotiation Extension
>
>
>
> Hi everyone,
>
>
>