Change:
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.

Discussion:
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