Change:
order bit set to 1.  A client which receives a mandatory extension they do not understand must reject the ESNIConfig content. <del> Any of the listed keys in the ESNIConfig value may be used to encrypt the SNI for the associated domain name.  The cipher suite list is orthogonal to the list of keys, so each key may be used with any cipher suite.  Clients MUST parse the extension list and check for unsupported mandatory extensions.  If an unsupported mandatory extension is present, clients MUST reject the ESNIConfig value. </del> <ins> Clients MUST parse the extension list and check for unsupported mandatory extensions.  If an unsupported mandatory extension is present, clients MUST reject the ESNIConfig value. </ins> 5.

Discussion:
[This was split out of as the possibly more controversial idea. It's a simplification but does make configs larger. Also it sits on top of and . Ignore the first two comments to look at it in isolation.] We have two layers of parameter negotiation. Multiple KeyShareEntry values in an ESNIConfig, and multiple ESNIConfig values in an ESNIConfigs. We only need one mechanism, so store only one KeyShareEntry in an ESNIConfig. This simplifies representing client and server configuration, which simplies implementation internals as well as interchange formats. Right now, on the server, configuration has two layers of lists, something like list(tuple(ESNIConfig, list(PrivateKey))). Now the server maintains list(tuple(ESNIConfig, PrivateKey)). Note also that draft-farrell-tls-pemesni-00 has this oddity around what to do if the ESNIKeys contains multiple keys. This is particularly a nuisance because a single standalone PRIVATE KEY / ESNIKEY pair is not usable because the server has committed to answering to all keys in the ESNIKeys. The server must consume multiple PEM files, match up the ESNIKeyses, and then assert that it has a complete set. The downside is that, in the likely case that the server only supports one version and one set of extensions, the non-key ESNI config bits (notably the public name; the rest is small) are repeated for every named group. However, the public name should not be that large and servers should only need one or two groups (see URL).
A server can publish multuple HTTPSSVC RRs. Each of those RRs can contain multiple ECHOConfigContent values (i.e. multiple public keys and public_names). I don't see a real need for two different ways to publish more than one public key for a given server. There is a minor efficiency benefit in terms of the number of octets published in the DNS, but there is a lot of additional complexity in terms of code, and if nobody really publishes multiple public keys in one RR value, then that code is likely where we'll find CVEs. Reducing down to one public key per RR value also reduces the complexity when writing code to create/publish RRs.

I think we have to support multiple RR values, because that's how the DNS works, and so the one to drop is the multiple public keys in one ECHOConfigs. IOW, I'd suggest one ECHOConfig has exactly one public key.
A single ECHOConfig already has only one public key from URL and the HPKE bits. I'm guessing you meant ECHOConfigs? There are multiple ECHOConfig values in a single ECHOConfigs, which came from URL (Gosh, that was a terrible name. My bad. Maybe we should rename it?) I think the rationale there still holds? The problem is that an HTTPSSVC RR implies transport-level decisions, while not every ECHOConfig can be consumed by your TLS logic. Folding them together means transport connect needs to be aware of the exact ECHO capabilities. For instance, the origin could advertise: URL echo=ECHOv1 URL echo=ECHOv2 The logic picking HTTPSSVC routes no longer gets a free choice. It must be aware of the TLS layer's capabilities (down to mandatory extensions and HPKE code points). ECHOConfigs means the expectation is: URL echo={ECHOv1,ECHOv2} URL echo={ECHOv2,ECHOv2} Now you pick a route freely and pass the entire opaque ECHO blob to TLS to interpret. Of course, one could still advertise: URL echo={ECHOv1} URL echo={ECHOv2} But you're no longer forced to explode it out like that, so we can say an ECHOv1-only client that happens to pick route2 can act as if route1 didn't exist. I.e. we set an expectation that all your routes must be equally capable. (Just as we have that expectation elsewhere. If route1 only speaks TLS 1.0, you're not getting TLS's downgrade protection because the attacker can always redirect you to the less secure config.)
Gotta admit I'm not at all getting what you're saying, sorry. As-is, clients need to write the code to handle multiple keys (and all other variations possible) at both levels, or if not, then we're missing text in the spec saying what doesn't need to be supported. I don't see a real benefit in that complexity.

As per today's call, we're closing this due to the simplifications it brings for client implementations, in particular around collating all logic about ECHOConfig compatibility inside the TLS stack without the need for additional interfaces.
This LGTM -- thanks, David!