Change:
The first commit shaves 33 bytes (or 49 with SHA-384) from the ciphertext by replacing inner_digest with ClientHelloOuterAAD. This also allows for more compact ClientHelloInner encodings when non-extension fields are expected to match. The second commit shaves a further 32 bytes. We could probably further optimize the encoding but I've left it alone here. In particular, if we merge , all non-extension fields may as well be implicit. We could then replace the encryption payload with a plain extensions block.
I guess that doesn't quite work, but we could replace it with the new random value, and an extensions block. Something like: Anyway, a topic for a later PR, depending on how strongly we want to match cipher suites for . The size savings are a bit more marginal beyond that.
I like this change a lot. At some point while implementing ECH, I noted that it would be nice to have an explicit binding of CHOuter to the handshake in case of ECH acceptance. This provides that.
Rebased and review comments addressed. The PR should also no longer be in a confusing state if you look at the diff.
We previously decided not to bind the outer ClientHello to the ECH decryption, because doing so is rather a hassle. We'd need something similar to the TLS 1.3 PSK binder mess, with the added complexity of ordering relative to the PSK binders themselves. Additionally, while the binding helps with some of the don't stick out properties, there were other attacks on distinguishability anyway. So, instead, we mitigated the inner ClientHello malleability with OuterExtensions.innerdigest and left the outer ClientHello unbound. However, we've since decided not to allow PSKs in the outer ClientHello. Perhaps we should revisit this. Suppose the AD parameter to the ECH decryption were the entire outer ClientHello, minus the ECH extension. I believe this means we no longer need OuterExtensions.innerdigest. That would give an immediate 33-byte savings. It would then unlock a further 32 byte savings by amending to say: Client always encodes an empty legacysessionid in ECH plaintext. Client-facing server copies outer legacysessionid to inner ClientHello as part of decompression. (The reason we can't do this easily right now is that, if the inner ClientHello lacks outerextension, nothing's authenticating legacysessionid. Since the field in TLS 1.3 is all but ignored, it's "obvious" that the malleability doesn't matter, but it's nicer to not need to think about such things.) From looking at Chrome's ClientHellos and working through what we'd send (drop 1.2-only extensions, compress most), it looks like the inner ClientHello will be maybe 130-140 bytes, plus a padded servername. The 65 bytes above would almost halve the overhead. In some sense what's going on here is our 16-byte tag on the AEAD in the ECH ciphertext is going to waste. We didn't authenticate everything that goes into reconstructing the inner ClientHello (outerextensions), so instead we authenticate another hash (OuterExtensions.innerdigest) and check that afterwards. (To that end, if we do this, we probably need to say the server MUST reject encryptedclienthello showing up in outer_extensions, that way all inputs to decompression are authenticated. Equivalently, decompression is done relative to the ECH-extension-less AD input rather than the original outer ClientHello.)
I like this idea. How do you think the AD ought to be structured? The ClientHelloOuter, but with an an empty "encryptedclienthello" extension?
Yeah, that's seems the simplest to me. Filling it with zeros, as NAME pointed out to me, requires predicting the length of the encrypted inner ClientHello. This isn't strictly impossible (ADs don't change ciphertext length), but it seems needlessly fussy and ECH is already requiring we be able re-encode ClientHellos, lengths and all. Note this assumes there are no computed PSK binders in the outer ClientHello because otherwise there's a circular dependency. (If we wanted to allow that, the AD would need to zero/trim the binders or so.)
Another benefit is it stops the attacker from probing information about compressed extensions. Currently an attacker can learn which extensions are compressed in the inner ClientHello by removing them from the outer ClientHello and seeing if the server still responds. This isn't a huge deal, as compressed extensions are assumed public, but this way we remove that vector and leave only the fuzzier signal from the ciphertext length.
On second thought, a lot of implementations will need to do that prediction anyway. The workaround for the old F5 bug means we'll need to know the eventual size for the padding extension. (On the other hand, you can probably assume anyone doing ECH is not running those things. On the other hand, turning off the workaround would stick out.) Ah well. We did it for PSK already. (I wonder if we can do drop that workaround now that TLS 1.0 and 1.1 are gone...)
What's the "old F5 bug"? What's the value in authenticating the length of the "encryptedclienthello" extension? This is authenticated by the handshake already, no?
URL URL It's why we have a padding extension. There isn't. It's just a question of what's easier to deal with. Filling it with zeros means that, when computing the AD on the receiving side, you don't need to fully reconstruct a modified outer ClientHello with updated length prefixes. We avoided this when computing the PSK binder in RFC8446. It's a mild hassle but not a huge deal. More importantly, we've thoroughly given up on that elsewhere in the spec. The F5 padding business also means computing this AD will likely take some length prediction anyway, but so it goes.
I agree, it's not a big hassle.
Rather than make the extension empty, can we just omit the and fields? (The and should arguably be included, a la ).
That works, though it's even more fussy for the client to construct, since it needs to half-encode the ECH extension before fully encoding it. is already bound into HPKE itself. is less clear, but I thought our model already said all this didn't matter? Isn't the binding here just to replace and provide malleability? If there's something more going on, I'd like us to have some principled answer here, so we can answer questions like... what about the handshake transcript, which may not be empty on HRR? If we do that, what's going on with echhrr_key?
The principle I was following is: include as much as possible in the AD. Yeah, it's more of a pain, and we've gotten away with no binding whatsoever up to now, so maybe we don't need it. I'm happy to start with an empty ECH extension and then consider the other parts later, if that'll help.
I don't feel very strongly either way.
Let's just do the simpler thing now, then! :-) The following errata report has been verified for RFC8996, "Deprecating TLS 1.0 and TLS 1.1". You may review the report below and at: URL Status: Verified Type: Editorial Reported by: Gaëtan Leurent Date Reported: 2024-02-03 Verified by: RFC Editor Section: 10.2 Original Text [Bhargavan2016] Bhargavan, K. and G. Leuren, Corrected Text [Bhargavan2016] Bhargavan, K. and G. Leurent, Notes The last name "Leurent" is misspelt is the references. RFC8996 (draft-ietf-tls-oldversions-deprecate-12) Title : Deprecating TLS 1.0 and TLS 1.1 Publication Date : March 2021 Author(s) : K. Moriarty, S. Farrell Category : BEST CURRENT PRACTICE Source : Transport Layer Security Area : Security Stream : IETF

Discussion:
extension. OuterExtensions consists of one or more ExtensionType values, each of <del> which reference an extension in ClientHelloOuter, and a digest of the complete ClientHelloInner. </del> <ins> which reference an extension in ClientHelloOuter. </ins> When sending ClientHello, the client first computes ClientHelloInner, including any PSK binders.  It then computes a new value, the EncodedClientHelloInner, by first making a copy of ClientHelloInner. <del> It then MAY substitute extensions which it knows will be duplicated in ClientHelloOuter.  To do so, the client computes the digest of the entire ClientHelloInner message as: where "inner" is the ClientHelloInner structure and "Extract", "Expand", and "Nh" are as defined by the KDF API in I-D.irtf-cfrg- hpke.  Then, the client removes and replaces extensions from EncodedClientHelloInner with a single "outer_extensions" extension. Removed extensions MUST be ordered consecutively in ClientHelloInner. The list of outer extensions, OuterExtensions.outer_extensions, includes those which were removed from EncodedClientHelloInner, in the order in which they were removed. </del> <ins> It then replaces the legacy_session_id field with an empty string. The client then MAY substitute extensions which it knows will be duplicated in ClientHelloOuter.  To do so, the client removes and replaces extensions from EncodedClientHelloInner with a single "outer_extensions" extension.  Removed extensions MUST be ordered consecutively in ClientHelloInner.  The list of outer extensions, OuterExtensions, includes those which were removed from EncodedClientHelloInner, in the order in which they were removed. </ins> Finally, EncodedClientHelloInner is serialized as a ClientHello structure, defined in Section 4.1.2 of RFC8446.  Note this does not include the four-byte header included in the Handshake structure. The client-facing server computes ClientHelloInner by reversing this <del> process.  It scans EncodedClientHelloInner for an "outer_extensions" extension.  If there is none, the ClientHelloInner is the EncodedClientHelloInner.  Otherwise, it replaces the extension with the corresponding sequence of extensions in the ClientHelloOuter.  If any referenced extensions are missing, the server MUST abort the connection with an "illegal_parameter" alert.  The server then computes the digest of the reconstructed ClientHelloInner.  If the digest does not equal OuterExtensions.inner_digest, then the server MUST abort the connection with a "decrypt_error" alert. </del> <ins> process.  First it makes a copy of EncodedClientHelloInner and copies the legacy_session_id field from ClientHelloOuter.  It then looks for an "outer_extensions" extension.  If found, it replaces the extension with the corresponding sequence of extensions in the ClientHelloOuter.  If any referenced extensions are missing or if "encrypted_client_hello" appears in the list, the server MUST abort the connection with an "illegal_parameter" alert. </ins> The "outer_extensions" extension is only used for compressing the ClientHelloInner.  It MUST NOT be sent in either ClientHelloOuter or ClientHelloInner. <ins> 5.2. To prevent a network attacker from modifying the reconstructed ClientHelloInner (see flow-clienthello-malleability), ECH authenticates ClientHelloOuter by deriving a ClientHelloOuterAAD value.  This is computed by serializing ClientHelloOuter with the "encrypted_client_hello" extension removed.  ClientHelloOuterAAD is then passed as the associated data parameter to the HPKE encryption. Note the decompression process in encoding-inner forbids "encrypted_client_hello" in OuterExtensions.  This ensures the unauthenticated portion of ClientHelloOuter is not incorporated into ClientHelloInner. </ins> 6. 6.1.

It SHOULD contain TLS padding RFC7685 as described in padding. <del> If it intends to compress any extensions (see outer-extensions), it MUST order those extensions consecutively. </del> <ins> If it intends to compress any extensions (see encoding-inner), it MUST order those extensions consecutively. </ins> The client then constructs EncodedClientHelloInner as described in <del> outer-extensions.  Finally, it constructs the ClientHelloOuter message just as it does a standard ClientHello, with the exception of the following rules: </del> <ins> encoding-inner.  Finally, it constructs the ClientHelloOuter message just as it does a standard ClientHello, with the exception of the following rules: </ins> It MUST offer to negotiate TLS 1.3 or above.

ClientHelloOuter.random using a secure random number generator. (See flow-client-reaction.) <del> If implementing TLS 1.3's compatibility mode (see Appendix D.4 of RFC8446), it MUST copy the legacy_session_id field from ClientHelloInner.  This allows the server to echo the correct session ID when ECH is negotiated. </del> <ins> It MUST copy the legacy_session_id field from ClientHelloInner. This allows the server to echo the correct session ID for TLS 1.3's compatibility mode (see Appendix D.4 of RFC8446) when ECH is negotiated. </ins> It MUST include an "encrypted_client_hello" extension with a payload constructed as described below.

extensions are not offered in the ClientHelloOuter.  See outer- clienthello for additional guidance. <del> To encrypt EncodedClientHelloInner, the client first needs to generate the HPKE encryption context.  It computes the encapsulated key, context, HRR key (see hrr), and payload as: </del> <ins> To encrypt EncodedClientHelloInner, the client first computes ClientHelloOuterAAD as described in authenticating-outer.  Note this requires the "encrypted_client_hello" be computed after all other extensions.  In particular, this is possible because the "pre_shared_key" extension is forbidden in ClientHelloOuter. The client then generates the HPKE encryption context.  Finally, it computes the encapsulated key, context, HRR key (see client-hrr), and payload as: </ins> Note that the HPKE functions Deserialize and SetupBaseS are those which match "ECHConfig.kem_id" and the AEAD/KDF used with "context"

receipt of a second ClientHello message with a ClientECH value, servers set up their HPKE context and decrypt ClientECH as follows: <ins> ClientHelloOuterAAD is computed from the second ClientHelloOuter as described in authenticating-outer. </ins> If the client offered ECH in the first ClientHello, then it MUST offer ECH in the second.  Likewise, if the client did not offer ECH in the first ClientHello, then it MUST NOT not offer ECH in the

decrypts ClientECH.payload, using the private key skR corresponding to ECHConfig, as follows: <del> If decryption fails, the server MUST abort the connection with a "decrypt_error" alert.  Otherwise, the server reconstructs ClientHelloInner from EncodedClientHelloInner, as described in outer- extensions. Upon determining the true SNI, the client-facing server then either serves the connection directly (if in Shared Mode), in which case it executes the steps in the following section, or forwards the TLS connection to the backend server (if in Split Mode).  In the latter case, it does not make any changes to the TLS messages, but just blindly forwards them. If the server sends a NewSessionTicket message, the corresponding PSK MUST be ignored by all other servers in the deployment when not negotiating ECH, including servers which do not implement this specification. </del> <ins> ClientHelloOuterAAD is computed from ClientHelloOuter as described in authenticating-outer.  If decryption fails, the server MUST abort the connection with a "decrypt_error" alert.  Otherwise, the server reconstructs ClientHelloInner from EncodedClientHelloInner, as described in encoding-inner. Upon determining the ClientHelloInner, the client-facing server then forwards the ClientHelloInner to the appropriate backend server, which proceeds as in backend-server-behavior.  If the backend server responds with a HelloRetryRequest, the client-facing server forwards it, decrypts the client's second ClientHelloOuter using the modified procedure in server-hrr, and forwards the resulting second ClientHelloInner.  The client-facing server forwards all other TLS messages between the client and backend server unmodified. </ins> 7.1.1.

Values which are independent of the true server name, or other information the client wishes to protect, MAY be included in ClientHelloOuter.  If they match the corresponding ClientHelloInner, <del> they MAY be compressed as described in outer-extensions.  However, note the payload length reveals information about which extensions are compressed, so inner extensions which only sometimes match the </del> <ins> they MAY be compressed as described in encoding-inner.  However, note the payload length reveals information about which extensions are compressed, so inner extensions which only sometimes match the </ins> corresponding outer extension SHOULD NOT be compressed. <del> [[OPEN ISSUE: In addition to the fuzzy leak from the length, there is an active attack to probe compressed extensions.  See issue #323.  We should either document this attack, or bind ClientHelloOuter.]] </del> Clients MAY include additional extensions in ClientHelloOuter to avoid signaling unusual behavior to passive observers, provided the choice of value and value itself are not sensitive.  See do-not-

This attack aims to leak information about secret parts of the encrypted ClientHello by adding attacker-controlled parameters and observing the server's response.  In particular, the compression <del> mechanism described in outer-extensions references parts of a </del> <ins> mechanism described in encoding-inner references parts of a </ins> potentially attacker-controlled ClientHelloOuter to construct ClientHelloInner, or a buggy server may incorrectly apply parameters from ClientHelloOuter to the handshake.

This attack may be generalized to any parameter which the server varies by server name, such as ALPN preferences. <del> ECH mitigates this attack by only using ClientHelloOuter to compute ClientHelloInner and authenticating all of ClientHelloInner with the HPKE AEAD.  If any extensions are compressed as in outer-extensions, the OuterExtensions.inner_digest field authenticates the decompressed result.  If none are compressed, the entire ClientHello is encrypted and authenticated directly.  An earlier iteration of this specification only encrypted and authenticated the "server_name" </del> <ins> ECH mitigates this attack by only negotiating TLS parameters from ClientHelloInner and authenticating all inputs to the ClientHelloInner (EncodedClientHelloInner and ClientHelloOuter) with the HPKE AEAD.  See authenticating-outer.  An earlier iteration of this specification only encrypted and authenticated the "server_name" </ins> extension, which left the overall ClientHello vulnerable to an analogue of this attack.