Subject: Re: [quicwg/base-drafts] Authenticate connection IDs (#3499)

From: David Schinazi <[email-masked]>
Date: Fri, 08 May 2020 16:34:36 -0700

@DavidSchinazi commented on this pull request.

@ekr's review prompted me to look at this closer, and I think we should resolve these discrepancies before merging.

> @@ -4609,10 +4662,9 @@ The following transport parameters are defined:
 original_connection_id (0x00):
 
 : The value of the Destination Connection ID field from the first Initial packet
-  sent by the client.  This transport parameter is only sent by a server.  This
-  is the same value sent in the "Original Destination Connection ID" field of a
-  Retry packet (see {{packet-retry}}).  A server MUST include the
-  original_connection_id transport parameter if it sent a Retry packet.
+  sent by the client; see {{cid-auth}}.  This transport parameter is only sent
+  by a server.  The value of this parameter depends on whether the server

I don't understand the sentence "The value of this parameter depends on whether the server sent a Retry packet". From reading the rest of this PR it sounds like this TP has the same value no matter what happened with RETRY. Am I missing something?

> @@ -4786,17 +4838,28 @@ active_connection_id_limit (0x0e):
   When a zero-length connection ID is being used, the active_connection_id_limit
   parameter MUST NOT be sent.
 
+initial_connection_id (0x0f):
+
+: The value that the endpoint included in the Source Connection ID field of the
+  first Initial packet it sends during the handshake; see {{cid-auth}}.

nit: I would s/packet it sends during the handshake/packet it sent on this connection/

> @@ -1475,6 +1475,49 @@ lifetime of a connection, especially in response to connection migration
 ({{migration}}); see {{issue-cid}} for details.

+## Authenticating Connection IDs {#cid-auth}
+
+The choice each endpoint makes about connection IDs during the handshake is
+authenticated by including all values in transport parameters; see
+{{transport-parameters}}. This ensures that all connection IDs used for the
+handshake are also authenticated by the cryptographic handshake.
+
+Each endpoint includes the value of the Source Connection ID field from the most

Should this be the "first packet it sent" as opposed to "most recent" for consistency?

> @@ -1475,6 +1475,49 @@ lifetime of a connection, especially in response to connection migration
 ({{migration}}); see {{issue-cid}} for details.

+## Authenticating Connection IDs {#cid-auth}
+
+The choice each endpoint makes about connection IDs during the handshake is
+authenticated by including all values in transport parameters; see
+{{transport-parameters}}. This ensures that all connection IDs used for the
+handshake are also authenticated by the cryptographic handshake.
+
+Each endpoint includes the value of the Source Connection ID field from the most
+recent Initial packet it sent in the initial_connection_id transport
+parameter; see {{transport-parameter-definitions}}. A server includes the
+Destination Connection ID field it receives in original Initial packets from the

I would rephrase this to "A server includes the Destination Connection ID field from the first Initial packet it received from the client in the original_connection_id transport parameter; note that if the server sent a Retry packet this refers to the first Initial packet received before sending the Retry packet."

> @@ -1475,6 +1475,49 @@ lifetime of a connection, especially in response to connection migration
 ({{migration}}); see {{issue-cid}} for details.

+## Authenticating Connection IDs {#cid-auth}
+
+The choice each endpoint makes about connection IDs during the handshake is
+authenticated by including all values in transport parameters; see
+{{transport-parameters}}. This ensures that all connection IDs used for the
+handshake are also authenticated by the cryptographic handshake.
+
+Each endpoint includes the value of the Source Connection ID field from the most
+recent Initial packet it sent in the initial_connection_id transport
+parameter; see {{transport-parameter-definitions}}. A server includes the
+Destination Connection ID field it receives in original Initial packets from the
+client - Initial packets received by the server prior to sending a Retry packet
+- in the original_connection_id transport parameter. After sending a Retry

I would s/After sending a/If it sent a/

> +The choice each endpoint makes about connection IDs during the handshake is
+authenticated by including all values in transport parameters; see
+{{transport-parameters}}. This ensures that all connection IDs used for the
+handshake are also authenticated by the cryptographic handshake.
+
+Each endpoint includes the value of the Source Connection ID field from the most
+recent Initial packet it sent in the initial_connection_id transport
+parameter; see {{transport-parameter-definitions}}. A server includes the
+Destination Connection ID field it receives in original Initial packets from the
+client - Initial packets received by the server prior to sending a Retry packet
+- in the original_connection_id transport parameter. After sending a Retry
+packet, a server also includes the Source Connection ID field from the Retry
+packet in the retry_connection_id transport parameter.
+
+The values provided by a peer for these transport parameters MUST match the
+values that an endpoint used in the Destination Connection ID field of Initial

This sentence appears to apply to all 3 transport parameters, but it mentions Destination CID. I would s/Destination/Source and Destination/

> @@ -4395,13 +4439,22 @@ A client MUST NOT reset the packet number for any packet number space after
 processing a Retry packet; {{packet-0rtt}} contains more information on this.
 
 A server acknowledges the use of a Retry packet for a connection using the
-original_connection_id transport parameter (see
-{{transport-parameter-definitions}}).  If the server sends a Retry packet, it
-MUST include the Destination Connection ID field from the client's first
-Initial packet in the transport parameter.
+retry_connection_id transport parameter; see
+{{transport-parameter-definitions}}.  If it sends a Retry packet, the server
+also subsequently includes the value of the Source Connection ID field from
+the Retry packet in its retry_connection_id transport parameter.
+
+A server always includes an original_connection_id transport parameter. If it

I think we can remove this entire paragraph as it appears to be redundant with other parts of this PR, and it seems to have discrepancies.

-- 
You are receiving this because you are subscribed to this thread.
Reply to this email directly or view it on GitHub:
https://github.com/quicwg/base-drafts/pull/3499#pullrequestreview-408576300

---

From: Christian Huitema <[email-masked]>
Date: Fri, 08 May 2020 17:08:06 -0700

@huitema commented on this pull request.

Everywhere else, "initial_connection_id" is defined as _The value that the endpoint included in the  **Destination** Connection ID field of the first Initial packet it sends during the handshake._
I think that the simplest solution is to s/Source/Destination/

-- 
You are receiving this because you are subscribed to this thread.
Reply to this email directly or view it on GitHub:
https://github.com/quicwg/base-drafts/pull/3499#discussion_r422425820

---

From: Christian Huitema <[email-masked]>
Date: Fri, 08 May 2020 17:47:10 -0700

We already have a definition for an original_connection_id transport parameter (see section  17.2.5.3. of the transport spec, Continuing a Handshake After Retry). From the point of view of the client, we have the following:

1) Without retry:
* Initial DCID: DCID of the first Initial packet sent by the client.
* Server SCID: CID chosen by the server to identify the connection during the handshake. Also used by the client for further Initial or Handshake packets once the client receives a first handshake message from the server.

2) With Retry:
* Original DCID: the DCID in the first Initial packet sent by the client, when that packet triggered a Retry.
* Server suggested DCID: the SCID of the Retry packet sent by the server. 
* Initial DCID: the DCID in the first Initial packet sent by the client in response to the Retry. MUST be set to the Server suggested DCID.
* Server SCID: the SCID chosen by the server to identify the connection. May or may not be the same as Server suggested DCID.

Maybe we should tighten these definitions.

-- 
You are receiving this because you are subscribed to this thread.
Reply to this email directly or view it on GitHub:
https://github.com/quicwg/base-drafts/pull/3499#discussion_r422432965

---

From: David Schinazi <[email-masked]>
Date: Fri, 08 May 2020 17:50:19 -0700

I don't think that's right @huitema. `initial_connection_id` refers to the Source Connection ID because that's the one that needs protection. The Destination Connection ID is covered by `original_connection_id `.

-- 
You are receiving this because you are subscribed to this thread.
Reply to this email directly or view it on GitHub:
https://github.com/quicwg/base-drafts/pull/3499#discussion_r422433344

---

From: David Schinazi <[email-masked]>
Date: Fri, 08 May 2020 17:52:42 -0700

`original_connection_id` used to only apply when a Retry was involved. This PR changes that: servers now always send `original_connection_id` no matter what.

-- 
You are receiving this because you are subscribed to this thread.
Reply to this email directly or view it on GitHub:
https://github.com/quicwg/base-drafts/pull/3499#discussion_r422433616

---

From: Christian Huitema <[email-masked]>
Date: Fri, 08 May 2020 18:09:05 -0700

@DavidSchinazi If you want to refer to the source CID set by the client in its first Initial packet, you should really use a different name than `initial_connection_id`, because this is widely used to refer to the initial destination CID. Maybe you should call that `initial_source_connection_id`. But using the same name for 2 concepts seems bad. 

-- 
You are receiving this because you are subscribed to this thread.
Reply to this email directly or view it on GitHub:
https://github.com/quicwg/base-drafts/pull/3499#discussion_r422435538

---

From: David Schinazi <[email-masked]>
Date: Fri, 08 May 2020 18:37:48 -0700

I didn't pick the name, but I agree that `initial_source_connection_id` might be clearer than `initial_connection_id`.

-- 
You are receiving this because you are subscribed to this thread.
Reply to this email directly or view it on GitHub:
https://github.com/quicwg/base-drafts/pull/3499#discussion_r422438394

---

From: Martin Thomson <[email-masked]>
Date: Sun, 10 May 2020 18:43:49 -0700

@martinthomson pushed 1 commit.

e30cf5fc52da2f7958901fbdf5bcbec920a60d71  Editorial comments thanks to @DavidSchinazi

-- 
You are receiving this because you are subscribed to this thread.
View it on GitHub:
https://github.com/quicwg/base-drafts/pull/3499/files/e2f2b33800cb09add5e185e09f1512ef71e86099..e30cf5fc52da2f7958901fbdf5bcbec920a60d71

---

From: Marten Seemann <[email-masked]>
Date: Sun, 10 May 2020 22:21:05 -0700

@marten-seemann commented on this pull request.

> +first Initial packet it sent in the initial_source_connection_id transport
+parameter; see {{transport-parameter-definitions}}. A server includes the
+Destination Connection ID field from the first Initial packet it received from
+the client in the original_destination_connection_id transport parameter; if
+the server sent a Retry packet this refers to the first Initial packet received
+before sending the Retry packet. If it sends a Retry packet, a server also
+includes the Source Connection ID field from the Retry packet in the
+retry_source_connection_id transport parameter.
+
+The values provided by a peer for these transport parameters MUST match the
+values that an endpoint used in the Destination and Source Connection ID fields
+of Initial packets that it sent. Including connection ID values in transport
+parameters and verifying them ensures that that an attacker cannot influence
+the choice of connection ID for a successful connection by injecting packets
+carrying attacker-chosen connection IDs during the handshake. An endpoint MUST
+treat any of the following as a connection error of type PROTOCOL_VIOLATION:

The absence / presence checks should result in TRANSPORT_PARAMETER_ERROR.

> @@ -1503,7 +1548,7 @@ An endpoint MUST NOT send a parameter more than once in a given transport
 parameters extension.  An endpoint SHOULD treat receipt of duplicate transport
 parameters as a connection error of type TRANSPORT_PARAMETER_ERROR.
 
-A server MUST include the original_connection_id transport parameter
+A server MUST include the original_destination_connection_id transport parameter

This is inconsistent with the error condition above:

> * absence of the original_destination_connection_id transport parameter from the server"

-- 
You are receiving this because you are subscribed to this thread.
Reply to this email directly or view it on GitHub:
https://github.com/quicwg/base-drafts/pull/3499#pullrequestreview-408853491

---

From: ianswett <[email-masked]>
Date: Mon, 11 May 2020 05:52:23 -0700

@ianswett approved this pull request.

-- 
You are receiving this because you are subscribed to this thread.
Reply to this email directly or view it on GitHub:
https://github.com/quicwg/base-drafts/pull/3499#pullrequestreview-409141491

---

From: martinduke <[email-masked]>
Date: Mon, 11 May 2020 10:02:39 -0700

Is it not necessary to authenticate both client CIDs in a Retry scenario?

C->S: Initial SCID = C1 (attacker modifies to C2), DCID = S1
C<-S: Retry SCID = S2, DCID = C1
C->S: Initial SCID = C1, DCID = S2
C<-S: Initial SCID = S3, DCID = C1
....
C->S: Short DCID = S3
C<-S: Short DCID = C1

Admittedly, I don't really understand the threat model here, but this isn't authenticate unless the Initial SCID is in the Token. I don't think a TP is needed, as the server can reject the 2nd initial if there's a mismatch in SCID.

Relatedly, if it's written anywhere that the client MUST NOT change its CID during a Retry, it's not in a place that's obvious to me.

-- 
You are receiving this because you are subscribed to this thread.
Reply to this email directly or view it on GitHub:
https://github.com/quicwg/base-drafts/pull/3499#issuecomment-626827846

---

From: David Schinazi <[email-masked]>
Date: Mon, 11 May 2020 14:50:24 -0700

I'm not sure I agree. TRANSPORT_PARAMETER_ERROR indicates a parse issue, whereas this validation is performed somewhere else in our implementation.

-- 
You are receiving this because you are subscribed to this thread.
Reply to this email directly or view it on GitHub:
https://github.com/quicwg/base-drafts/pull/3499#discussion_r423340357

---

From: Martin Thomson <[email-masked]>
Date: Mon, 11 May 2020 15:56:51 -0700

@martinduke, if the attacker modifies the SCID in an Initial that ultimately causes a Retry to be sent, that doesn't affect the final state in any way, so there is no harm done.

The reason we authenticate the DCID on that packet in the way we do is to verify that the the server is cooperating with the entity that generated the Retry token.  Also, the client-chosen DCID value might persist as the server could use that value to determine its choice of connection ID.  The client can't change its mind, so authenticating a single value is enough.

That said, I will take on advisement that we don't require that the client doesn't change its connection ID.  Issue inbound.

-- 
You are receiving this because you are subscribed to this thread.
Reply to this email directly or view it on GitHub:
https://github.com/quicwg/base-drafts/pull/3499#issuecomment-627008804

---

From: martinduke <[email-masked]>
Date: Mon, 11 May 2020 15:58:58 -0700

@martinduke approved this pull request.

-- 
You are receiving this because you are subscribed to this thread.
Reply to this email directly or view it on GitHub:
https://github.com/quicwg/base-drafts/pull/3499#pullrequestreview-409587998

---

From: Martin Thomson <[email-masked]>
Date: Mon, 11 May 2020 17:16:19 -0700

@martinthomson pushed 2 commits.

81bcdb67d85d953aca7ae378b1f2e4c6a4744578  Merge branch 'master' into authenticate-hs-cid
740cd920d57433273a5dbc74b7fb9a276f6c84bc  Add a picture

-- 
You are receiving this because you are subscribed to this thread.
View it on GitHub:
https://github.com/quicwg/base-drafts/pull/3499/files/e30cf5fc52da2f7958901fbdf5bcbec920a60d71..740cd920d57433273a5dbc74b7fb9a276f6c84bc

---

From: Martin Thomson <[email-masked]>
Date: Mon, 11 May 2020 17:16:24 -0700

6c046207932beb402d293fc5b2c37180995e0dba  Merge branch 'master' into authenticate-hs-cid

-- 
You are receiving this because you are subscribed to this thread.
View it on GitHub:
https://github.com/quicwg/base-drafts/pull/3499/files/740cd920d57433273a5dbc74b7fb9a276f6c84bc..6c046207932beb402d293fc5b2c37180995e0dba

---

From: Martin Thomson <[email-masked]>
Date: Mon, 11 May 2020 17:16:58 -0700

I just added a picture.

This new figure shows the complete set of messages that determine the
connection ID that each peer uses.  The explanation describes how the
connection IDs in those messages are included in transport parameters.

Hopefully this aids in understanding how this is put together.  It's not
complicated, but it's not necessarily clear from reading a set of rules
that this is the case.

-- 
You are receiving this because you are subscribed to this thread.
Reply to this email directly or view it on GitHub:
https://github.com/quicwg/base-drafts/pull/3499#issuecomment-627033693

---

From: Martin Thomson <[email-masked]>
Date: Mon, 11 May 2020 17:18:41 -0700

@martinthomson commented on this pull request.

Not following.  These are consistent by my reading:

> A server MUST include the original_destination_connection_id transport parameter

> An endpoint MUST
treat any of the following as a connection error of type PROTOCOL_VIOLATION:
> * absence of the original_destination_connection_id transport parameter from the server

-- 
You are receiving this because you are subscribed to this thread.
Reply to this email directly or view it on GitHub:
https://github.com/quicwg/base-drafts/pull/3499#discussion_r423391794

---

From: David Schinazi <[email-masked]>
Date: Mon, 11 May 2020 18:24:01 -0700

@martinthomson you truncated the sentence in your quote. The full sentence from your PR is:

> ({{transport-parameter-definitions}}) if it sent a Retry packet to enable
> validation of the Retry, as described in {{packet-retry}}.

How about we keep the first line, but replace the rest:

> to mitigate an attacker's ability to tamper with connection IDs during the handshake.

-- 
You are receiving this because you are subscribed to this thread.
Reply to this email directly or view it on GitHub:
https://github.com/quicwg/base-drafts/pull/3499#discussion_r423409742

---

From: Martin Thomson <[email-masked]>
Date: Mon, 11 May 2020 18:31:36 -0700

Ahh, that's lazy of me.  Yes, that is good.  Thanks.

-- 
You are receiving this because you are subscribed to this thread.
Reply to this email directly or view it on GitHub:
https://github.com/quicwg/base-drafts/pull/3499#discussion_r423411801

---

From: Martin Thomson <[email-masked]>
Date: Mon, 11 May 2020 18:33:37 -0700

Oh, that's a throwback paragraph.  I'm going to replace that with a more generic reference.

-- 
You are receiving this because you are subscribed to this thread.
Reply to this email directly or view it on GitHub:
https://github.com/quicwg/base-drafts/pull/3499#discussion_r423412323

---

From: Martin Thomson <[email-masked]>
Date: Mon, 11 May 2020 18:36:00 -0700

6ae7f18bfb4052e352d91c2f95715867b41d2582  Use a more generic reference

-- 
You are receiving this because you are subscribed to this thread.
View it on GitHub:
https://github.com/quicwg/base-drafts/pull/3499/files/6c046207932beb402d293fc5b2c37180995e0dba..6ae7f18bfb4052e352d91c2f95715867b41d2582

---

From: David Schinazi <[email-masked]>
Date: Mon, 11 May 2020 19:32:50 -0700

> +~~~
+Client                                                  Server
+
+Initial: DCID=S1, SCID=C1 ->
+                                   <- Retry*: DCID=C1, SCID=S2
+Initial*: DCID=S2, SCID=C1 ->
+                                  <- Initial: DCID=C1, SCID=S3
+                             ...
+1-RTT: DCID=S3 ->
+                                             <- 1-RTT: DCID=C1
+~~~
+{: #fig-auth-cid title="Use of Connection IDs in a Handshake"}
+
+For the handshake in {{fig-auth-cid}} the client sets the value of the
+initial_source_connection_id transport parameter to `C1`. If the server sends a
+Retry packet (that is, the packets marked with a '*' are sent), it sets

I really like the diagram, but I would prefer to split it in two. There are two possible handshakes described here: without and with retry involved. I think that the `the packets marked with a '*' are sent` caveat is confusing, because when those packets aren't sent, the `1-RTT: DCID=S3` needs to be replaced by `1-RTT: DCID=S2`. It might be simpler to have two diagrams and two examples.

 If the client received and processed a Retry packet, it MUST validate that the
-original_connection_id transport parameter is present and correct; otherwise, it
-MUST validate that the transport parameter is absent.  A client MUST treat a
-failed validation as a connection error of type TRANSPORT_PARAMETER_ERROR.
+retry_source_connection_id transport parameter is present and correct;
+otherwise, it MUST validate that the transport parameter is absent. A client
+MUST treat a failed validation as a connection error of type
+TRANSPORT_PARAMETER_ERROR.

This contradicts line 1568
> An endpoint MUST treat any of the following as a connection error of type PROTOCOL_VIOLATION

I have a slight preference for PROTOCOL_VIOLATION over TRANSPORT_PARAMETER_ERROR, though I don't much care as long as this section matches the other one.

-- 
You are receiving this because you are subscribed to this thread.
Reply to this email directly or view it on GitHub:
https://github.com/quicwg/base-drafts/pull/3499#pullrequestreview-409656812

---

From: Marten Seemann <[email-masked]>
Date: Mon, 11 May 2020 19:54:12 -0700

Our rule for FRAME_ENCODING_ERROR is that we use that error code when the error is detectable while parsing the frame, without accessing connection state. I'd argue that we should apply a similar logic here. The absence of `initial_source_connection_id` and `original_destination_connection_id` definitely falls in that category, and I'd put the absence / presence of `retry_source_connection_id` in the same category, although I could see why someone could make an argument against this.

The mismatch seems to be a clear case for PROTOCOL_VIOLATION.

-- 
You are receiving this because you are subscribed to this thread.
Reply to this email directly or view it on GitHub:
https://github.com/quicwg/base-drafts/pull/3499#discussion_r423432102

---

From: Martin Thomson <[email-masked]>
Date: Mon, 11 May 2020 21:12:33 -0700

fee4020ce6d9aaa9bbee74a51424701ca98bbf44  Correct a few more errors
27dfb6973f76c27fee090c001b6f82427b463806  Fewer words

-- 
You are receiving this because you are subscribed to this thread.
View it on GitHub:
https://github.com/quicwg/base-drafts/pull/3499/files/6ae7f18bfb4052e352d91c2f95715867b41d2582..27dfb6973f76c27fee090c001b6f82427b463806

---

From: Martin Thomson <[email-masked]>
Date: Mon, 11 May 2020 21:14:26 -0700

I like that rationale.  I could also see my way to choose TRANSPORT_PARAMETER_ERROR.  We use that for transport parameters that are present when they are disallowed (like preferred_address from a client) in addition to obvious encoding problems, which suggests that you might implement this as `if transport_parameters.bad() then TRANSPORT_PARAMETER_ERROR`, but this validation does involve accessing external state, as you say, so a different code is fully justified.

-- 
You are receiving this because you are subscribed to this thread.
Reply to this email directly or view it on GitHub:
https://github.com/quicwg/base-drafts/pull/3499#discussion_r423451259

---

From: David Schinazi <[email-masked]>
Date: Tue, 12 May 2020 11:49:42 -0700

@DavidSchinazi approved this pull request.

Thanks for all the edits!

-- 
You are receiving this because you are subscribed to this thread.
Reply to this email directly or view it on GitHub:
https://github.com/quicwg/base-drafts/pull/3499#pullrequestreview-410327743

---

From: Jana Iyengar <[email-masked]>
Date: Wed, 13 May 2020 23:20:32 -0700

@janaiyengar approved this pull request.

Thanks for the diagrams, definitely clearer with them.

-- 
You are receiving this because you are subscribed to this thread.
Reply to this email directly or view it on GitHub:
https://github.com/quicwg/base-drafts/pull/3499#pullrequestreview-411493945

---

From: ekr <[email-masked]>
Date: Fri, 15 May 2020 13:31:46 -0700

@ekr approved this pull request.

LGTM

> @@ -1453,11 +1453,11 @@ Source Connection ID values as the client's first Initial packet.
 
 Upon first receiving an Initial or Retry packet from the server, the client uses
 the Source Connection ID supplied by the server as the Destination Connection ID
-for subsequent packets, including all subsequent 0-RTT packets.  This means that
-a client might have to change the connection ID it sets in the Destination
-Connection ID field twice during connection establishment: once in response to a
-Retry, and once in response to an Initial packet from the server. Once a client
-has received an Initial packet from the server, it MUST discard any subsequent
+for subsequent packets, including any 0-RTT packets.  This means that a client
+might have to change the connection ID it sets in the Destination Connection ID
+field twice during connection establishment: once in response to a Retry, and
+once in response to an Initial packet from the server. Once a client has
+received an Initial packet from the server, it MUST discard any subsequent

Perhaps we want to clarify "valid" here just to make sure that people recognize that it has to pass deprotection?

-- 
You are receiving this because you are subscribed to this thread.
Reply to this email directly or view it on GitHub:
https://github.com/quicwg/base-drafts/pull/3499#pullrequestreview-408511028

---

From: Martin Thomson <[email-masked]>
Date: Sun, 17 May 2020 17:53:34 -0700

62103ceb9ff5b87b2717bea1af6ff64cff1a989e  Only valid packets change this state

-- 
You are receiving this because you are subscribed to this thread.
View it on GitHub:
https://github.com/quicwg/base-drafts/pull/3499/files/27dfb6973f76c27fee090c001b6f82427b463806..62103ceb9ff5b87b2717bea1af6ff64cff1a989e

---

From: Martin Thomson <[email-masked]>
Date: Sun, 17 May 2020 17:54:02 -0700

Merged #3499 into master.

-- 
You are receiving this because you are subscribed to this thread.
Reply to this email directly or view it on GitHub:
https://github.com/quicwg/base-drafts/pull/3499#event-3345125474