Change:
[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!

Discussion:
The ESNIConfigs structure contains one or more ESNIConfig structures in decreasing order of preference.  This allows a server to support <del> multiple versions of ESNI and multiple sets of ESNI extensions. </del> <ins> multiple versions of ESNI and multiple sets of ESNI parameters. </ins> The ESNIConfig structure contains the following fields:

encryption keys.  This is used to repair misconfigurations, as described in handle-server-response. <del> The list of keys which can be used by the client to encrypt the SNI.  Every key being listed MUST belong to a different group. </del> <ins> The key which can be used by the client to encrypt the SNI. Clients MUST ignore any ESNIConfig structure with a key using a NamedGroup they do not support or are unwilling to use. </ins> The length to pad the ServerNameList value to prior to encryption. This value SHOULD be set to the largest ServerNameList the server

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.

In order to send an encrypted SNI, the client MUST first select one of the ESNIConfig values in the ESNIConfigs structure which it is <del> able to process (see esni-configuration).  It MUST then select one of the KeyShareEntry values in the ESNIConfig and generate an (EC)DHE share in the matching group.  This share will then be sent to the server in the "encrypted_server_name" extension and used to derive the SNI encryption key.  It does not affect the (EC)DHE shared secret used in the TLS key schedule.  The client MUST also select an </del> <ins> able to process (see esni-configuration).  It then generates an (EC)DHE share in the matching group.  This share will then be sent to the server in the "encrypted_server_name" extension and used to derive the SNI encryption key.  It does not affect the (EC)DHE shared secret used in the TLS key schedule.  The client MUST also select an </ins> appropriate cipher suite from the list of suites offered by the server.  If the client is unable to select an appropriate group or suite it SHOULD ignore that ESNIConfig value and MAY attempt to use

If the ClientEncryptedSNI value does match a known ESNIConfig, the server performs the following checks: <del> If the ClientEncryptedSNI.key_share group does not match one in the ESNIConfigContents.keys, it MUST abort the connection with an </del> <ins> If the ClientEncryptedSNI.key_share group does not match ESNIConfigContents.key, it MUST abort the connection with an </ins> "illegal_parameter" alert. If the length of the "encrypted_server_name" extension is