Code change:
The backend server begins by generating a message ServerHelloECHConf, which is identical in content to a ServerHello message with the exception that ServerHelloECHConf.random is equal to 24 random bytes <del> followed by 8 zero bytes.  It then computes a string where Derive-Secret and Handshake Secret are as specified in RFC8446, Section 7.1, and ClientHelloInner...ServerHelloECHConf refers to the sequence of handshake messages beginning with the first ClientHello and ending with ServerHelloECHConf.  Finally, the backend server constructs its ServerHello message so that it is equal to </del> <ins> followed by 8 zero bytes.  It then computes an 8-byte string where HKDF-Expand-Label and Transcript-Hash are as defined in RFC8446, Section 7.1, "0" indicates a string of Hash.length bytes set to zero, and ClientHelloInner...ServerHelloECHConf refers to the sequence of handshake messages beginning with the first ClientHelloInner and ending with ServerHelloECHConf.  (Note that ClientHelloInner and ServerHelloECHConf messages replace the ClientHello and ServerHello messages in the transcript hash sequence as specified in Section 4.1.1 of RFC8446.  Finally, the backend server constructs its ServerHello message so that it is equal to </ins> ServerHelloECHConf but with the last 8 bytes of ServerHello.random <del> set to the first 8 bytes of accept_confirmation. </del> <ins> set to "accept_confirmation". </ins> The backend server MUST NOT perform this operation if it negotiated TLS 1.2 or below.  Note that doing so would overwrite the downgrade

Related discussion:
text: ECH draft-09 (and -10) requires the client to verify that the server has processed ECH by comparing 8 octets in the URL with a value one can only know if you know the inner CH octets and (pretty much all) the rest of the h/s so far. That seemingly removes the need for the client to maybe decrypt twice (once based on inner CH, once based on outer) to see what works. That can be implemented ok for the nominal case. As far as I can see that was the result of github issue [2] back in Aug/Sep 2020. But in error cases that seems to require the client to possibly process the same ServerHello extensions twice, which could have side effects, is generally ickky and I think shows that this aspect of the design maybe needs a change. That's not much different from trial decryption really. In my current, (and still horrible;-), OpenSSL code the minimal effect is memory leaks for error cases that are tricky to handle, but can be handled, although at the expense of probably making the code even more brittle. (Note: I'm neither defending nor attacking the way OpenSSL handles extensions here:-) The specific problem is one needs the handshake secret to calculate the 8 octets one expects in the URL, but generating the handshake secret typically involves a key share ServerHello extension and might in future involve some PQC stuff, so I think we can't say for sure which subset of extensions are needed for the calculation. And if the first time doesn't give the right answer (e.g. if the client used the wrong ECHConfig) then you gotta process the ServerHello incl. its extensions a 2nd time. I think the requirement we ought try meet here is that any confirmation value needs to be something the client can calculate using values known before the SH is rx'd plus the ServerHello as a (mostly) unprocessed buffer. That could be e.g. some hash of the encoded inner CH, ECHConfig and the ServerHello maybe. Some of those issues were discussed in [2] already and in an interim meeting I missed but I reckon it might be worthwhile thinking again. Thanks, S. [1] URL [2] URL