Draft: draft-ietf-tls-esni

Commentary: [ This was split out of as the hopefully less controversial idea . Also it sits on top of . Ignore the first commit to look at it in isolation . ] We have two levels of mechanisms for servers to advertise parameters . Each ESNIConfig has multiple keys , so a server can suport multiple groups at once . These keys are stapled together so a single HTTPSSVC record delivers the entire configuration . Then we have a versions / extensions mechanism . A server supporting multiple versions of ESNI or multiple sets of mandatory extensions must advertise multiple  . Unlike the group advertisement , these require separate HTTPSSVC records . This means the full ESNI config is not reliably available to logic across different layers of the client stack . In particular , the HTTPSSVC spec text in [ 0 ] does not work . ( FYI ) Currently , a single ESNIConfig , rather than a set of them , is correlated with an IP address or CNAME , by way of the HTTPSSVC record . A server which supports multiple ESNI versions or mandatory extension sets must advertise several HTTPSSVC records , potentially with different IPs . DNS and transport logic must know TLS capabilities to pick the right HTTPSSVC record . This PR introduces an ESNIConfigs structure which advertises a list of ESNIConfig structures . The ESNIConfigs is the new unit of ESNI configuration . This should be paired with a change to draft - nygren - dnsop - svcb - httpssvc to carry an ESNIConfigs , not an ESNIConfig ( formerly ESNIKeys ) . Now the server can include the full config at once , so the DNS and transport logic can reasonably pick an HTTPSSVC purely based on whether the TLS stack supports ESNI , rather than the full set of capabilities . This fixes the text in [ 0 ] . That ESNIConfigs and ESNIConfig are separate structures is slightly goofy naming . Alternate names if folks prefer : ESNIConfig / ESNIConfigs =  / ESNIBundleESNIConfig / ESNIConfigs = ESNIKeys / ESNIConfig ( Nuisance : an ESNIConfig is a list of ESNIKeyses , which brings back the double plural . ) ( While I 'm here , fix the remnants of ESNIKeyShares . I 'll split that out if this PR is controversial , but GitHub is really bad at dependent PRs , so I 've left it together for now . ) [ 0 ] 
 Why does n't it work ? The transport side tries the highest - priority option , and the TLS library is free to reject it , in which case the transport side will try the next option , etc . All the transport side needs to know is whether the TLS library supports ESNI . Are you thinking about a case where the client only supports ESNI version 1 , and the server only offers ESNI version 2 ? 
 That presumes your retry loop is integrated up and down the layers . It also means that , unless you shortcircuit things and check ESNI ahead of time , you pay for a TCP roundtrip to the unsupported ESNIConfig addresses . It also does n't account for cases when the client does n't support any of the ESNI versions . In that case , it should connect without ESNI ( presuming it would be okay doing so without ESNI configs ) . 
 OK , I agree with all of this . I 'm happy to reduce the required level of integration , if we can find a way . Are you proposing that the TLS library should silently ignore an ESNIConfigs with all unsupported versions / ciphers , and send the SNI in cleartext ? That seems like a recommendation that should go in this draft . Or are you proposing a fallback check that goes back to the transport layer ? 
 It is in the draft . It says you ignore  you do n't understand . 
 Discussed in Singapore ; no objections . Will merge . 
 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 do n'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 and the HPKE bits . I 'm guessing you meant  ? There are multiple ECHOConfig values in a single ECHOConfigs , which came from ( 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 : echo = ECHOv1 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 : echo={ECHOv1,ECHOv2 } 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 : echo={ECHOv1 } 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 did n'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 . ) 
 Got ta 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 does n't need to be supported . I do n'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 . 
 As of draft - ietf - tls - esni-04 , when the server rejects ESNI with esniretryrequest , it includes one or more ESNIKeys for retry : The client behavior described in section 5.1.2 is to pick one of the ESNIKeys values from this vector : Encoding of contains the total size of the vector field but not the size of the individual ESNIKeys elements . This makes it difficult ( impossible ? ) to parse a vector with unknown future versions , so this part is not really extensible . 
 Thanks for the comment ! should resolve this . It 's a little goofy - looking but ah well . 
 Thank you for confirming the issue . Not the nicest but will be effective . It 's the only real blocking point I see when implementing draft  . 
 I do n't know that I can give out my advice here before , so I had written an e - mail to draft - rescorla - tls- and that 's my first time to send an email to IETF . I do n't know whether sending an e - mail is the right thing , so I would paste it here again . I 'm not a native English speaker , I would apologize for my poor English first . It is n't possible for us to set a txt record for wildcard A or AAAA record ( esni . * 60S IN TXT " ... " " ... " ) . The same problem as mentioned . In general , we ca n't change the DNS record very conveniently ( except some DNS which provide API ) , so that most domain owners wo n't change the key very often . And in some cases , domain owners host their site at host service providers ( I do n't know how to explain that in English . Host service provider use one physical or virtual server to host many sites ) once the provider want to change the ESNI public key , all owners on this server need to change their txt record on time , which is very inconvenient and nearly impossible . The lack of frequently changing public key and the lack of Perfect Forward Security only provide us little security . As DNS record is needed to be set , only the owner of a exist and valid domain can enable ESNI . However , sometimes we want to use encrypted - sni as a private entry . For example , I configured my server that client hello with the sni " " would lead to the vps control panel ( I issued a cert for " " from my own PKI ) . Though I configured that the server must verify my identity using a client certificate , I do n't want this private sni to be known to the public . But your draft 's method wo n't allow this use case as " " is n't a valid domain name , so that I wo n't be able to own this domain and set a DNS record for it . The hosts file can only take the place of A and AAAA record and wo n't be able to set public key in hosts in the near future . This ESNI draft requires that the server can the decrypt the sni which is encrypted by any public key . This means that the server has to take hold all ESNI private keys . However , I think that it 's not proper . To CDN service provider like Cloudflare , it means that one Cloudflare server has to storage all private ESNI keys of the websites hosted on it . In my opinion , the browser should encrypt the sni using all public keys found in the DNS record and add a few invalid ESNI along with those , to prevent the attacker identify the real sni from the number of public keys . In some countries ( e.g. China , North Korea , Iran ) , people may want to use ESNI to break the government 's GreatFireWall , which blocks people from visiting some website . The wall is n't just a wall , but a very complicated system which can modify DNS resolve result ( DNSSEC is n't common ) . They can simply block all esni txt record , so that all browsers can ' get the public key and downgrade to cleartext - sni . If ESNI can be used with   , it would be a good solution to break the wall . However the DNS record limited only the owner can enable ESNI ( Another disadvantage of DNS :D ) I think some ideas of is quite good . One good idea is to use a certificate to authenticate the Client - Facing Server instead of publishing a public key . My main idea is to introduce a series of HTTP headers like HSTS and HPKP . I will give an example below : Firstly , I would introduce these headers ( These names may be not proper due to my poor English ) It 's impossible to have more than one header with the same name , but we may construct these in to a structure and encode ( e.g.base64 ) into one header . The header may be very big , but do n't worry , only the first response contains it , HTTP2 will automatically compress them into a few bits in the later response . When the first time I visit , I just do the normal tls1.3 hand shake with clear - text sni . The server just do what it should do , but the response MUST contain those ESNI headers if ESNI is enabled . The browser MUST remember these headers ( unless in incognito mode ) When the second time I visit , the browser SHOULD NOT query DNS resolve for , instead , it should visit the FQDN / IP mentioned in the ESNI - Resolve header . And the browser MUST NOT send clear - text sni in the ClientHello , instead , send a CH without sni or a special CH indicates using ESNI . The Client - Facing Server send back certificate , the browser validates the certificate and makes sure the certificate contains at least one FQDN / IP mentioned in the ESNI - Trust header . If mismatch , check the ESNI - Policy and determine whether to retry or not . And then re - handshake with the server with encrypted - ClientHello in the 0RTT data or another CH extension . ( similar to draft - ietf - tls - sni - encryption method ) My suggestions ' advantages and disadvantages are nearly the same as draft - ietf - tls - sni - encryption method . It provides PFS , do n't need to change the Split - Mode Server and solved some part of DNS sniffing . But the disadvantage is obvious too , the first hand shake ca n't be encrypted and the ESNI - Resolve may become incorrect over time . It needs to handshake twice , however , it provided PFS and I think it worth it . And if multiple sites are hosted on the same server ( e.g. CDN , Host Service Provider ) , we just need to do the second handshake , which eliminate the risk of DoS attack in some level .... And I think  symmetric decryption is better than asymmetric decryption . Setting max - age too big or too small both have some risks , and I do n't know how to solve it . If you do n't like the idea of authenticating the Client - Facing Server , you can simply replace the ESNI - Trust header 's content into a public key and use your method . But I don ' think set a static public key is a good idea ( explained in some part of DNS disadvantage ) . All in all , we ca n't have both efficiency , performance and security , reliability . You should make up your mind and choose whether to provide perfect security or perfect performance . I prefer the former . I know that my idea is similar to draft - ietf - tls - sni - encryption than yours . However , you are the candidate for TLS WG , so I hope you can think it over . But I do agree with your idea that " no need to do n't stick out " or simply " we can stick out " :D 
 prevent this case happening . This introduces exposure and interception opportunities ; a TLS connection that remains connected may not require an external  . ( I am not sure ) TLS also works with protocols other than HTTP(S ) . If the ESNI relies on HTTP headers , it is HTTPS only . 
 To solve this problem , we can simply change from HTTP headers to TLS extensions . In TLS1.3 , handshake extensions are encrypted as well . The only thing need to be done is defining the structure of these settings . 
 Not seeing much support for removing DNS here , so closing this issue .
Paragraph (version A):
SNI Encryption configuration information is conveyed with the
following ESNIConfig structure.
Paragraph (version B):
SNI Encryption configuration information is conveyed with the
following ESNIConfigs structure.
Paragraph (version B):
The ESNIConfigs structure contains one or more ESNIConfig structures
in decreasing order of preference.  This allows a server to support
multiple versions of ESNI and multiple sets of ESNI extensions.
Paragraph (version B):
An opaque byte string whose contents depend on the version of the
structure.  For this specification, the contents are an
ESNIConfigContents structure.
Paragraph (version A):
padded_length The length to pad the ServerNameList value to prior to
encryption.  This value SHOULD be set to the largest ServerNameList
the server expects to support rounded up the nearest multiple of 16.
If the server supports arbitrary wildcard names, it SHOULD set this
value to 260.  Clients SHOULD reject ESNIConfig as invalid if
padded_length is greater than 260.
Paragraph (version B):
The length to pad the ServerNameList value to prior to encryption.
This value SHOULD be set to the largest ServerNameList the server
expects to support rounded up the nearest multiple of 16.  If the
server supports arbitrary wildcard names, it SHOULD set this value
to 260.  Clients SHOULD reject ESNIConfig as invalid if
padded_length is greater than 260.
Paragraph (version A):
In order to send an encrypted SNI, the client MUST first select one
of the server ESNIKeyShareEntry values 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 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 another value
provided by the server.  The client MUST NOT send encrypted SNI using
groups or cipher suites not advertised by the server.
Paragraph (version B):
In order to send an encrypted SNI, the client MUST first select one
of the ESNIConfig values in the ESNIConfigs structure which it is
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
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
another value provided by the server.  The client MUST NOT send
encrypted SNI using groups or cipher suites not advertised by the
server.
Paragraph (version A):
Zero padding whose length makes the serialized
PaddedServerNameList struct have a length equal to
ESNIConfig.padded_length.
Paragraph (version A):
This value consists of the serialized ServerNameList from the
"server_name" extension, padded with enough zeroes to make the total
structure ESNIConfig.padded_length bytes long.  The purpose of the
padding is to prevent attackers from using the length of the
"encrypted_server_name" extension to determine the true SNI.  If the
serialized ServerNameList is longer than ESNIConfig.padded_length,
the client MUST NOT use the "encrypted_server_name" extension.
Paragraph (version B):
Zero padding whose length makes the serialized
PaddedServerNameList struct have a length equal to
ESNIConfigContents.padded_length.
Paragraph (version B):
This value consists of the serialized ServerNameList from the
"server_name" extension, padded with enough zeroes to make the total
structure ESNIConfigContents.padded_length bytes long.  The purpose
of the padding is to prevent attackers from using the length of the
"encrypted_server_name" extension to determine the true SNI.  If the
serialized ServerNameList is longer than
ESNIConfigContents.padded_length, the client MUST NOT use the
"encrypted_server_name" extension.
Paragraph (version A):
Note: future extensions may end up reusing the server's
ESNIKeyShareEntry for other purposes within the same message (e.g.,
encrypting other values).  Those usages MUST have their own HKDF
labels to avoid reuse.
Paragraph (version B):
Note: future extensions may end up reusing the server's KeyShareEntry
for other purposes within the same message (e.g., encrypting other
values).  Those usages MUST have their own HKDF labels to avoid
reuse.
Paragraph (version A):
The client MUST place the value of ESNIConfig.public_name in the
"server_name" extension.  (This is required for technical conformance
with RFC7540; Section 9.2.)  The client MUST NOT send a "cached_info"
extension RFC7924 with a CachedObject entry whose
CachedInformationType is "cert", since this indication would divulge
the true server name.
Paragraph (version B):
The client MUST place the value of ESNIConfigContents.public_name in
the "server_name" extension.  (This is required for technical
conformance with RFC7540; Section 9.2.)  The client MUST NOT send a
"cached_info" extension RFC7924 with a CachedObject entry whose
CachedInformationType is "cert", since this indication would divulge
the true server name.
Paragraph (version A):
If the value is "esni_retry_request", the client proceeds with the
handshake, authenticating for ESNIConfig.public_name as described
in auth-public-name.  If authentication or the handshake fails,
the client MUST return a failure to the calling application.  It
MUST NOT use the retry keys.
Paragraph (version B):
If the value is "esni_retry_request", the client proceeds with the
handshake, authenticating for ESNIConfigContents.public_name as
described in auth-public-name.  If authentication or the handshake
fails, the client MUST return a failure to the calling
application.  It MUST NOT use the retry keys.
Paragraph (version A):
If the server negotiates an earlier version of TLS, or if it does not
provide an "encrypted_server_name" extension in EncryptedExtensions,
the client proceeds with the handshake, authenticating for
ESNIConfig.public_name as described in auth-public-name.  If an
earlier version was negotiated, the client MUST NOT enable the False
Start optimization RFC7918 for this handshake.  If authentication or
the handshake fails, the client MUST return a failure to the calling
application.  It MUST NOT treat this as a secure signal to disable
ESNI.
Paragraph (version B):
If the server negotiates an earlier version of TLS, or if it does not
provide an "encrypted_server_name" extension in EncryptedExtensions,
the client proceeds with the handshake, authenticating for
ESNIConfigContents.public_name as described in auth-public-name.  If
an earlier version was negotiated, the client MUST NOT enable the
False Start optimization RFC7918 for this handshake.  If
authentication or the handshake fails, the client MUST return a
failure to the calling application.  It MUST NOT treat this as a
secure signal to disable ESNI.
Paragraph (version A):
The client MUST verify that the certificate is valid for
ESNIConfig.public_name.  If invalid, it MUST abort the connection
with the appropriate alert.
Paragraph (version B):
The client MUST verify that the certificate is valid for
ESNIConfigContents.public_name.  If invalid, it MUST abort the
connection with the appropriate alert.
Paragraph (version A):
It MUST include the "encrypted_server_name" extension in
EncryptedExtensions message with the "response_type" field set to
"esni_retry_requested" and the "retry_keys" field set to one or
more ESNIConfig structures with up-to-date keys.  Servers MAY
supply multiple ESNIConfig values of different versions.  This
allows a server to support multiple versions at once.
Paragraph (version B):
It MUST include the "encrypted_server_name" extension in
EncryptedExtensions message with the "response_type" field set to
"esni_retry_requested" and the "retry_configs" field set to an up-
to-date ESNIConfigs structure.
Paragraph (version A):
If the ClientEncryptedSNI.key_share group does not match one in
the ESNIConfig.keys, it MUST abort the connection with an
"illegal_parameter" alert.
Paragraph (version B):
If the ClientEncryptedSNI.key_share group does not match one in
the ESNIConfigContents.keys, it MUST abort the connection with an
"illegal_parameter" alert.
Paragraph (version A):
If the decrypted value's length is different from the advertised
ESNIConfig.padded_length or the padding consists of any value other
than 0, then the server MUST abort the connection with an
"illegal_parameter" alert.  Otherwise, the server uses the
PaddedServerNameList.sni value as if it were the "server_name"
extension.  Any actual "server_name" extension is ignored, which also
means the server MUST NOT send the "server_name" extension to the
client.
Paragraph (version B):
If the decrypted value's length is different from the advertised
ESNIConfigContents.padded_length or the padding consists of any value
other than 0, then the server MUST abort the connection with an
"illegal_parameter" alert.  Otherwise, the server uses the
PaddedServerNameList.sni value as if it were the "server_name"
extension.  Any actual "server_name" extension is ignored, which also
means the server MUST NOT send the "server_name" extension to the
client.
Paragraph (version A):
Authenticating the ESNIConfig structure naturally authenticates the
included public name.  This also authenticates any retry signals from
the server because the client validates the server certificate
against the public name before retrying.
Paragraph (version B):
Authenticating the ESNIConfigs structure naturally authenticates the
included public name.  This also authenticates any retry signals from
the server because the client validates the server certificate
against the public name before retrying.