Code change:
We previously derived the acceptance signal from the handshake secret. This meant that clients which used the wrong ECHConfig might need to process ServerHello extensions twice before computing the signal, which can be problematic for some libraries. Given that the signal's secrecy is entirely dependent on URL, we can relax the signal computation and base it on the transcript alone, which includes URL, rather than a secret derived from that transcript. cc NAME NAME NAME
Yep, good change. Thanks, S.
Encoding the "ECH worked" signal in the URL lower octets is, at best, ugly. It also arguably causes violation of internal API designs in at least the OpenSSL code. And I'm not sure if there might be some interaction between that and ticket handling (just from the look of some code I've yet to understand really.) Do we really need such a hacky solution? Given the ECH sticks out in the CH, we could just define a non-encrypted extension in the SH to include the confirmation value maybe (though even that is pretty hacky).
I have some sympathy for this, but the point of this signal design was to make it difficult to distinguish GREASE from non-GREASE ECH.
Could you elaborate on the concern with ticket handling? I'm not sure I see how tickets would relate to this.
Sorry for being slow - should get to looking again at that bit of code in a few days. S
Just to synchronize the discussion across list/GitHub a bit, this was discussed a bit in URL I agree with NAME now that there's some fiddly interactions from two ClientHellos and ServerHello. I sketched out a possible change sketched out in URL We could compute ClientHelloInner...ServerHelloECHConf as in the draft right now. But then rather than feed that into the handshake secret computation, just rely on that transcript containing a secret URL and computing F(transcript). (Hash it down, KDF it with zero secret, whatever.) One observation is that, while this relies oddly on a secret URL, the existing construction does too. An attacker constructing a ServerHello gets to pick ServerHello.keyshare and, with knowledge of ClientHello.keyshare, compute the resulting shared secret.
One further observation here is that if the client uses the same shares for CHInner and CHOuter, the double key derivation piece doesn't really need to apply.
For my own reference, I just wanted to flag something that we've discussed on the list: the current acceptance signal interacts with PSK in a way that could easily cause a bug in an implementation that hasn't carefully vetted this interaction.
dns records are 8 bit clean and rfc 6763 is an example of a TXT record carrying arbitrary data
:+1: Base64 is not only complication. It is a space overhead for a large object like ESNIKeys. Seeing corruption of binary will be rare; we can detect them by validating the checksum and fallback to non-ESNI in such case. So why not go for binary? FWIW, base64 was introduced in .
This is solved in which removes base64 with new ESNI RR type.
We only added this since TXT records were claimed to not work well with binary, per Christian’s suggestion. I’m fine either way.
Thanks for the info. I found URL, in which NAME asks: We know that we can store more than 128 octets in TXT record, and that we have a precedent that uses TXT to store binary data, as NAME has pointed out. Regarding configurability, my understanding is that RFC 1035 defines how binary TXT records can be specified in a zone file, and therefore many DNS server "softwares" support having such records. Googling also tells me that managed DNS services like Route 53 and Azure DNS provide the capability, although surprisingly they use different escape sequences ( uses + 3 octal, uses + 3 decimal).
I do not think it’s a complication, and I’m not convinced space is an issue as per your comment above.
only tangentially related - NAME do you know why your URL txt record is broken into 127 char strings instead of 255? Is there a bug being worked around somewhere?
NAME Oh I did not notice that. I am using tinydns (of djbdns), and it seems like tinydns works that way (see URL).
NAME The issue about the size is that with base64 we might hit the 512-octet threshold when publishing keys of multiple strengths. As an example, query for URL currently returns a 186-byte response, which contains just secp256r1 key. If we add secp384r1 and secp521r1, I'd assume that the size will come near to 490 octets. In other words, for longer hostnames we will hit the 512-octet threshold if we publish three keys. If we avoid the base64 encoding, we will have enough room to store 3 ECDH keys that can provide 128-, 192-, 256-bit-level security. I do not think this argument is strong enough to prohibit the use of base64 for the following reasons: we will be using DoH / DNS over TLS between the client and the resolver we might be using DNS over UDP that has the 512-octet threshold between the resolver and the authoritative server, but we would want that to migrate to DNSSEC or something even better we might be just fine with publishing only two keys: X25519, X448 But it does make me prefer not to have the overhead of base64, if that is possible.
Fixed in .
Seems fine to me. Christian Huitema wrote: computation per se. Actually doing trial decryption with the secret requires reaching down into the record layer. This is especially onerous for QUIC, where the record layer is offloaded to another protocol (often outside the TLS library). I think going back to full trial decryption would still considerably increase the cost of implementing ECH over the current draft. That said, I do agree that even the first part seems more onerous than expected and it's worth reconsidering things. We switched using just the randoms to the full handshake secret to mitigate some issues. Perhaps there's an intermediate point that still mitigates those issues but avoids the expensive part of the secret computation? What if we kept the ClientHelloInner...ServerHelloECHConf transcript as in the current draft, but derived the signal exclusively from that? (Or from the client random and that transcript, though the transcript includes the client random anyway.) We'll still bind in the ServerHello extensions, and it still includes the encrypted inner ClientHello random, as in the randoms formulation. It doesn't do the job as clearly as the handshake secret did, but maybe it'll work? Implementation-wise, this variant would not require actually processing the ServerHello extensions. David
Hi all, I raised a github issue [1] related to (but different from) this but would prefer a discussion via email if that's ok. Depending how this goes, I can create a new GH issue I guess. 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

Related discussion:
text: 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