Change:
While at it, remove the redundant "ClientHello." prefix, the name KeyShareClientHello implies this. _ NAME I did not explicitly mention that "extension_data" is a list of key shares, but hope that the suggested phrase is strong enough to imply that. Do you think that is sufficient or should it become something like: (By the way, the list of client key shares could contain one or more entries, but it could also be empty if the client would like to trigger a HRR. That should still be OK since the key/nonce is derived from the client hello random.)
Those changes seem okish though could maybe be better. My issue was that OpenSSL (afaics) previously ignored all the key shares except the one used, so handling two of 'em as the AAD meant adding some new state. No idea if other implementations are like that though. I guess it might help someone else if it explicitly said that there can be >1 key share to include in the AAD. (BTW, on your BTW, I need to think more about ESNI and HRR handling, haven't tested that yet so not sure if there's a case where the encoding of 0 key shares would be used as ESNI AAD.)
If this adds so much complication, why not drop the KeyShareClientHello completely? If the purpose is to bind this to the original Client Hello, then the Client Random should be sufficient. An implementation that creates the encrypted_sni field is also responsible for generating a random client hello and a new ESNI key share. Assuming that these are always unique between Client Hellos, then removing the KeyShareClientHello extension should still preserve the binding property. And even if for some reason the ESNI key share is reused (e.g. shared between different client hello, or reusing one from KeyShareClientHello), the derived key/nonce would still be different if Client Hello Random is unique. NAME what was the reason for mixing the client key shares list in addition to the existing client random (commit 134b15c87b5c4cde7f52500138662f64757e2e9c)?
If you don't bind in the client key share, there is a trivial attack in which the attacker replaces the key share with their own, causing the server's certificate to be encrypted to them.
What NAME said above. :-)
Thanks for the clarifying text!
Oh I see, the sender of the ESNI extension does not have to prove its knowledge about the ESNI private key, so an attacker could trivially copy the victim's ESNI extension and client random into the attacker's handshake, and recover the certificate if the client key shares were not included. Even if the attacker cannot decrypt SNI, they can still observe the side-effects. Thanks! I think the text could be clarified to highlight this, I'll prepare a proposal for this.
A second point in issue wasn't addressed very clearly by . "...HKDF-Extract(0, Z) should probably become HKDF-Extract("", Z) or be explicit about the salt as is done in URL (or URL)." The current draft does clarify this in the text below, but I initially missed that. The reader has to know that the first argument to HKDF-Extract is named "salt" to put it together. I did end up getting it right, figuring this was intended to mean a buffer of zeros. I think it would be clearer to say:
NAME a PR to clarify this point is most welcome!
It would be ok to take this suggestion into consideration under the terms of the IETF's "Note Well" blue sheet terms, and incorporate it into a PR by the editors.
NAME I agree! Could you please provide that PR?
Oh, I was asking you folks to do it. If there's a reason I need to do it, I'd be happy to.
I don't think this is necessary, so while I'm willing to look at a PR, I'm not going to make one at this time.
It was initially not clear to me what should be included in the AAD for encrypting ClientESNIInner into ClientEncryptedSNI.encryptedsni. The spec currently says: ClientHello.KeyShareClientHello is not defined in this document. At first I thought it would cover ClientEncryptedSNI.keyshare, but that seemed odd. Later I found that it refers to the Key Share extension defined in . Perhaps this reference could be added? On a related note, should probably become or be explicit about the salt as is done in URL (or URL).
FWIW, I also had a little trouble with this. My initial interpretation was to use the h/s key share that ends up being used as the AAD, which worked fine for my client (vs cloudflare) and for my server (vs. NSS' tstclnt). In each of those cases though only one key share was sent by the client. FF nightly however sends two key shares in the h/s so I had to change my code to do the right thing. Could be worth a note in the internet-draft e.g. saying to use (the encoding of) all key shares from the h/s and that some clients do send more than one.

Discussion:
The SNI encryption key is computed from Z as follows: where ESNIContents is as specified below and Hash is the hash <del> function associated with the HKDF instantiation. </del> <ins> function associated with the HKDF instantiation.  The salt argument for HKDF-Extract is a string consisting of Hash.length bytes set to zeros. </ins> The client then creates a ClientESNIInner structure:

The ClientEncryptedSNI.encrypted_sni value is then computed using the usual TLS 1.3 AEAD: <del> Where ClientHello.KeyShareClientHello is the body of the extension but not including the extension header.  Including ClientHello.KeyShareClientHello in the AAD of AEAD-Encrypt binds the </del> <ins> Where KeyShareClientHello is the "extension_data" field of the "key_share" extension in a Client Hello (Section 4.2.8 of RFC8446)). Including KeyShareClientHello in the AAD of AEAD-Encrypt binds the </ins> ClientEncryptedSNI value to the ClientHello and prevents cut-and- paste attacks.

If the server sends a HelloRetryRequest in response to the ClientHello and the client can send a second updated ClientHello per the rules in RFC8446, the "encrypted_server_name" extension values <del> which do not depend on the (possibly updated) ClientHello.KeyShareClientHello, i.e,, ClientEncryptedSNI.suite, ClientEncryptedSNI.key_share, and ClientEncryptedSNI.record_digest, MUST NOT change across ClientHello messages.  Moreover, ClientESNIInner.nonce and ClientESNIInner.realSNI MUST not change across ClientHello messages.  Informally, the values of all unencrypted extension information, as well as the inner extension plaintext, must be consistent between the first and second ClientHello messages. </del> <ins> which do not depend on the (possibly updated) KeyShareClientHello, i.e,, ClientEncryptedSNI.suite, ClientEncryptedSNI.key_share, and ClientEncryptedSNI.record_digest, MUST NOT change across ClientHello messages.  Moreover, ClientESNIInner.nonce and ClientESNIInner.realSNI MUST not change across ClientHello messages. Informally, the values of all unencrypted extension information, as well as the inner extension plaintext, must be consistent between the first and second ClientHello messages. </ins> 5.1.3.