Original text:
the one hand, the requirements on info seem weaker, but maybe actually this needs to be secret?  Analysis needed.]] <del> [[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.]] </del> 6.4. If the client attempts to connect to a server and does not have an 7.1.1. <del> It is an error for the client to offer ECH before the HelloRetryRequest but not 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. </del>  7.2.

Reviewer comments:
A related PR that we probably should make sure flow together: URL
If a server implements ECH decryption via an RPC call, it's possible for the client's ECH keys to be valid for CH1 but invalid for CH2, perhaps due to a race condition that updates the server's keys across these messages. This should be exceedingly rare in practice, so failing the connection with an alert is probably fine, but we should probably be clear about this. cc NAME
There are two ways a accept-then-HRR-then-reject can happen: The client sends a after HRR that differs from the it sent before HRR. The is the same, but the server no longer recognizes it. The latter is the server's fault, and I think the only thing to do is abort with "internalerror". However, I think requiring that the doesn't change is a good idea. I wonder if this is implied by the client behavior? In any case, I think we should have the server abort with "illegalparameter" if the changes over HRR.
Is this any different from, say, the server's cipher suite capabilities changing in between CH1 and CH2 and thus the server not being able to meet what they said with HRR? I guess I've always assumed that, within the context of a single "handshake" from the perspective of the client, we expect the server to behave self-consistently and, if it can't, I guess the server can internal_error and live with failing one or two connections across server restarts.
I think this is a little different, since it can be triggered by changing the set of ECHConfigs the server knows, and not necessarily the set of cipher suites. I agree. And even though the situation is a bit different, I think "internalerror" is appropriate here as well. However, I also think the client SHOULD NOT change the ECHConfig across HRR. If it does, I think the server SHOULD abort with "illegalparamter". Do you think this would be hard for the server to enforce? In particular, a QUIC server might want to handle the HRR path statelessly, and this check requires remembering the previous .
I think that's already true. The client can't change things across HRR unless explicitly allowed. (URL) Exactly what the server enforces on HRR is... all over the place. I'm normally in favor of maximal enforcement, but enforcing all the rules of HRR is such a mess. It's especially messy for folks who do stateless HRR, because any state carried over across the two increases the size of the cookie. So I think we need to be a little careful here. Honestly, I think we didn't really get HRR right. QUIC does not care about stateless HRR. It did in the original design, but that's been dropped. DTLS does, though. And I believe NSS always implements it statelessly? (BoringSSL currently only does stateful.)
It sounds like a weak "MAY" would be most appropriate.
Can you elaborate?
Looking at this again, I suspect the minimal thing that needs to be checked across CH1 and CH2 is that the server cannot change its mind on whether it used ECH in CH2 and, if using ECH, it needs to have remembered echhrrkey (and HPKE cipher suites?) to bind the two. Dunno if it makes sense to just also remember the config_id at that point.
I agree, but I don't think tracking the is sufficient. Suppose the client sends an acceptable in CH1 but a rejecting in CH2. In this case doesn't provide the binding we need, since the client-facing server won't attempt to decrypt the payload in CH2. A conservative way to ensure the server doesn't change its mind is as follows: Require that the does not change across HRR. If it does, then abort with "illegalparameter" alert. Require that the does not change across HRR. If it does, then abort with "illegalparameter" alert. If isn't recognized after HRR --- say because of key rotation -- then abort with "internalerror" alert.  I'm not sure 1. is strictly necessary. If the isn't supported by the ECH configuration, then the server would abort with "illegalparameter". (This is already specified.) Moreover, I don't think we have any agility issues here, given that the derived AEAD key is bound to the cipher suite. However, this does ensure that the server chooses the HPKE cipher suite and KEM key in the same way before and after HRR. This may turn out to be useful.
Right, this is what I mean by "the server cannot change its mind on whether it used ECH in CH2". If you declined ECH in CH1, you ignore ECH in CH2. If you accepted ECH in CH1, failure to handle ECH in CH2 is fatal. But, yeah, if you remember the config used, you can check a bit more accurately, so maybe we should just do that and ask stateless HRRs make an even larger cookie. (Need to be a bit careful about itself because we allow a trial decryption flow too.)
For what it's worth, I'm not a huge fan of CH2 having a fresh HPKE exchange in the first place. That seems an unnecessary asymmetric operation. Were it not for stateless HRR, I think I'd advocate we just make CH2's ECH payload a second pop at the HPKE context's Seal() function (next sequence number), with the rest ignored. But with stateless HRR, we need to serialize the HPKE context into the cookie, which is mildly annoying.
I don't see how you can avoid CH2 having a new payload. Unless you are looking to just perform address validation (DTLS), you only send HRR to correct something in the ClientHello.
I think what NAME means is that the AEAD ciphertext could change without changing the encapsulating key. Hence, the ClientHelloInner can be changed without updating the HPKE shared secret. Wouldn't this leak the HPKE encryption key? The cookie extension is transmitted in the HRR in plaintext, right?
Specifically, HPKE supports multiple sequential encryption calls on a context with an internal sequence number. URL The cookie already needs to encrypted and authenticated by the server like tickets. It certainly needs to be authenticated independent of ECH, otherwise the attacker can manipulate it anyway. And then the existing echhrrkey construction requires encryption too. (If you didn't encrypt the cookie for some reason and only MAC'd it, this design would allow you to, rather than serializing the HPKE encryption key, remember the expected HPKE config, redo the KEM operation, and bump the sequence number up. But if you're remembering some secret for the MAC, may as well encrypt it...)
Right, thanks for again clarifying a misunderstanding of mine about the TLS spec :) Incidentally, I brought up specifying HPKE context serialization here: URL There doesn't seem to be a lot of appetite for it. I suppose we could specify it in ECH, but there are so many use cases for it that I think it would be worth specifying it in the HPKE draft.
Given the uncertainty about stateless HRR pointed out in , let's permit the client-facing server to abort if ECH usage changes after HRR. This allows stateful-HRR servers to enforce this behavior, but leaves it as an "OPEN ISSUE" about how stateful-hRR servers will enforce this. I'll prepare a PR tomorrow or Thursday.

Revised text:
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 7.1.1.  <ins> In case a HelloRetryRequest (HRR) is sent, the client-facing server MUST consistently accept or decline ECH between the two ClientHellos, using the same ECHConfig, and abort the handshake if this is not possible.  This is achieved as follows.  Let CH1 and CH2 denote, respectively, the first and second ClientHello transmitted on the wire by the client: If CH1 contains the "encrypted_client_hello" extension but CH2 does not, or if CH2 contains the "encrypted_client_hello" extension but CH1 does not, then the server MUST abort the handshake with an "illegal_parameter" alert. Suppose the "encrypted_client_hello" extension is sent in both CH1 and CH2, If the configuration identifier (see ech-configuration) differs between CH1 and CH2, then the server MUST abort with an "illegal_parameter" alert. [[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.  Stateful HRR on the client- facing server works fine, however.  See issue #333.]] </ins> 7.2.