Original text:
The server MUST NOT reply with a version that is not present in the client's compatible versions, unless it is the original version. <del> If the server does not find a compatible version, it will use the original version if it supports it, and if it doesn't then the server will perform incompatible version negotiation instead, see incompat- vn. </del>  Note that, after the first flight is converted to the negotiated version, the handshake completes in the negotiated version.  The by only including the Chosen Version in the Other Versions field of the Version Information Transport Parameter.  5. During the handshake, endpoints will exchange Version Information, the version in use for the connection is the version that the server sent in the Chosen Version field of its Version Information.  That remains true even if other versions were used in the Version field of <del> long headers at any point in the lifetime of the connection; endpoints MUST NOT change the version that they consider to be in use based on the Version field of long headers as that field could be forged by attackers. </del>  7.

Reviewer comments:
The currently states: Let's assume the following scenario: the server supports both v1 and v2, and is configured to perform compatible negotiation from 1 to 2 when possible client sends a QUIC v1 initial with "Other Versions" = (1, 2) the server decides to switch to v2, and sends its initial ServerHello as v2, followed by a v2 handshake packet with the first half of the certificate - the transport parameters will follow in a later packet when the client receives the v2 handshake packet, it technically doesn't yet know the "negotiated version" because it hasn't yet parsed the transport parameters Conceptually, this sounds like a terminology issue with the VN draft: at that point in time the client does know that the server wants to use v2, because it's parsed a v2 ServerHello - but I think we need a name for that in the VN draft so that the v2 draft can refer to it for the MUST above. cc NAME NAME -- the more I think about it the more I think that to "use instead of validate" is making the spec and implementation writing a lot harder - the idea makes sense in theory, but in practice it complicates the handshake. Maybe we go back to something like "the negotiated version is the one seen in long headers, and the Chosen Version in the TP exists to validate that it matches after the fact"
I agree. Let's get the terminology right.
This WFM. FWIW, my implementation at the client uses long header packets to "negotiate" the version at the client; the transport parameters are used at the server. Validation at the server is not necessary, but the validation based on a chosen version is right for the client.
My reading of the current editors' draft is that this is a no-op: AFAICT, the term "negotiated version" refers to whatever is the version field of the first server flight. If the editors agree, please close this issue.
I'm reading editor's draft. It says that both peers MUST parse Version Information. This is good. However, a client MUST validate Version Information only on incompatible version negotiation ("acted on a Version Negotiation packet"). I think the same validation should be done on compatible version negotiation.
Can you elaborate? What validation should the client do if it hasn't reacted to a VN packet?
I think that Chosen Version MUST be the same as the version in the Initial packet from the server.
That doesn't quite work - sometimes you might use some packets of the original version (see and ). The security property we want is to ensure that after the handshake, the server's "Chosen Version" is the one used by the connection. That property is guaranteed by this text from the end of Section 6:
NAME So, why does a server have to send back Version Information (which is not validated at all) for compatible negotiation?
This exists to prevent an attacker from modifying the version: since the authenticity of version information is protected during the handshake, it guarantees that, once the handshake is complete, the client is using the version that the server chose.
Do you mean that a client should check if Chosen Version is the same as using version when handshake is complete?
Yes, that's one way of implementing this
The exchange with a successful negotiation may look like: The discussion of issue shows the value of having the server switch to the new version as soon as the negotiation completes. But then, the client can only decrypt the server response if it verifies that "version = B" is an expected response, and then starts using the version B encryption keys, etc. There is very little protection there. Are we worried that third parties might spoof a successful negotiation?
I'm not sure I understand the problem here. The version will be retroactively blessed when the handshake completes, just like the rest of the handshake negotiation.
+1 to what NAME said. NAME this is addressed in :
Yes, I get that the whole posture is "best effort until the handshake is complete, detection of issues during the completion", and that the final verification will detect spoofing and terminate the connection. So this is basically one more avenue for injecting spoofed packets during the handshake, causing the connection to crash. We may or may not want to mention that.
It's already trivial to cause a handshake failure by injecting spoofed packets, this doesn't make it any worse. So I don't think we absolutely need to mention this but I wouldn't oppose an addition to Security Considerations if you want to write a PR.
I am implementing that right now, and I am trying to understand when to accept this "automatic" negotiation. This may be a V1 only issue, but I suspect we will find it with other versions as well. The basic protection is that packets received with the wrong version are considered bogus, and are just ignored. I need to create an exception to that rule when negotiation is in progress, accept packet if the version would be accepted after successful negotiation, and do as if the negotiation had already succeeded. The obvious restriction is to only do that on the client, before the handshake is complete. But that still opens a large time window for attacks. Restricting to "TP negotiation not complete yet" is a bit better than "handshake not complete yet", but still leaves a large window. Restricting to "this is the first packet from the server" does not work, because the server could be sending initial acknowledgements. Reasoning such as "no crypto frame received yet" might work, but the existence of "crypto frames" is version dependent.
NAME what problem are you trying to solve? As long as initial receive keys are present, it's trivial for an attacker to make the handshake fail. The version field doesn't change that in any way, and doesn't offer any protection. Or am I misunderstanding something here?
There is indeed a denial-of-service window lasting as long as initial packets are accepted. It stops when the handshake keys are computed, at which point picoquic stops processing initial packets. I am trying to ensure that supporting version negotiation does not extend that window. But yes, I fully get the general point. If we want to protect against packet injection during the handshake, we have to use some crypto. Maybe derive a token from ECH, or something like that.
Thanks, I totally agree with your goal. So far I don't see any vector by which this draft extends the DoS window, but adding text to Security Considerations (or perhaps to Considerations for Future Versions) could help ensure we avoid this pitfall in the future.
As we've merged this into the quicv2 draft, is there anything else to say in VN?
Between what we have in the QUICv2 draft and PR here, this should be covered. So I'm going to close this with no action. Please reopen/comment if you disagree.
I am reading the draft, and I have doubts on how this is supposed to affect encryption keys. Suppose a classic exchange: the client sends a couple of initial packets, the server parses them and acknowledges them, the server then sends initial and handshake packets. I think we have the following constraints: 1) The server will only complete the compatible version negotiation after receiving "enough" of the client's initial packets, i.e., enough to parse the QUIC transport parameters. 2) The server transport parameters are sent as encrypted extensions. The client will only receive them after receiving "enough" handshake packets. 3) The client could learn that the server has chosen a new version at any point during the handshake, by receiving a new version field in a long header packet -- Initial or handshake. So we get possible variations: 1) Server receives client first flight with V1, may reply with V1 ACK until the flight has been received, but uses V2 for the reminder of the handshake, i.e., sends server hello in a V2 Initial packet, sends the encrypted parameters and the reminder of the server first flight in V2 handshake packets. Upon reception and successful decryption of initial V2 packet, the client switches to V2. 2) Same but server sends all Initial packets using V1, all handshake packets using V2. Client switches to V2 upon reception and successful decryption of V2 handshake packet. 3) Server keeps using V1 for initial and handshake packets, switches to V2 for 1RTT packets. Client keeps using V1 until the handshake completes, switches to V2 for 1RTT packets. Variation 3 is the easiest to implement, but I suspect that "the server converts the client's first flight to that version and replies to the client as if it had received the converted first flight" actually implies variation 1. That variation requires that the client makes a pretty expensive decision, recomputing the encryption keys using the new version's parameters upon receiving a packet with a "negotiated" version in the version header. Since the encryption key of initial packets is well known, this is yet another avenue for third parties to send poisoned packets and cause the client to fail the connection, but I suppose that will not stop us because there are many such attacks already. In any case, the current text is not very explicit. I think that we will save a lot of interop issues by being more explicit, maybe presenting the expected flow of packets.
I agree that timing of key installation is severe. From the anti-ossification point of view, V2 should be used as much as possible. So, variant 1 is ideal but it is hard to implement it.
I am doing a thought experiment, hypothesizing a V3 version of QUIC in which the handshake changes radically. Instead of using just Initial and Handshake packets, the Handshake would also use Zero RTT packets, because we magically found a way to always have a Resume Token available, or maybe a shared password. In the new V3 version we would have: From client: Initial packet carry Client Hello and some unencrypted parameters. From client: Zero RTT packets carry encrypted handshake parameters From server: Zero RTT packets carry Server Hello and unencrypted extensions. From server: Handshake packets carry reminder of server flight From client: Handshake packet carry Client's Fin. Maybe V3 is designed to prevent third parties from spoofing the server. In the transition phase, we would want something like: From client: Initial V1 packet carry Client Hello and all parameters. From server: Zero RTT V3 packets carry Server Hello and unencrypted extensions. etc... That would preserve the key "defense from spoofing" when the negotiation succeeds. But it does require something like variation .
The current text in the draft was written with Google's implementation in mind, where the server will never send a packet until it has received the full client hello. Therefore the intent was: client sends first flight in v1 if some of those packets get lost, the client retransmits all of them once server receives the full client hello, it can decide to perform compatible VN with v2 all server packets are then sent with v2 This issue makes me realize that some implementations may wish to acknowledge initial packets before receiving the full client hello, which I hadn't thought of. If the client hello ever becomes more than a handful of packets, this might be useful. I would prefer variation 1 (server sends initial acks of client's first flight in v1 but everything else in v2, including the server hello) as that would match our implementation model, and has the property of switching to the newer version ASAP. I agree that we should be clearer, though I'm not sure if that belongs in this draft since this draft intends to be mostly version agnostic. Perhaps that's something that needs to be added to the QUICv2 draft in the section when it declares itself compatible with QUICv1?
Yes, it would be nicer if we add VN scenarios to the V2 draft instead of the VN draft.
I think you cannot pass the burden to every new version draft. You have to be very explicit about what is expected. On the implementation part: picoquic uses picotls. When initial or handshake packets are received, picoquic passes the content of the "crypto" frames to picotls. Picotls does its own thing according to the TLS 1.3 logic. At some point it will callback into picoquic for events like "handshake secret is now available" or "transport parameter option has been received". In my tests, the order of events on the client was: set zero RTT encode secret (when sending the client hello) receive the server initial packet with new version set (which causes client to declare negotiation successful) set handshake encode secret (when receiving the server hello extensions) set handshake decode secret (when receiving the server hello extensions) process the transport options (when receiving the server first flight) set the 1rtt encode secret (after receiving the server first flight) set the 1rtt decode secret (father receiving the server first flight) On the server, the order is: set the zero RTT decode secret (when receiving the client hello) process the transport options (when receiving the client hello) and do the negotiation set handshake encode secret (after composing the server hello) set handshake decode secret (after composing the server hello) send the server initial packet with new version set (which causes client to declare negotiation successful) set the 1rtt encode secret (after sending the server first flight) set the 1rtt decode secret (father receiving the client finished) In the specific case of the V1 to V2 "reprocessing" means replacing the initial key computed with the V1 initial secret by one that uses the V2 secret. However, on the client side, we have to do this reprocessing in a speculative manner, upon receiving a first long header packet with version = V2.
There is something weird happening with 0-RTT. See issue
I don't understand, how can this draft be explicit about future versions that don't exist yet? Is that a problem?
LGTM

Revised text:
The server MUST NOT reply with a version that is not present in the client's compatible versions, unless it is the original version.  <ins> Clients will be made aware of compatible version negotiation by seeing a change in the QUIC long header Version field.  It is possible for the server to initially send packets with the original version before switching to the negotiated version (for example, this can happen when the client's Version Information structured spans multiple packets; in that case the server might acknowledge the first packet in the original version and later switch to a different negotiated version). </ins> Note that, after the first flight is converted to the negotiated version, the handshake completes in the negotiated version.  The by only including the Chosen Version in the Other Versions field of the Version Information Transport Parameter. <ins> If the server does not find a compatible version, it will use the original version if it supports it, and if it doesn't then the server will perform incompatible version negotiation instead, see incompat- vn. </ins> 5. During the handshake, endpoints will exchange Version Information, the version in use for the connection is the version that the server sent in the Chosen Version field of its Version Information.  That remains true even if other versions were used in the Version field of  <ins> long headers at any point in the lifetime of the connection.  In particular, since during compatible version negotiation the client is made aware of the negotiated version by the QUIC long header version (see compat-vn), clients MUST validate that the server's Chosen Version is equal to the negotiated version; if they do not match, the client MUST close the connection with a version negotiation error. This prevents an attacker's ability to influence version negotiation by forging the Version long header field. </ins> 7.