Per client-behavior and server-behavior, implementations are required to track, alongside each PSK established by a previous connection, <del> whether the connection negotiated this extension with the "ech_accept" response type.  If so, this is referred to as an "ECH PSK".  Otherwise, it is a "non-ECH PSK".  This may be implemented by adding a new field to client and server session states. </del>  6.1.

Section 6: implementations to track, alongside each PSK established by a previous connection, whether the connection negotiated this extension with the "echaccept" response type. If so, this is referred to as an "ECH PSK". Otherwise, it is a "non-ECH PSK". This may be implemented by adding a new field to client and server session states. "echaccept" is not defined anywhere.
I suspect "the connection negotiated this extension with the 'ech_accept' response type" is an incorrect way of saying that ECH was accepted.

Per client-behavior and server-behavior, implementations are required to track, alongside each PSK established by a previous connection,  <ins> whether ECH was accepted for this connection.  If so, this is referred to as an "ECH PSK".  Otherwise, it is a "non-ECH PSK".  This may be implemented by adding a new field to client and server session states. </ins> 6.1.


The protocol specified by this document takes a different approach. It assumes that private origins will co-locate with or hide behind a <del> provider (reverse proxy, application server, etc.) that protects SNIs for all of the domains it hosts.  As a result, SNI protection does not indicate that the client is attempting to reach a private origin, but only that it is going to a particular service provider, which the observer could already tell from the visible IP address. </del>  This document specifies a new TLS extension, called Encrypted Client Hello (ECH), that allows clients to encrypt their ClientHello to a or not ECH was accepted and proceeds with the handshake accordingly. (See client-behavior for details.)  4. ECH uses HPKE for public key encryption I-D.irtf-cfrg-hpke.  The ECH 10.1. <del> Informally, the primary security and privacy goals of ECH against an active attacker are as follows: </del>  Use of ECH does not weaken the security properties of TLS without ECH. cipher suite, it may be possible to identify that host based on the contents of unencrypted handshake messages.  10.2. In comparison to I-D.kazuho-protected-sni, wherein DNS Resource

cc NAME
In just a few sentences, let's define the anonymity set and say what it means for connections to end points in this set to be indistignuishable. We might also enumerate non-goals (though we still need consensus on what these are).
I've not yet gotten around to the PR yet, but here's the gist: Attackers: Passive attackers are those which cannot probe, e.g., buggy middleboxes that filter based on plaintext packet contents. Active attackers can probe, e.g., censors. Goals: ECH does not negatively affect any existing security properties of TLS 1.3. ECH connections to servers in the same anonymity set are indistinguishable from one another for passive and active attackers, modulo traffic analysis. (Note: an anonymity set is determined by the client and server parameters, e.g., the SNI, supported application protocols, and so on.) ECH support is secret from passive attackers who don't know the ECH configuration, but not active attackers. ECH usage is secret from passive attackers who don't know the ECH configuration, but not active attackers. More specifically: Passive attackers with a known ECH configuration can distinguish between a connection that negotiates ECH with that configuration and one which does not (GREASEd or using a different configuration). Passive attackers without the ECH configuration cannot distinguish between a connection that negotiates ECH and one which does not (but GREASEd). Active attackers can distinguish between a connection that negotiates ECH and one which does not (but GREASEd).
Yeah this looks good.
From the interim: merge this as a PR and then address censorship separately (if desired).

The protocol specified by this document takes a different approach. It assumes that private origins will co-locate with or hide behind a  <ins> provider (reverse proxy, application server, etc.) that protects sensitive ClientHello parameters, including the SNI, for all of the domains it hosts.  These co-located servers form an anonymity set wherein all elements have a consistent configuration, e.g., the set of supported application protocols, ciphersuites, TLS versions, and so on.  Usage of this mechanism reveals that a client is connecting to a particular service provider, but does not reveal which server from the anonymity set terminates the connection.  Thus, it leaks no more than what is already visible from the server IP address. </ins> This document specifies a new TLS extension, called Encrypted Client Hello (ECH), that allows clients to encrypt their ClientHello to a or not ECH was accepted and proceeds with the handshake accordingly. (See client-behavior for details.) <ins> Informally, a primary goal of ECH is ensuring that connections to servers in the same anonymity set are indistinguishable from one another without affecting any existing security properties of TLS 1.3.  See goals for more details about the ECH security and privacy goals. </ins> 4. ECH uses HPKE for public key encryption I-D.irtf-cfrg-hpke.  The ECH 10.1.  <ins> ECH considers two types of attackers: passive and active.  Passive attackers can read packets from the network.  They cannot perform any sort of active behavior such as probing servers or querying DNS.  A middlebox that filters based on plaintext packet contents is one example of a passive attacker.  In contrast, active attackers can write packets into the network for malicious purposes, such as interfering with existing connections, probing servers, and querying DNS.  In short, an active attacker corresponds to the conventional threat model for TLS 1.3 RFC8446. Given these types of attackers, the primary goals of ECH are as follows. </ins> Use of ECH does not weaken the security properties of TLS without ECH. cipher suite, it may be possible to identify that host based on the contents of unencrypted handshake messages. <ins> Beyond these primary security and privacy goals, ECH also aims to hide, to some extent, (a) whether or not a specific server supports ECH and (b) whether or not ECH was accepted for a particular connection.  ECH aims to achieve both properties, assuming the attacker is passive and does not know the set of ECH configurations offered by the client-facing server.  It does not achieve these properties for active attackers.  More specifically: Passive attackers with a known ECH configuration can distinguish between a connection that negotiates ECH with that configuration and one which does not, because the latter used a GREASE "encrypted_client_hello" extension (as specified in grease- extensions) or a different ECH configuration. Passive attackers without the ECH configuration cannot distinguish between a connection that negotiates ECH and one which uses a GREASE "encrypted_client_hello" extension. Active attackers can distinguish between a connection that negotiates ECH and one which uses a GREASE "encrypted_client_hello" extension. See do-not-stick-out for more discussion about the "do not stick out" criteria from RFC8744. </ins> 10.2. In comparison to I-D.kazuho-protected-sni, wherein DNS Resource


the query domain name.  That is, if a client queries example.com, the ESNI TXT Resource Record might be: <del> Servers SHOULD configure DNS such that, upon querying a domain name with ESNI support, at most one each of A, AAAA, TXT ESNI, and ALTSVC I-D.schwartz-httpbis-dns-alt-svc Resource Record is returned.  Alt- Svc records may be used to inform the client of the plaintext </del>  (fronting) SNI.  If present, clients SHOULD use its value in the SNI extension of the subsequent ClientHello.

See
This looks good to me. Clarifying the requirements rather than specifying how to act makes more sense here. FWIW, I thought the intent of the original text was to encourage not having ESNIKeys shared between multiple hosts whenever possible. This change loses that property. But anyways, that is mentioned in the appendix. Hence my :+1: to the PR.
I suggest dropping the references to Ben's individual alt-svc in DNS draft.. it hasn't been adopted and its future is murky. actual alt-svc (the non dns kind) actually requires the use of the plaintext SNI. As those are both cachable and connections to new IP addresses (less linkable) you could consider updating it with mike bishop's URL (also an individual draft atm)
NAME also suggested that the reference might not be needed in URL so I made to actually remove it.
Regarding: Why is that a SHOULD? Does it apply to both fronting and shared mode? For reference, Cloudflare almost always sends at least two A and AAAA answers for every question. (I get that it's not a MUST, though I'd also like to understand what the reasoning is in the first place)
NAME ??
The idea was to simplify key management. If multiple A or AAAA records are returned, the server needs to ensure each server behind those addresses have all the necessary keys.
NAME I see, I'd rather the spec said that instead of trying to impose limits (I made with suggested fix).
NAME works for me :)

the query domain name.  That is, if a client queries example.com, the ESNI TXT Resource Record might be:  <ins> Servers MUST ensure that, if multiple A, AAAA or ALTSVC I-D.schwartz- httpbis-dns-alt-svc records are returned for a domain with ESNI support, all the servers pointed to by those records are able to handle the keys returned as part of a ESNI TXT record for that domain. Alt-Svc records may be used to inform the client of the plaintext </ins> (fronting) SNI.  If present, clients SHOULD use its value in the SNI extension of the subsequent ClientHello.


It SHOULD contain TLS padding RFC7685 as described in padding.  The client might duplicate non-sensitive extensions in both messages. However, implementations need to take care to ensure that sensitive extensions are not offered in the ClientHelloOuter.  [[OPEN ISSUE: We

Thanks!
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! :-)
If the client uses TLS 1.3's , there are a couple subtle points: Even though the middlebox will never see the inner ClientHello, we still need a non-empty legacysessionid, because some servers use that value to condition the other bits. More importantly, the inner legacysessionid needs to match the outer legacysessionid! Otherwise the server will echo back the wrong one and trip up all the buggy middleboxes again. Alternatively, we could say the server uses the outer ClientHello to negotiate middlebox compatibility mode. That would reduce bytes on the wire, but it'd upset all the split mode arrangements.
Good catch. I'd be fine with this matching constraint. Could you please propose text? 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

It SHOULD contain TLS padding RFC7685 as described in padding. <ins> If implementing TLS 1.3's compatibility mode (see Appendix D.4 of RFC8446), it MUST copy the legacy_session_id field from ClientHelloOuter.  This allows the server to echo the correct session ID when ECH is negotiated. </ins> The client might duplicate non-sensitive extensions in both messages. However, implementations need to take care to ensure that sensitive extensions are not offered in the ClientHelloOuter.  [[OPEN ISSUE: We


If it compressed any extensions in EncodedClientHelloInner, it MUST copy the corresponding extensions from ClientHelloInner.  It MAY copy any other field from the ClientHelloInner except ClientHelloInner.random.  Instead, It MUST generate a fresh ClientHelloOuter.random using a secure random number generator. It MUST NOT include the "pre_shared_key" extension.  (See flow- clienthello-malleability.)  The client might duplicate non-sensitive extensions in both messages. However, implementations need to take care to ensure that sensitive extensions are not offered in the ClientHelloOuter.  See outer- 6.1.3.  If the server sends a HelloRetryRequest in response to the ClientHello, the client sends a second updated ClientHello per the rules in RFC8446.  However, at this point, the client does not know

This is limited to parameters applicable to TLS 1.3 and beyond. For example, TLS 1.2 ciphersuites may appear in ClientHelloOuter, so long as all TLS 1.3 ciphersuites in ClientHelloOuter match that in ClientHelloInner. cc NAME NAME
NAME NAME NAME this is ready for another look.
I'm not totally sure this is the right outcome. I can imagine situations where you might want CHOuter and CHInner to be different. I wonder if we can use a similar PRF() trick for HRR to tell which one it was, though I appreciate that this is in conflict with folding the secret into random.
I think there're three issues we'd want to sort out, to allow a mismatch. Without an ECH acceptance signal in HRR, the error gets deferred and that seems like a huge mess. I think this is the issue you have in mind with the PRF trick? If the HRR matches CHInner and is inconsistent with CHOuter, we still need to construct some CHOuter. The current draft says you don't follow the RFC8446 rules, but that can't be right because we need the cookie to appear in CHOuter even if CHOuter can't be constructed. Even if a mismatch is allowed, I think we should have text encouraging a match, so implementers know to avoid a complexity cliff if they don't need it. (It's also possible this would interact with , if we want some goofy HRRInner vs HRROuter scheme...)
Yes, I am trying to with the PRF trick. And yes, there is some possibility of a goofy HRRInner/HRROuter
I was thinking about and the discussion on server HRR checks started by URL, and I'm not sure HRR cookies even work in split mode right now. First off, suppose we don't merge something like . The current text says: This means if you get a cookied HRR only good for ClientHelloInner, there's no requirement to stick the cookie in ClientHelloOuter! But now the client-facing server can't see the cookie, so it can't be stateless. It would seem like fixes this, but now we have a more fundamental problem: whose cookie? Suppose the client-facing server wishes to handle HRR statelessly. It decrypts the first ClientHelloInner and forwards it to the backend server. If the backend server responds with HRR, the client-facing server must forward the HRR as-is, or the transcript breaks. But the client-facing server has no opportunity to bind its state (echhrrkey) into the HRR cookie. (Perhaps there isn't even one. If there is, it's encrypted with the backend server's key anyway. Either way, it's fixed now.) The simplest solution to all this is to only support stateful client-facing servers in split mode, unless the client-facing and backend servers can coordinate enough to share one cookie. I don't know if that would satisfy everyone. I could imagine doing something horrific to URL, akin to what we did to URL, but that sounds really messy.
Let's mark this as an "OPEN ISSUE" in the draft.
Done. URL
In a normal ECHO handshake with HelloRetryRequest there are 4 ClientHellos sent: ClientHelloOuter1, ClientHelloInner1, ClientHelloOuter2, ClientHelloInner2. Encryption of ClientHelloInner2 is bound to echohrrkey so the server must have the ECHOConfig used for ClientHelloInner1 in order to process ECHO the second time. And by the time the client receives encrypted handshake traffic it is not useful to signal if ClientHelloInner1 was successfully decrypted, what matters is to know if ECHO is globally successful or not. So one aspect that I find not explained enough is whether the server replying with HRR should use ClientHelloInner1 in the transcript, or instead always takes ClientHelloOuter1 (data on the wire) regardless of the first decryption status. The ECHO status accept/reject can be conveyed based on distinction ClientHello2 inner/outer only. This point impacts the number of transcripts the client may have to try, i.e. can be sure the combination (ClientHelloInner1, ClientHelloOuter2) is never valid.
Hi NAME the spec has changed quite a bit since this question was first posed. In particular, we've landed , which changes the server behavior so that it provides an explicit signal of ECH acceptance in its SH. Hence, no more trial decryption. I'm wondering if your question still applies? I believe what to do in case of HRR is well-specified at this point. There is no such ECH accdeptance signal provided in the HRR, so to determine if ECH was accepted, the client must wait until the SH. Until that point, the client needs to compute two transcripts in parallel: one assuming the ClientHelloOuter was used, and another assuming the ClientHelloInner was used.
I don't think resolves this. If the client has different HRR-relevant preferences (key share and cipher suite) between CHInner and CHOuter, I believe HRR is still a mess because the HRR message doesn't contain a signal for whether ECH was accepted. Though I suppose it is a little less complex now that you don't need to go as far as trial decrypting. Different HRR-relevant preferences means the HRR may be good for one ClientHello but not the other, yet the client needs to manage that state and defer the actual error-handling to when ECH acceptance is known. An option where the HRR message included an ECH acceptance signal would close this, but then we have sticking out woes. The draft touches on this, but somewhat vaguely. URL We've been intending that our implementation would always match inner and outer preferences for HRR-sensitive fields, to avoid this case. But having this odd complexity cliff hidden in the spec is poor. I'd advocate we either require or at least strongly recommend clients do this.
suggests to add the following text to the HRR section: after. Likewise, it is an error for the client to offer ECH after the HelloRetryRequest but not before. If the client-facing server accepts ECH for the first ClientHello but not the second, or it accepts ECH for the second ClientHello but not the first, then it MUST abort the handshake with an "illegal_parameter" alert. I think this solves the problem, at least partially. It ensures that the HRR path aligns with ECH acceptance/rejection.
That doesn't address the problem. This is about complexity for the client, not the server. Yes, the server needs to enforce consistency between the two modes, but it is easy for it to do this. Imagine you're the client and your CHOuter has: keyshares = {X25519} supportedgroups = {X25519, P-256, P-384} ciphersuites = {TLSAES128GCMSHA256, TLSAES256GCMSHA384} And your CHInner has: keyshares = {P-256} supportedgroups = {X25519, P-256, P-384} ciphersuites = {TLSAES256GCMSHA384, TLSCHACHA20POLY1305SHA256} Now consider how you have to respond to each of these HRRs. Remember that at the time you process the HRR, you don't know if ECH was accepted. ciphersuite = TLSAES256GCMSHA384; keyshare = P-384 ciphersuite = TLSAES128CCMSHA256; keyshare = P-521 ciphersuite = TLSCHACHA20POLY1305SHA256; keyshare = X25519 ciphersuite = TLSAES128GCMSHA256; keyshare = P-256 ciphersuite = TLSCHACHA20POLY1305SHA256; keyshare = X25519 1 is valid for both ClientHellos. This is the easy case and you can compute new CHInner and CHOuter values. 2 is valid for neither ClientHello. If you can detect this, you can error immediately. 3 is valid for neither ClientHello, but for messy reasons. The key share is valid for CHOuter but the cipher suite is not. The cipher suite is valid for CHInner but the key share is not. (Recall that a key share in HRR is allowed if it is in CH.supportedgroups and not CH.keyshares. If it were in CH.keyshares, you shouldn't have sent HRR.) 4 is valid for only CHOuter, so you can't error. It is not possible to compute a CHInner, so I guess you drop the extension? But you still need to make a note to raise an error later if you see SH which claims it did accept ECH. 5 is valid for only CHInner, so you can't error. It is not possible to compute a CHOuter, but you have to, so I guess you send something garbage? But you still need to make a note to raise an error if you see SH which claims it rejected ECH. This is a huge mess. The client can avoid this mess by always matching keyshares, supportedgroups, and cipher_suites between CHInner and CHOuter. That means it can process HRR without knowing which CH to use. It is not obvious in the spec that you should do this, and the spec allows you to not do this.
I see, I wasn't thinking about client complexity. Thanks for laying out the various edge cases. I would favor being stricter about how the keyshares, supportedgroups, and ciphersuites are chosen. At the very least, we should guide implementations towards ensuring they are the same in the CHInner and CHOuter. An alternative would be to add an ECH signal to HRR. We can't do the same trick we did with the URL, as this would stick out. But maybe there's another way? I thought of using HRR.sessionid, but that could get messy.
I think there is still ambiguity in the design about what CH is used by the client-facing server in its own transcript when forwarding CHInner to the backend server. Or else the combination (ECH accepted before HRR, ECH rejected after HRR) can be explicitly forbidden, but I don't see why/how.
Incidentally, this is addressed by URL This adds the following to client-facing server behavior: after. Likewise, it is an error for the client to offer ECH after the HelloRetryRequest but not before. If either of these conditions occurs, then the client-facing server MUST abort the handshake with an "illegal_parameter" alert. It's not hard for the client-facing server to enforce this. It just needs to remember if an HRR was triggered and whether ECH was offered in in the first CH.
Ok that will do it. Assuming "ECH not offered" is same as "ECH not accepted", due to greasing.

If it compressed any extensions in EncodedClientHelloInner, it MUST copy the corresponding extensions from ClientHelloInner. <ins> It MUST ensure that all extensions or parameters in ClientHelloInner that might change in response to receiving HelloRetryRequest match that in ClientHelloOuter.  See client-hrr for more information. </ins> It MAY copy any other field from the ClientHelloInner except ClientHelloInner.random.  Instead, It MUST generate a fresh ClientHelloOuter.random using a secure random number generator. It MUST NOT include the "pre_shared_key" extension.  (See flow- clienthello-malleability.) <ins> [[OPEN ISSUE: We currently require HRR-sensitive parameters to match in ClientHelloInner and ClientHelloOuter in order to simplify client- side logic in the event of HRR.  See https://github.com/tlswg/draft- ietf-tls-esni/pull/316 for more information.  We might also solve this by including an explicit signal in HRR noting ECH acceptance. We need to decide if inner/outer variance is important for HRR- sensitive parameters, and if so, how to best deal with it without complicated client logic.]] </ins> The client might duplicate non-sensitive extensions in both messages. However, implementations need to take care to ensure that sensitive extensions are not offered in the ClientHelloOuter.  See outer- 6.1.3. <ins> As required in real-ech, clients offering ECH MUST ensure that all extensions or parameters that might change in response to receiving a HelloRetryRequest have the same values in ClientHelloInner and ClientHelloOuter.  That is, if a HelloRetryRequest causes a parameter to be changed, the same change is applied to both ClientHelloInner and ClientHelloOuter.  Applicable parameters include: TLS 1.3 RFC8446 ciphersuites in the ClientHello.cipher_suites list. The "key_share" and "supported_groups" extensions RFC8446.  (These extensions may be copied from ClientHelloOuter into ClientHelloInner as described in real-ech.) Versions in the "supported_versions" extension, excluding TLS 1.2 and earlier.  Note the ClientHelloOuter MAY include these older versions, while the ClientHelloInner MUST omit them. Future extensions that might change across first and second ClientHello messages in response to a HelloRetryRequest MUST have the same value. </ins> If the server sends a HelloRetryRequest in response to the ClientHello, the client sends a second updated ClientHello per the rules in RFC8446.  However, at this point, the client does not know


MUST send when it offered an "encrypted_client_hello" extension that was not accepted by the server.  (See alerts.) <del> [[OPEN ISSUE: The following text is a remnant of the section on "ech_nonce".  It specifies client/server behavior that is unrelated to "ech_nonce".]] Per client-behavior and server-behavior, implementations are required to track, alongside each PSK established by a previous connection, whether ECH was accepted for this connection.  If so, this is referred to as an "ECH PSK".  Otherwise, it is a "non-ECH PSK".  This may be implemented by adding a new field to client and server session states. </del> 5.1. Some TLS 1.3 extensions can be quite large and having them both in <del> the inner and outer ClientHello will lead to a very large overall size.  One particularly pathological example is "key_share" with post-quantum algorithms.  In order to reduce the impact of duplicated extensions, the client may use the "outer_extension" extension. </del>  OuterExtensions MUST only be used in ClientHelloInner.  It consists of one or more ExtensionType values, each of which reference an The value of "ECHConfig.public_name" MUST be placed in the "server_name" extension. <del> It MUST NOT include the "pre_shared_key" extension.  [[OPEN ISSUE: The text needs to be updated to reflect this rule.]] </del>  The client then constructs the ClientHelloInner message just as it does a standard ClientHello, with the exception of the following necessary to ensure the backend server does not negotiate a TLS version that is incompatible with ECH. <del> It MUST NOT offer to resume any non-ECH PSK or any session for TLS 1.2 and below. </del>  It MAY offer any other extension in the ClientHelloOuter except those that have been incorporated into the ClientHelloInner as ClientHello, the client sends a second updated ClientHello per the rules in RFC8446.  However, at this point, the client does not know whether the server processed ClientHelloOuter or ClientHelloInner, <del> and MUST regenerate both values to be acceptable.  Note: if the inner and outer ClientHellos use different groups for their key shares or differ in some other way, then the HelloRetryRequest may actually be invalid for one or the other ClientHello, in which case a fresh ClientHello MUST be generated, ignoring the instructions in HelloRetryRequest.  Otherwise, the usual rules for HelloRetryRequest processing apply. </del>  Clients bind encryption of the second ClientHelloInner to encryption of the first ClientHelloInner via the derived ech_hrr_key by a "decode_error" alert if it is invalid.  It otherwise ignores the extension and MUST NOT use the retry keys.  Offering a GREASE extension is not considered offering an encrypted ClientHello for purposes of requirements in client-behavior.  In particular, the client MAY offer to resume sessions established case, it does not make any changes to the TLS messages, but just blindly forwards them. <del> If the server sends a NewSessionTicket message, the corresponding ECH PSK MUST be ignored by all other servers in the deployment when not </del>  negotiating ECH, including servers which do not implement this specification. own ticket and forwards the resulting ClientHello.  Assume the server then validates the PSK binder on the outer ClientHello and chooses connection parameters based on the inner ClientHello.  A server which <del> then validates information in the outer ClientHello ticket against information in the inner ClientHello, such as the SNI, introduces an </del>  oracle that can be used to test the encrypted SNI value of specific ClientHello messages. <del> ECH mitigates against this attack by requiring servers not mix-and- match information from the inner and outer ClientHello.  For example, if the server accepts the inner ClientHello, it does not validate binders in the outer ClientHello.  This means that ECH PSKs are used </del>  within the HPKE encryption envelope. 11. 12. <del> Any future information or hints that influence the outer ClientHello </del>  SHOULD be specified as ECHConfig extensions.  This is primarily because the outer ClientHello exists only in support of ECH.  Namely, it is both an envelope for the encrypted inner ClientHello and

We need to decide if we want to allow a GREASE+ticket distinguisher, and if not, how we GREASE the "presharedkey" extension in a meaningful way. The latter seems like somewhat of a cat and mouse game, so I'm not really enthusiastic about trying to specify that behavior, but I'd like to hear what others think. cc NAME NAME NAME
This looks good. I still feel that there are a lot of ways that ECH usage might stick out, even to passive observers. "presharedkey" is one of many. If anything I would change the "OPEN ISSUE" to encompass top-to-bottom "don't stick out" review.
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 current draft contains various rules around ECH and resumption. These rules date to before we started encrypting the entire ClientHello and were intended to avoid mixing up session resumptions between the public and private name, since the same presharedkey extension was used for both, and amount to saying implementations must add a boolean to their session state. Now that the public and private names correspond to different ClientHellos altogether, the client can (and should) only send inner name tickets in the inner ClientHello and outer name tickets in the outer ClientHello. () I suspect that means we no longer need further ECH special cases here.
This seems like a good simplification, but we should probably be careful about the interaction with cross-SNI resumption.
I think it just composes as expected? If you offer a PSK in the outer ClientHello, cross-SNI or no, it means that you are satisfied with it as an alternative to the certificate covering the public name. If you offer a PSK in the inner ClientHello, cross-SNI or no, it means you are satisfied with it as an alternative to the certificate covering the true name. Your ClientHellos should be consistent with this, which means that servers resuming isn't an issue. TBH I forgotten some of the details for why this was here. I found this long comment I wrote, but apparently past me was just unsatisfied that a case (4) was "weird". So that was very unhelpful, past me. Regardless, I think the ambiguity is moot now. URL (I also expect clients just won't offer PSKs in the outer ClientHellos. It's unlikely that you'll get any to offer anyway. And if your API has an external session cache, the caller needs to go out of their way to do two lookups.)
I was thinking about clients that might mistakenly use inner tickets for the outer name when given the cross-SNI signal. (I agree that it composes naturally.) A simple note would probably suffice. For outer tickets, I tend to think they should just be discouraged. What's the benefit of resuming a TLS 1.3 connection you'd tear down anyway?
I'm not sure I fully understand the (historical) need to distinguish between ECH-PSKs and non-ECH-PSKs. But why would a client offer PSK to a client-facing server at all? In other words, will it ever be useful to send a PSK extension in the ClientHelloOuter? It seems to me that this feature is only meaningful to the intended backend server, and so should only appear in the ClientHelloInner. The same question applies to all other extensions. If the client-facing server terminates the connection, then the only action the client will take is to close the connection. Hence, the only functionality we will ever need for the clientclient-facing server connection is to complete a full, server-authenticated handshake. If I were implementing the client, I would restrict the contents of the ClientHelloOuter to only those extensions that are necessary to implement this functionality. I wonder if it makes sense to enforce this in the spec. Something like: "When offering the 'ech' extension, the client MAY include the following extensions in the ClientHelloOuter: 'supportedversions', 'servername'. 'keyshares', 'signaturealgorithms', [and so on]. It MUST NOT include any other extension." What are the downsides to doing something like this? I think the upside is that it simplifies the problem of deciding what to put in the ClientHelloInner versus the ClientHelloOuter. I suppose one downside is that it potentially sticks out a bit.
Based on this week's interim, we decided it was fine to prohibit PSKs in the outer CH for ECH connections, so the current draft text prohibits "presharedkey" extensions in the outer CH. A connection using GREASE may, however, decide to resume its connection to the outer name. This gives a passive adversary a pretty easy distinguisher between GREASE and non-GREASE: if there's a ticket present, the connection is definitely GREASE. It seems that if we want to maintain our current indistinguishability properties, there always needs to be some ticket. So perhaps that means: ECH (non-GREASE) clients would generate a bogus (GREASEd) ticket GREASE clients that don't have a ticket would generate a bogus ticket GREASE clients that have a ticket would use it NAME NAME NAME NAME NAME penny for your thoughts?
I mostly figure there won't be a ticket in the initial connection to a server, or any connections made after long time, so intolerance to ticket-less ClientHellos will likely be noticed quickly. Though it is true that probably a lot of connections offer tickets. There may be something to be said for allowing clients to send bogus tickets if they want, but it's worth noting an active attacker can tell if the ticket is (most likely) bogus by simply trying to resume it. (A tangent: we actually saw a very old TLS 1.0 server implementation that broke with TLS 1.3 because it would take the client's session IDs and unconditionally echo it back. I'm not sure what was going on there. It didn't actually switch to a resumption handshake or implement resumption at all. It just echoed back the session ID for some reason. Only reason this didn't break before was that it never issued session IDs, so this codepath had never been tested before.)
True, but that's already the case for active attackers. What I'm concerned about are passive attackers.
I think this gets at a larger "don't stick out" problem currently only addressed in bits and pieces. Just as dummy (i.e., greased) ECH is used to provide covertext for real ECH, real-ECH connections also need to provide covertext for the real extensions offered in dummy-ECH connections. In particular, the spec should provide guidance on how to generate dummy extensions in the ClientHelloOuter. 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

MUST send when it offered an "encrypted_client_hello" extension that was not accepted by the server.  (See alerts.)  5.1. Some TLS 1.3 extensions can be quite large and having them both in  <ins> ClientHelloInner and ClientHelloOuter will lead to a very large overall size.  One particularly pathological example is "key_share" with post-quantum algorithms.  In order to reduce the impact of duplicated extensions, the client may use the "outer_extension" extension. </ins> OuterExtensions MUST only be used in ClientHelloInner.  It consists of one or more ExtensionType values, each of which reference an The value of "ECHConfig.public_name" MUST be placed in the "server_name" extension.  <ins> It MUST NOT include the "pre_shared_key" extension.  (See flow- resumption-oracle.) </ins> The client then constructs the ClientHelloInner message just as it does a standard ClientHello, with the exception of the following necessary to ensure the backend server does not negotiate a TLS version that is incompatible with ECH.  <ins> It MUST NOT offer to resume any session for TLS 1.2 and below. </ins> It MAY offer any other extension in the ClientHelloOuter except those that have been incorporated into the ClientHelloInner as ClientHello, the client sends a second updated ClientHello per the rules in RFC8446.  However, at this point, the client does not know whether the server processed ClientHelloOuter or ClientHelloInner,  <ins> and MUST regenerate both values to be acceptable.  Note: if ClientHelloOuter and ClientHelloInner use different groups for their key shares or differ in some other way, then the HelloRetryRequest may actually be invalid for one or the other ClientHello, in which case a fresh ClientHello MUST be generated, ignoring the instructions in HelloRetryRequest.  Otherwise, the usual rules for HelloRetryRequest processing apply. </ins> Clients bind encryption of the second ClientHelloInner to encryption of the first ClientHelloInner via the derived ech_hrr_key by a "decode_error" alert if it is invalid.  It otherwise ignores the extension and MUST NOT use the retry keys. <ins> [[OPEN ISSUE: if the client sends a GREASE "encrypted_client_hello" extension, should it also send a GREASE "pre_shared_key" extension? If not, GREASE+ticket is a trivial distinguisher.]] </ins> Offering a GREASE extension is not considered offering an encrypted ClientHello for purposes of requirements in client-behavior.  In particular, the client MAY offer to resume sessions established case, it does not make any changes to the TLS messages, but just blindly forwards them.  <ins> If the server sends a NewSessionTicket message, the corresponding PSK MUST be ignored by all other servers in the deployment when not </ins> negotiating ECH, including servers which do not implement this specification. own ticket and forwards the resulting ClientHello.  Assume the server then validates the PSK binder on the outer ClientHello and chooses connection parameters based on the inner ClientHello.  A server which  <ins> then validates information in ClientHelloOuter ticket against information in ClientHelloInner, such as the SNI, introduces an </ins> oracle that can be used to test the encrypted SNI value of specific ClientHello messages.  <ins> ECH mitigates against this attack by (1) prohibiting the "pre_shared_key" extension in ClientHelloOuter and (2) requiring servers ignore this extension when processing ClientHelloOuter. Thus, if the server accepts the inner ClientHello, it only validates binders in the inner ClientHello.  This means that PSKs are used </ins> within the HPKE encryption envelope. 11. 12.  <ins> Any future information or hints that influence ClientHelloOuter </ins> SHOULD be specified as ECHConfig extensions.  This is primarily because the outer ClientHello exists only in support of ECH.  Namely, it is both an envelope for the encrypted inner ClientHello and


does a standard ClientHello, with the exception of the following rules: <del> It MUST NOT offer to negotiate TLS 1.2 or below. </del>  It MUST NOT offer to resume any non-ECH PSK or any session for TLS 1.2 and below. 7.1. Upon receiving an "encrypted_client_hello" extension, the client- <del> facing server MUST check that it is able to negotiate TLS 1.3 or greater.  If not, it MUST abort the connection with a "handshake_failure" alert. </del>  The ClientECH value is said to match a known ECHConfig if there exists an ECHConfig that can be used to successfully decrypt

The version check on the outer ClientHello doesn't make much sense since, when ECH is accepted, the version is based on the inner ClientHello anyway. See URL I've left a TODO since it seems weird to have a client requirement like that be so load-bearing without a corresponding server check. Not sure where we should go there.
I like this change, but it might be worth resolving the TODO.
Removed the TODO in favor of some clarifying text on the client side.

does a standard ClientHello, with the exception of the following rules:  <ins> It MUST NOT offer to negotiate TLS 1.2 or below.  Note this is necessary to ensure the backend server does not negotiate a TLS version that is incompatible with ECH. </ins> It MUST NOT offer to resume any non-ECH PSK or any session for TLS 1.2 and below. 7.1. Upon receiving an "encrypted_client_hello" extension, the client-  <ins> facing server determines if it will accept ECH, prior to negotiating any other TLS parameters.  Note that successfully decrypting the extension will result in a new ClientHello to process, so even the client's TLS version preferences may have changed. </ins> The ClientECH value is said to match a known ECHConfig if there exists an ECHConfig that can be used to successfully decrypt


10.10.3. <del> This attack uses resumption PSKs as an oracle for dictionary attacks against a given ClientHello's true SNI.  To begin, the attacker first interacts with a server to obtain a resumption ticket for a given test domain, such as "test.com".  Later, upon receipt of a legitimate ClientHello without a PSK binder, it computes a PSK binder using its own ticket and forwards the resulting ClientHello.  Assume the server then validates the PSK binder on the outer ClientHello and chooses connection parameters based on the inner ClientHello.  A server which then validates information in ClientHelloOuter ticket against information in ClientHelloInner, such as the SNI, introduces an oracle that can be used to test the encrypted SNI value of specific ClientHello messages. ECH mitigates against this attack by (1) prohibiting the "pre_shared_key" extension in ClientHelloOuter and (2) requiring servers ignore this extension when processing ClientHelloOuter. Thus, if the server accepts the inner ClientHello, it only validates binders in the inner ClientHello.  This means that PSKs are used within the HPKE encryption envelope. </del>  11.

I think this is a more general attack and deserves a more general description of the mitigation. See URL and . NAME

10.10.3.  <ins> 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 mechanism described in outer-extensions references parts of a potentially attacker-controlled ClientHelloOuter to construct ClientHelloInner, or a buggy server may incorrectly apply parameters from ClientHelloOuter to the handshake. To begin, the attacker first interacts with a server to obtain a resumption ticket for a given test domain, such as "example.com". Later, upon receipt of a ClientHelloOuter, it modifies it such that the server will process the resumption ticket with ClientHelloInner. If the server only accepts resumption PSKs that match the server name, it will fail the PSK binder check with an alert when ClientHelloInner is for "example.com" but silently ignore the PSK and continue when ClientHelloInner is for any other name.  This introduces an oracle for testing encrypted SNI values. This attack may be generalized to any parameter which the server varies by server name, such as ALPN preferences. 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" extension, which left the overall ClientHello vulnerable to an analogue of this attack. </ins> 11.


The client might duplicate non-sensitive extensions in both messages. However, implementations need to take care to ensure that sensitive <del> extensions are not offered in the ClientHelloOuter.  [[OPEN ISSUE: We should provide guidance on what extensions are sensitive and suggest suitable substitutes.]] </del>  To encrypt EncodedClientHelloInner, the client first needs to generate the HPKE encryption context.  It computes the encapsulated values associated with the true server name in the ClientHelloOuter. It MAY send such values in the ClientHelloInner.  Values which are independent of the true server name, or other information the client wishes to protect, MAY be included in <del> ClientHelloOuter and compressed as described in outer-extensions. </del>  10.6.

I noticed there's an OPEN ISSUE to add ClientHelloOuter guidance, but there's already a section for it. Link to it and flesh it out a little more. Sadly, in doing so, I've traded one OPEN ISSUE for another OPEN ISSUE, but hopefully that'll be closed shortly.

The client might duplicate non-sensitive extensions in both messages. However, implementations need to take care to ensure that sensitive  <ins> extensions are not offered in the ClientHelloOuter.  See outer- clienthello for additional guidance. </ins> To encrypt EncodedClientHelloInner, the client first needs to generate the HPKE encryption context.  It computes the encapsulated values associated with the true server name in the ClientHelloOuter. It MAY send such values in the ClientHelloInner. <ins> A client may also use different preferences in different contexts. For example, it may send a different ALPN lists to different servers or in different application contexts.  A client that treats this context as sensitive SHOULD NOT send context-specific values in ClientHelloOuter. </ins> Values which are independent of the true server name, or other information the client wishes to protect, MAY be included in  <ins> ClientHelloOuter.  If they match the corresponding ClientHelloInner, 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 corresponding outer extension SHOULD NOT be compressed. [[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.]] 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- stick-out. </ins> 10.6.


ClientHelloInner and ClientHelloOuter will lead to a very large overall size.  One particularly pathological example is "key_share" with post-quantum algorithms.  In order to reduce the impact of <del> duplicated extensions, the client may use the "outer_extension" </del>  extension. <del> OuterExtensions MUST only be used in ClientHelloInner.  It consists of one or more ExtensionType values, each of which reference an extension in ClientHelloOuter, and a digest of the complete ClientHelloInner. </del>  When sending ClientHello, the client first computes ClientHelloInner, <del> including any PSK binders, and 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: </del>  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 <del> ClientHelloInner with a single "outer_extension" extension.  The list of outer extensions, OuterExtensions.outer_extensions, includes those which were removed from ClientHelloInner, in the order in which they were removed. This process is reversed by client-facing server.  Specifically, the server replaces the "outer_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>  6. It SHOULD contain TLS padding RFC7685 as described in padding. <del> The client then constructs the ClientHelloOuter message just as it does a standard ClientHello, with the exception of the following rules: </del>  It MUST offer to negotiate TLS 1.3 or above. <del> Any extensions compressed as described in outer-extensions must match the ClientHelloInner.  [[OPEN ISSUE: When #331 and compression ordering is resolved, be a bit more precise here.]] </del>  It MAY copy any other field from the ClientHelloInner except ClientHelloInner.random.  Instead, It MUST generate a fresh should provide guidance on what extensions are sensitive and suggest suitable substitutes.]] <del> To encrypt ClientHelloInner, the client first needs to generate the HPKE encryption context.  It computes the encapsulated key, context, and HRR key (see hrr) as: </del>  Note that the HPKE functions Deserialize and SetupBaseS are those which match "ECHConfig.kem_id" and the AEAD/KDF used with "context" <del> are those which match client's chosen preference from "ECHConfig.cipher_suites".  The encrypted ClientHelloInner is computed as: </del>  <del> The payload of the "encrypted_client_hello" extension in the </del>  ClientHelloOuter is a "ClientECH" with the following values: "cipher_suite", the client's chosen cipher suite; to ECHConfig, as follows: If decryption fails, the server MUST abort the connection with a <del> "decrypt_error" alert.  Otherwise, the server scans ClientHelloInner for an "outer_extension" extension and substitutes its value with the values in ClientHelloOuter and validates the ClientHelloInner digest, as described in outer-extensions. </del>  Upon determining the true SNI, the client-facing server then either serves the connection directly (if in Shared Mode), in which case it encrypted_client_hello(0xff08), with "TLS 1.3" column values being set to "CH, EE", and "Recommended" column being set to "Yes". <del> outer_extension(0xff04), with the "TLS 1.3" column values being set to "CH", and "Recommended" column being set to "Yes". </del>  11.2.

Along the way, I've made two additional tweaks: Renamed outerextension to outerextensions, now that it contains multiple extensions. One of the diagrams even already referred to "outerextensions" because I didn't realize it was called "outerextension".Clarified in compression process and ClientHelloInner construction rules that the to-be-compressed extensions must be consecutive. Hopefully that helps with .
NAME FYI. I'm hoping this will help clarify why the ordering thing isn't actually a big deal.
I've updated the PR to include text to disambiguate the serialization. It's ambiguous whether the encoding includes the 4-byte (12-byte in DTLS) header from the Handshake structure or is just the body, from the ClientHello structure. I've picked the smaller one. This also saves having to answer, in DTLS, what happens when the inner sequence number is wrong.
There are two ClientHelloInners, the actual one used in the handshake, and the encoded one with outerextensions substituted in. Both are described as ClientHelloInner, but this makes it slightly ambiguous which goes into, say, the handshake transcript. (It's late now, so I'll leave this as an issue without a PR. If we don't end up resolving this separately, I'll probably fix this as part of . If we decide to implicitly copy over legacysession_id, the encryption payload becomes less and less defensibly an actual ClientHelloInner.)
In my own implementation, I've been referring to the plaintext payload as CompressedClientHelloInner. Which is terrible, but unambiguous.
Works for me. I was thinking EncodedClientHelloInner because it's shorter and still works even if you choose not to compress anything. (A no-op encoding is still an encoding.)
+1 to EncodedClientHelloInner, given that compression is optional. NAME if you have time, could you whip up a PR?

ClientHelloInner and ClientHelloOuter will lead to a very large overall size.  One particularly pathological example is "key_share" with post-quantum algorithms.  In order to reduce the impact of  <ins> duplicated extensions, the client may use the "outer_extensions" </ins> extension.  <ins> OuterExtensions consists of one or more ExtensionType values, each of which reference an extension in ClientHelloOuter, and a digest of the complete ClientHelloInner. </ins> When sending ClientHello, the client first computes ClientHelloInner,  <ins> including any PSK binders.  It then computes a new value, the EncodedClientHelloInner, by first making a copy of ClientHelloInner. 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: </ins> 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  <ins> 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. 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 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. The "outer_extensions" extension is only used for compressing the ClientHelloInner.  It MUST NOT be sent in either ClientHelloOuter or ClientHelloInner. </ins> 6. It SHOULD contain TLS padding RFC7685 as described in padding.  <ins> If it intends to compress any extensions (see outer-extensions), it MUST order those extensions consecutively. The client then constructs EncodedClientHelloInner as described in outer-extensions.  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.  <ins> If it compressed any extensions in EncodedClientHelloInner, it MUST copy the corresponding extensions from ClientHelloInner. </ins> It MAY copy any other field from the ClientHelloInner except ClientHelloInner.random.  Instead, It MUST generate a fresh should provide guidance on what extensions are sensitive and suggest suitable substitutes.]]  <ins> 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: </ins> Note that the HPKE functions Deserialize and SetupBaseS are those which match "ECHConfig.kem_id" and the AEAD/KDF used with "context"  <ins> are those which match the client's chosen preference from "ECHConfig.cipher_suites". </ins>  <ins> The value of the "encrypted_client_hello" extension in the </ins> ClientHelloOuter is a "ClientECH" with the following values: "cipher_suite", the client's chosen cipher suite; to ECHConfig, as follows: If decryption fails, the server MUST abort the connection with a  <ins> "decrypt_error" alert.  Otherwise, the server reconstructs ClientHelloInner from EncodedClientHelloInner, as described in outer- extensions. </ins> Upon determining the true SNI, the client-facing server then either serves the connection directly (if in Shared Mode), in which case it encrypted_client_hello(0xff08), with "TLS 1.3" column values being set to "CH, EE", and "Recommended" column being set to "Yes".  <ins> outer_extensions(0xff04), with the "TLS 1.3" column values being set to "", and "Recommended" column being set to "Yes". </ins> 11.2.


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>  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>  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>  The "outer_extensions" extension is only used for compressing the ClientHelloInner.  It MUST NOT be sent in either ClientHelloOuter or ClientHelloInner.  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>  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>  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>  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>  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:  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>  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>  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>  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>  extension, which left the overall ClientHello vulnerable to an analogue of this attack.

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

extension. OuterExtensions consists of one or more ExtensionType values, each of  <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.  <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  <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.  <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  <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.)  <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.  <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:  <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,  <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.  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  <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.  <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.


the one hand, the requirements on info seem weaker, but maybe actually this needs to be secret?  Analysis needed.]]  6.4. If the client attempts to connect to a server and does not have an

Issue .
I was thinking about and the discussion on server HRR checks started by URL, and I'm not sure HRR cookies even work in split mode right now. First off, suppose we don't merge something like . The current text says: This means if you get a cookied HRR only good for ClientHelloInner, there's no requirement to stick the cookie in ClientHelloOuter! But now the client-facing server can't see the cookie, so it can't be stateless. It would seem like fixes this, but now we have a more fundamental problem: whose cookie? Suppose the client-facing server wishes to handle HRR statelessly. It decrypts the first ClientHelloInner and forwards it to the backend server. If the backend server responds with HRR, the client-facing server must forward the HRR as-is, or the transcript breaks. But the client-facing server has no opportunity to bind its state (echhrrkey) into the HRR cookie. (Perhaps there isn't even one. If there is, it's encrypted with the backend server's key anyway. Either way, it's fixed now.) The simplest solution to all this is to only support stateful client-facing servers in split mode, unless the client-facing and backend servers can coordinate enough to share one cookie. I don't know if that would satisfy everyone. I could imagine doing something horrific to URL, akin to what we did to URL, but that sounds really messy.
Let's mark this as an "OPEN ISSUE" in the draft.
Done. URL

the one hand, the requirements on info seem weaker, but maybe actually this needs to be secret?  Analysis needed.]] <ins> [[OPEN ISSUE: If the client-facing server implements stateless HRR, it has no way to send a cookie, short of as-yet-unspecified integration with the backend server.  Stateless HRR on the client- facing server works fine, however.  See issue #333.]] </ins> 6.4. If the client attempts to connect to a server and does not have an