From 1daf948298fa00266fca96ace7d57de95025ad88 Mon Sep 17 00:00:00 2001 From: EKR Date: Sun, 3 Nov 2019 06:38:27 -0800 Subject: [PATCH 01/26] Update introduction --- draft-ietf-tls-esni.md | 37 +++++++++++++++++++++++-------------- 1 file changed, 23 insertions(+), 14 deletions(-) diff --git a/draft-ietf-tls-esni.md b/draft-ietf-tls-esni.md index 57c06b98..80cc895a 100644 --- a/draft-ietf-tls-esni.md +++ b/draft-ietf-tls-esni.md @@ -155,21 +155,30 @@ not have access to the plaintext of the connection. ## SNI Encryption -SNI encryption requires that each provider publish a public key and metadata which -is used for SNI encryption for all the domains for which it serves directly or -indirectly (via Split Mode). This document defines the format of the SNI encryption -public key and metadata, referred to as an ESNI configuration, and delegates DNS -publication details to {{!HTTPSSVC=I-D.nygren-dnsop-svcb-httpssvc}}, though other delivery -mechanisms are possible. In particular, if some of the clients of a private -server are applications rather than Web browsers, those applications might have -the public key and metadata preconfigured. +SNI encryption requires that each provider publish a public key and +metadata which is used for SNI encryption for all the domains for +which it serves directly or indirectly (via Split Mode). This document +defines the format of the SNI encryption public key and metadata, +referred to as an ESNI configuration, and delegates DNS publication +details to {{!HTTPSSVC=I-D.nygren-dnsop-svcb-httpssvc}}, though other +delivery mechanisms are possible. In particular, if some of the +clients of a private server are applications rather than Web browsers, +those applications might have the public key and metadata +preconfigured. When a client wants to form a TLS connection to any of the domains -served by an ESNI-supporting provider, it sends an "encrypted_server_name" -extension, which contains the true extension encrypted under the -provider's public key. The provider can then decrypt the extension -and either terminate the connection (in Shared Mode) or forward -it to the backend server (in Split Mode). +served by an ESNI-supporting provider, it constructs a ClientHello in +the regular fashion containing the true SNI value (ClientHelloInner) +and then encrypts it using the public key for the provider. It then +constructs a new ClientHello (ClientHelloOuter) with an innocuous SNI +(and potentially innocuous versions of other extensions such as ALPN +{{?RFC7301}}) and containing the encrypted ClientHelloInner as an +extension. It sends ClientHelloOuter to the server. + +Upon receiving ClientHelloOuter, the server can then decrypt +ClientHelloInner and either terminate the connection (in Shared Mode) +or forward it to the backend server (in Split Mode). + # Encrypted SNI Configuration {#esni-configuration} @@ -216,7 +225,7 @@ 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. +greater than 260. [[TODO: ekr]] extensions : A list of extensions that the client can take into consideration when From 5b76b9bda56ca29c3f31b2893dd9fa4970daa815 Mon Sep 17 00:00:00 2001 From: EKR Date: Sun, 3 Nov 2019 07:25:48 -0800 Subject: [PATCH 02/26] Rewrite the specification pieces --- draft-ietf-tls-esni.md | 350 +++++++++++++++-------------------------- 1 file changed, 129 insertions(+), 221 deletions(-) diff --git a/draft-ietf-tls-esni.md b/draft-ietf-tls-esni.md index 80cc895a..f3bea1fb 100644 --- a/draft-ietf-tls-esni.md +++ b/draft-ietf-tls-esni.md @@ -225,7 +225,8 @@ 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. [[TODO: ekr]] +greater than 260. [[OPEN ISSUE: How do we implement padding, presumably +with the padding extension.]] extensions : A list of extensions that the client can take into consideration when @@ -244,14 +245,14 @@ 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. -# The "encrypted_server_name" extension {#esni-extension} +# The "encrypted_client_hello" extension {#encrypted-client-hello} -The encrypted SNI is carried in an "encrypted_server_name" +The encrypted ClientHelloInner is carried in an "encrypted_client_hello" extension, defined as follows: ~~~ enum { - encrypted_server_name(0xffce), (65535) + encrypted_client_hello(TBD), (65535) } ExtensionType; ~~~ @@ -263,12 +264,12 @@ ClientEncryptedSNI structure: CipherSuite suite; KeyShareEntry key_share; opaque record_digest<0..2^16-1>; - opaque encrypted_sni<0..2^16-1>; - } ClientEncryptedSNI; + opaque encrypted_ch<0..2^16-1>; + } ClientEncryptedCH; ~~~~ suite -: The cipher suite used to encrypt the SNI. +: The cipher suite used to encrypt ClientHelloInner. key_share : The KeyShareEntry carrying the client's public ephemeral key share @@ -281,35 +282,21 @@ of the structure. This hash is computed using the hash function associated with `suite`. encrypted_sni -: The ClientESNIInner structure, AEAD-encrypted using cipher suite "suite" and -the key generated as described below. +: The serialized ClientHelloInner structure, AEAD-encrypted using +cipher suite "suite" and the key generated as described below. {:br} -For servers (in EncryptedExtensions), this extension contains the following -structure: -~~~ - enum { - esni_accept(0), - esni_retry_request(1), - } ServerESNIResponseType; +If the server accepts ESNI, then does not send this extension. +If it rejects ESNI, then it sends the following structure in +EncryptedExtensions: +~~~ struct { - ServerESNIResponseType response_type; - select (response_type) { - case esni_accept: uint8 nonce[16]; - case esni_retry_request: ESNIConfig retry_keys<1..2^16-1>; - } - } ServerEncryptedSNI; + ESNIConfig retry_keys<1..2^16-1>; + } ServerEncryptedCH; ~~~ -response_type -: Indicates whether the server processed the client ESNI extension. (See -{{handle-server-response}} and {{server-behavior}}.} - -nonce -: The contents of ClientESNIInner.nonce. (See {{client-behavior}}.) - retry_keys : One or more ESNIConfig structures containing the keys that the client should use on subsequent connections to encrypt the ClientESNIInner structure. @@ -318,12 +305,33 @@ This protocol also defines the "esni_required" alert, which is sent by the client when it offered an "encrypted_server_name" extension which was not accepted by the server. -~~~~ + +# The "esni_nonce" extension {#esni-nonce} + +When using ESNI, the client MUST also add an extension of type +"esni_nonce" to the ClientHelloInner (but not to the outer +ClientHello). + +The encrypted SNI is carried in an "encrypted_server_name" +extension, defined as follows: + +~~~ enum { - esni_required(121), - } AlertDescription; + esni_nonce(0xffce), (65535) + } ExtensionType; + + struct { + uint8 nonce[16]; + } ESNINonce; ~~~~ +nonce +: A random 16-octet value generated by the client and echoed by the +server. + +If server accepted ESNI, it MUST add the "esni_nonce" extension +with the client's nonce to EncryptedExtensions. + Finally, requirements in {{client-behavior}} and {{server-behavior}} require implementations to track, alongside each PSK established by a previous connection, whether the connection negotiated this extension with the @@ -338,7 +346,7 @@ to client and server session states. 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 +"encrypted_client_hello" 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 @@ -356,103 +364,34 @@ computed from Z as follows: ~~~~ Zx = HKDF-Extract(0, Z) - key = HKDF-Expand-Label(Zx, KeyLabel, Hash(ESNIContents), key_length) - iv = HKDF-Expand-Label(Zx, IVLabel, Hash(ESNIContents), iv_length) + key = HKDF-Expand-Label(Zx, KeyLabel, ClientHelloOuter.Random, key_length) + iv = HKDF-Expand-Label(Zx, IVLabel, ClientHelloOuter.Random, iv_length) ~~~~ -where ESNIContents is as specified below and Hash is the hash function -associated with the HKDF instantiation. The salt argument for HKDF-Extract is a -string consisting of Hash.length bytes set to zeros. For a client's first -ClientHello, KeyLabel = "esni key" and IVLabel = "esni iv", whereas for a -client's second ClientHello, sent in response to a HelloRetryRequest, -KeyLabel = "hrr esni key" and IVLabel = "hrr esni iv". (This label variance -is done to prevent nonce re-use since the client's ESNI key share, and -thus the value of Zx, does not change across ClientHello retries.) - -Note that ESNIContents will not be directly transmitted to the server in the -ClientHello. The server will instead reconstruct the same object by obtaining -its values from ClientEncryptedSNI and ClientHello. +Where the Hash for HKDFis the hash function associated with the HKDF +instantiation. The salt argument for HKDF-Extract is a string +consisting of Hash.length bytes set to zeros. For a client's first +ClientHello, KeyLabel = "esni key" and IVLabel = "esni iv", whereas +for a client's second ClientHello, sent in response to a +HelloRetryRequest, KeyLabel = "hrr esni key" and IVLabel = "hrr esni +iv". (This label variance is done to prevent nonce re-use since the +client's ESNI key share, and thus the value of Zx, does not change +across ClientHello retries.) [[TODO: label swapping fixes a bug in the spec, though this may not be the best way to deal with HRR. See https://github.com/tlswg/draft-ietf-tls-esni/issues/121 and https://github.com/tlswg/draft-ietf-tls-esni/pull/170 for more details.]] -~~~ - struct { - opaque record_digest<0..2^16-1>; - KeyShareEntry esni_key_share; - Random client_hello_random; - } ESNIContents; -~~~ - -record_digest -: Same value as ClientEncryptedSNI.record_digest. - -esni_key_share -: Same value as ClientEncryptedSNI.key_share. - -client_hello_random -: Same nonce as ClientHello.random. - -The client then creates a ClientESNIInner structure: - -~~~~ - struct { - opaque dns_name<1..2^16-1>; - opaque zeros[ESNIConfig.padded_length - length(dns_name)]; - } PaddedServerNameList; - - struct { - uint8 nonce[16]; - PaddedServerNameList realSNI; - } ClientESNIInner; -~~~~ - -nonce -: A random 16-octet value to be echoed by the server in the -"encrypted_server_name" extension. - -dns_name -: The true SNI DNS name, that is, the HostName value that would have been sent in the -plaintext "server_name" extension. (NameType values other than "host_name" are -unsupported since SNI extensibility failed {{SNIExtensibilityFailed}}). - -zeros -: Zero padding whose length makes the serialized PaddedServerNameList -struct have a length equal to ESNIConfig.padded_length. - -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. - -The ClientEncryptedSNI.encrypted_sni value is then computed using the usual -TLS 1.3 AEAD: - +The encrypted clientHello value is then computed as: ~~~~ - encrypted_sni = AEAD-Encrypt(key, iv, KeyShareClientHello, ClientESNIInner) + encrypted_sni = AEAD-Encrypt(key, iv, "", ClientHelloIInner) ~~~~ -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 ClientEncryptedSNI -value to the ClientHello and prevents cut-and-paste attacks. - -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. - [[OPEN ISSUE: If in the future you were to reuse these keys for 0-RTT priming, then you would have to worry about potentially -expanding twice of Z_extracted. We should think about how +expanding twice of Zx We should think about how to harmonize these to make sure that we maintain key separation.]] -This value is placed in an "encrypted_server_name" extension. - 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}} @@ -461,43 +400,49 @@ indication would divulge the true server name. ### Handling the server response {#handle-server-response} -If the server negotiates TLS 1.3 or above and provides an -"encrypted_server_name" extension in EncryptedExtensions, the client -then processes the extension's "response_type" field: - -- If the value is "esni_accept", the client MUST check that the extension's - "nonce" field matches ClientESNIInner.nonce and otherwise abort the - connection with an "illegal_parameter" alert. The client then proceeds - with the connection as usual, authenticating the connection for the origin - server. - -- 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. - - Otherwise, when the handshake completes successfully with the public name - authenticated, the client MUST abort the connection with an "esni_required" - alert. It then processes the "retry_keys" field from the server's - "encrypted_server_name" extension. - - If one of the values contains a version supported by the client, it can regard - the ESNI keys as securely replaced by the server. It SHOULD retry the - handshake with a new transport connection, using that value to encrypt the - SNI. The value may only be applied to the retry connection. The client - MUST continue to use the previously-advertised keys for subsequent - connections. This avoids introducing pinning concerns or a tracking vector, - should a malicious server present client-specific retry keys to identify - clients. - - If none of the values provided in "retry_keys" contains a supported version, - the client can regard ESNI as securely disabled by the server. As below, it - SHOULD then retry the handshake with a new transport connection and ESNI - disabled. - -- If the field contains any other value, the client MUST abort the connection - with an "illegal_parameter" alert. +As described in {{server-behavior}}, the server MAY either accept ESNI +and use ClientHelloInner or reject it and use ClientHelloOuter. However, +there is no indication in ServerHello of which one the server has done +and the client must therefore use trial decryption in order to determine +this. + +#### Accepted ESNI + +If the server used ClientHelloInner, the client MUST check that the +"esni_nonce" extension matches the nonce it used in ClientHelloInner. +otherwise abort the connection with an "illegal_parameter" alert. The client then proceeds +with the connection as usual, authenticating the connection for the origin +server. + +#### Rejected ESNI + +If the server used ClientHelloOuter, 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. + +Otherwise, when the handshake completes successfully with the public name +authenticated, the client MUST abort the connection with an "esni_required" +alert. It then processes the "retry_keys" field from the server's +"encrypted_server_name" extension. + +If one of the values contains a version supported by the client, it can regard +the ESNI keys as securely replaced by the server. It SHOULD retry the +handshake with a new transport connection, using that value to encrypt the +SNI. The value may only be applied to the retry connection. The client +MUST continue to use the previously-advertised keys for subsequent +connections. This avoids introducing pinning concerns or a tracking vector, +should a malicious server present client-specific retry keys to identify +clients. + +If none of the values provided in "retry_keys" contains a supported version, +the client can regard ESNI as securely disabled by the server. As below, it +SHOULD then retry the handshake with a new transport connection and ESNI +disabled. + +If the field contains any other value, the client MUST abort the connection +with an "illegal_parameter" alert. If the server negotiates an earlier version of TLS, or if it does not provide an "encrypted_server_name" extension in EncryptedExtensions, the @@ -522,18 +467,7 @@ servers which do not acknowledge the "encrypted_server_name" extension. If the client does not retry in either scenario, it MUST report an error to the calling application. -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 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 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. - -### Authenticating for the public name {#auth-public-name} +##### Authenticating for the public name {#auth-public-name} When the server cannot decrypt or does not process the "encrypted_server_name" extension, it continues with the handshake using the cleartext "server_name" @@ -561,6 +495,27 @@ trigger retries, as described in {{handle-server-response}}. This may be implemented, for instance, by reporting a failed connection with a dedicated error code. +#### HelloRetryRequest + +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}}. At this point, the client does not know whether the +server processed ClientHelloOuter or ClientHelloInner, and MUST +regenerate both values to be acceptable. Note: if the inner and outer +ClientHellos use different groups for their key shares or differ in +some other way, then the HRR may actually be invalid for one or the +other ClientHello. In that case, the Client MUST continue the +handshake without changing the unaffected CH. Otherwise, the usual +rules for HRR processing apply. + +[[OPEN ISSUE: This, along with trial decryption is +pretty gross. It would just be a lot easier if we were willing to +have the server indicate whether ESNI had been accepted or not. +Given that the server is supposed to only reject ESNI when it doesn't +know the key, and this is easy to probe for, can we just instead +have an extension to indicate what has happened.]] + + ### GREASE extensions {#grease-extensions} If the client attempts to connect to a server and does not have an ESNIConfig @@ -597,7 +552,7 @@ offer to resume sessions established without ESNI. ## Client-Facing Server Behavior {#server-behavior} -Upon receiving an "encrypted_server_name" extension, the client-facing +Upon receiving an "encrypted_client_hello" extension, the client-facing server MUST check that it is able to negotiate TLS 1.3 or greater. If not, it MUST abort the connection with a "handshake_failure" alert. @@ -619,12 +574,11 @@ If the ClientEncryptedSNI value does not match any known ESNIConfig structure, it MUST ignore the extension and proceed with the connection, with the following added behavior: -- 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. +- It MUST include the "encrypted_client_hello" extension in with 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. - The server MUST ignore all PSK identities in the ClientHello which correspond to ESNI PSKs. ESNI PSKs offered by the client are associated with the ESNI @@ -646,24 +600,10 @@ performs the following checks: - If the ClientEncryptedSNI.key_share group does not match one in the ESNIConfig.keys, it MUST abort the connection with an "illegal_parameter" alert. -- If the length of the "encrypted_server_name" extension is - inconsistent with the advertised padding length (plus AEAD - expansion) the server MAY abort the connection with an - "illegal_parameter" alert without attempting to decrypt. - Assuming these checks succeed, the server then computes K_sni -and decrypts the ServerName value. If decryption fails, the server +and decrypts the ClientHelloInner value. If decryption fails, the server MUST abort the connection with a "decrypt_error" alert. -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. - Upon determining the true SNI, the client-facing server then either serves the connection directly (if in Shared Mode), in which case it executes the steps in the following section, or forwards @@ -671,43 +611,11 @@ the TLS connection to the backend server (if in Split Mode). In the latter case, it does not make any changes to the TLS messages, but just blindly forwards them. -If the ClientHello is the result of a HelloRetryRequest, servers MUST -abort the connection with an "illegal_parameter" alert if any of the -ClientEncryptedSNI.suite, ClientEncryptedSNI.key_share, ClientEncryptedSNI.record_digest, -or decrypted ClientESNIInner values from the second ClientHello do not -match that of the first ClientHello. - -## Shared Mode Server Behavior - -A server operating in Shared Mode uses PaddedServerNameList.sni as -if it were the "server_name" extension to finish the handshake. It -SHOULD pad the Certificate message, via padding at the record layer, -such that its length equals the size of the largest possible Certificate -(message) covered by the same ESNI key. Moreover, the server MUST -include the "encrypted_server_name" extension in EncryptedExtensions -with the "response_type" field set to "esni_accept" and the "nonce" -field set to the decrypted PaddedServerNameList.nonce value from the client -"encrypted_server_name" extension. - If the server sends a NewSessionTicket message, the corresponding ESNI PSK MUST be ignored by all other servers in the deployment when not negotiating ESNI, -including servers which do not implement this specification. - -This restriction provides robustness for rollbacks (see {{misconfiguration}}). - -## Split Mode Server Behavior {#backend-server-behavior} - -In Split Mode, the backend server must know PaddedServerNameList.nonce -to echo it back in EncryptedExtensions and complete the handshake. -{{communicating-sni}} describes one mechanism for sending both -PaddedServerNameList.sni and ClientESNIInner.nonce to the backend -server. Thus, backend servers function the same as servers operating -in Shared Mode. +including servers which do not implement this specification (in Split mode, +the server can detect this case by the presence of the "esni_info" extension). -As in Shared Mode, if the backend server sends a NewSessionTicket message, the -corresponding ESNI PSK MUST be ignored by other servers in the deployment when -not negotiating ESNI, including servers which do not implement this -specification. # Compatibility Issues From 8cead14a9490d16659dc8b4236e05d988f0fbbfb Mon Sep 17 00:00:00 2001 From: EKR Date: Sun, 3 Nov 2019 07:46:43 -0800 Subject: [PATCH 03/26] Update the text on client side --- draft-ietf-tls-esni.md | 63 +++++++++++++++++++++++++----------------- 1 file changed, 37 insertions(+), 26 deletions(-) diff --git a/draft-ietf-tls-esni.md b/draft-ietf-tls-esni.md index f3bea1fb..0081ae89 100644 --- a/draft-ietf-tls-esni.md +++ b/draft-ietf-tls-esni.md @@ -197,7 +197,7 @@ ESNIConfig structure. opaque public_name<1..2^16-1>; KeyShareEntry keys<4..2^16-1>; CipherSuite cipher_suites<2..2^16-2>; - uint16 padded_length; + uint16 maximum_name_length; Extension extensions<0..2^16-1>; } ESNIConfig; ~~~~ @@ -219,14 +219,12 @@ keys : 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. -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. [[OPEN ISSUE: How do we implement padding, presumably -with the padding extension.]] +maximum_name_length +: the largest name 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 +256. Clients SHOULD reject ESNIConfig as invalid if maximum_name_length is +greater than 256. extensions : A list of extensions that the client can take into consideration when @@ -341,18 +339,27 @@ to client and server session states. ## Client Behavior {#client-behavior} -### Sending an encrypted SNI {#send-esni} +### Sending an encrypted ClientHello {#send-esni} -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_client_hello" 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. +In order to send an encrypted SNI, the client MUST first generate its +ClientHelloInner value. In addition to the normal values, ClientHelloInner +MUST also contain: + + - an "esni_nonce" extension + - a TLS padding {{!RFC7685}}. This SHOULD contain X bytes of padding + where X + the actual server name is equal to ESNIConfig.maximum_name_length + +Then, the client MUST 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_client_hello" +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. When offering an encrypted SNI, the client MUST NOT offer to resume any non-ESNI PSKs. It additionally MUST NOT offer to resume any sessions for TLS 1.2 or @@ -382,7 +389,7 @@ across ClientHello retries.) the best way to deal with HRR. See https://github.com/tlswg/draft-ietf-tls-esni/issues/121 and https://github.com/tlswg/draft-ietf-tls-esni/pull/170 for more details.]] -The encrypted clientHello value is then computed as: +The encrypted ClientHello value is then computed as: ~~~~ encrypted_sni = AEAD-Encrypt(key, iv, "", ClientHelloIInner) ~~~~ @@ -392,11 +399,15 @@ The encrypted clientHello value is then computed as: expanding twice of Zx We should think about how to harmonize these to make sure that we maintain key separation.]] -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. +Finally, the client MUST generate a ClientHelloOuter message +containing the "encrypted_client_hello" extension with the values as +indicated above. The cient MUST place the value of +ESNIConfig.public_name in the "server_name" extension. The remaining +contents of the ClientHelloOuter MAY be identical to those in +ClientHelloInner but MAY also differ. The ClientHelloOuter MUST NOT +contain a "cached_info" extension {{!RFC7924}} with a CachedObject +entry whose CachedInformationType is "cert", since this indication +would divulge the true server name. ### Handling the server response {#handle-server-response} From 30ed56d83f0ff81c653e488462479c48f3f779f4 Mon Sep 17 00:00:00 2001 From: EKR Date: Sun, 3 Nov 2019 07:55:06 -0800 Subject: [PATCH 04/26] Update the grease procedures --- draft-ietf-tls-esni.md | 41 +++++++++++++++++++---------------------- 1 file changed, 19 insertions(+), 22 deletions(-) diff --git a/draft-ietf-tls-esni.md b/draft-ietf-tls-esni.md index 0081ae89..8628cfb9 100644 --- a/draft-ietf-tls-esni.md +++ b/draft-ietf-tls-esni.md @@ -337,9 +337,9 @@ connection, whether the connection negotiated this extension with the Otherwise, it is a "non-ESNI PSK". This may be implemented by adding a new field to client and server session states. -## Client Behavior {#client-behavior} +# Client Behavior {#client-behavior} -### Sending an encrypted ClientHello {#send-esni} +## Sending an encrypted ClientHello {#send-esni} In order to send an encrypted SNI, the client MUST first generate its ClientHelloInner value. In addition to the normal values, ClientHelloInner @@ -375,7 +375,7 @@ computed from Z as follows: iv = HKDF-Expand-Label(Zx, IVLabel, ClientHelloOuter.Random, iv_length) ~~~~ -Where the Hash for HKDFis the hash function associated with the HKDF +Where the Hash for HKDF is the hash function associated with the HKDF instantiation. The salt argument for HKDF-Extract is a string consisting of Hash.length bytes set to zeros. For a client's first ClientHello, KeyLabel = "esni key" and IVLabel = "esni iv", whereas @@ -390,6 +390,7 @@ the best way to deal with HRR. See https://github.com/tlswg/draft-ietf-tls-esni/ and https://github.com/tlswg/draft-ietf-tls-esni/pull/170 for more details.]] The encrypted ClientHello value is then computed as: + ~~~~ encrypted_sni = AEAD-Encrypt(key, iv, "", ClientHelloIInner) ~~~~ @@ -401,7 +402,7 @@ to harmonize these to make sure that we maintain key separation.]] Finally, the client MUST generate a ClientHelloOuter message containing the "encrypted_client_hello" extension with the values as -indicated above. The cient MUST place the value of +indicated above. The client MUST place the value of ESNIConfig.public_name in the "server_name" extension. The remaining contents of the ClientHelloOuter MAY be identical to those in ClientHelloInner but MAY also differ. The ClientHelloOuter MUST NOT @@ -409,7 +410,7 @@ contain a "cached_info" extension {{!RFC7924}} with a CachedObject entry whose CachedInformationType is "cert", since this indication would divulge the true server name. -### Handling the server response {#handle-server-response} +## Handling the server response {#handle-server-response} As described in {{server-behavior}}, the server MAY either accept ESNI and use ClientHelloInner or reject it and use ClientHelloOuter. However, @@ -417,7 +418,7 @@ there is no indication in ServerHello of which one the server has done and the client must therefore use trial decryption in order to determine this. -#### Accepted ESNI +### Accepted ESNI If the server used ClientHelloInner, the client MUST check that the "esni_nonce" extension matches the nonce it used in ClientHelloInner. @@ -425,7 +426,7 @@ otherwise abort the connection with an "illegal_parameter" alert. The client the with the connection as usual, authenticating the connection for the origin server. -#### Rejected ESNI +### Rejected ESNI If the server used ClientHelloOuter, the client proceeds with the handshake, authenticating for ESNIConfig.public_name as described in @@ -478,7 +479,7 @@ servers which do not acknowledge the "encrypted_server_name" extension. If the client does not retry in either scenario, it MUST report an error to the calling application. -##### Authenticating for the public name {#auth-public-name} +#### Authenticating for the public name {#auth-public-name} When the server cannot decrypt or does not process the "encrypted_server_name" extension, it continues with the handshake using the cleartext "server_name" @@ -506,7 +507,7 @@ trigger retries, as described in {{handle-server-response}}. This may be implemented, for instance, by reporting a failed connection with a dedicated error code. -#### HelloRetryRequest +### HelloRetryRequest If the server sends a HelloRetryRequest in response to the ClientHello and the client can send a second updated ClientHello per the rules in @@ -527,11 +528,11 @@ know the key, and this is easy to probe for, can we just instead have an extension to indicate what has happened.]] -### GREASE extensions {#grease-extensions} +## GREASE extensions {#grease-extensions} If the client attempts to connect to a server and does not have an ESNIConfig structure available for the server, it SHOULD send a GREASE -{{I-D.ietf-tls-grease}} "encrypted_server_name" extension as follows: +{{I-D.ietf-tls-grease}} "encrypted_client_hello" extension as follows: - Select a supported cipher suite, named group, and padded_length value. The padded_length value SHOULD be 260 or a multiple of 16 less than @@ -546,22 +547,18 @@ structure available for the server, it SHOULD send a GREASE bytes, where hash_length is the length of the hash function associated with the chosen cipher suite. -- Set the "encrypted_sni" field to a randomly-generated string of - 16 + padded_length + tag_length bytes, where tag_length is the tag length - of the chosen cipher suite's associated AEAD. +- Set the "encrypted_client_hello" field to a randomly-generated string of + [TODO] bytes. -If the server sends an "encrypted_server_name" extension, the client +If the server sends an "encrypted_client_hello" extension, the client MUST check the extension syntactically and abort the connection with a -"decode_error" alert if it is invalid. If the "response_type" field -contains "esni_retry_requested", the client MUST ignore the extension -and proceed with the handshake. If it contains "esni_accept" or any other -value, the client MUST abort the connection with an "illegal_parameter" alert. +"decode_error" alert if it is invalid. Offering a GREASE extension is not considered offering an encrypted SNI for purposes of requirements in {{client-behavior}}. In particular, the client MAY offer to resume sessions established without ESNI. -## Client-Facing Server Behavior {#server-behavior} +# Client-Facing Server Behavior {#server-behavior} Upon receiving an "encrypted_client_hello" extension, the client-facing server MUST check that it is able to negotiate TLS 1.3 or greater. If not, @@ -585,7 +582,7 @@ If the ClientEncryptedSNI value does not match any known ESNIConfig structure, it MUST ignore the extension and proceed with the connection, with the following added behavior: -- It MUST include the "encrypted_client_hello" extension in with the +- It MUST include the "encrypted_client_hello" extension with 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 @@ -635,7 +632,7 @@ is not interoperable with existing servers, which expect the value in the existing cleartext extension. Thus server operators SHOULD ensure servers understand a given set of ESNI keys before advertising them. Additionally, servers SHOULD retain support for any -previously-advertised keys for the duration of their validity. +previously-advertised keys for the duration of their validity However, in more complex deployment scenarios, this may be difficult to fully guarantee. Thus this protocol was designed to be robust in case From 3913dad9acb9a0f6966f0d6433946a480706ab3d Mon Sep 17 00:00:00 2001 From: EKR Date: Sun, 3 Nov 2019 08:02:07 -0800 Subject: [PATCH 05/26] First at a "tunnelling ClientHello" PR. The general pattern is that the client creates a ClientHelloInner value with the true SNI, encrypts that, and puts it into an extension in ClientHelloOuter, which is what is actually sent to the server. There are a number of open issues here: - This design protects against the HRR splicing attack by having the transcript include ClientHelloInner.nonce, which relies on transcript secrecy. An alternative design would be to explicitly include it in the key schedule. However, this is a bit odd in split mode. - You have to use trial decryption on the client to determine whether the ClientHelloInner or ClientHelloOuter was accepted, and the client doesn't even know when it gets HRR whether it applies to the inner or outer CH. Should we consider having an extension indicate this? Obviously that leaks some information, however the primary thing that it leaks is whether the server knew the ESNI key, which can be independently determined by sending a new CH with the relevant config. This whole dual transcript thing is definitely a weak spot that would need some extensive analysis. From d99966454053d6fbc9caa90490a87d2c1a162fce Mon Sep 17 00:00:00 2001 From: EKR Date: Sat, 16 Nov 2019 18:12:10 -0800 Subject: [PATCH 06/26] Remove nonce echoing --- draft-ietf-tls-esni.md | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/draft-ietf-tls-esni.md b/draft-ietf-tls-esni.md index 8628cfb9..7499f4a5 100644 --- a/draft-ietf-tls-esni.md +++ b/draft-ietf-tls-esni.md @@ -327,9 +327,6 @@ nonce : A random 16-octet value generated by the client and echoed by the server. -If server accepted ESNI, it MUST add the "esni_nonce" extension -with the client's nonce to EncryptedExtensions. - Finally, requirements in {{client-behavior}} and {{server-behavior}} require implementations to track, alongside each PSK established by a previous connection, whether the connection negotiated this extension with the @@ -420,10 +417,8 @@ this. ### Accepted ESNI -If the server used ClientHelloInner, the client MUST check that the -"esni_nonce" extension matches the nonce it used in ClientHelloInner. -otherwise abort the connection with an "illegal_parameter" alert. The client then proceeds -with the connection as usual, authenticating the connection for the origin +If the server used ClientHelloInner, the client proceeds with the +connection as usual, authenticating the connection for the origin server. ### Rejected ESNI From 6cc05b362b93e5ebd9af042153036e1eb41be0f6 Mon Sep 17 00:00:00 2001 From: EKR Date: Sat, 16 Nov 2019 19:06:34 -0800 Subject: [PATCH 07/26] External extension --- draft-ietf-tls-esni.md | 51 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 51 insertions(+) diff --git a/draft-ietf-tls-esni.md b/draft-ietf-tls-esni.md index 7499f4a5..2b089de7 100644 --- a/draft-ietf-tls-esni.md +++ b/draft-ietf-tls-esni.md @@ -334,6 +334,46 @@ connection, whether the connection negotiated this extension with the Otherwise, it is a "non-ESNI PSK". This may be implemented by adding a new field to client and server session states. +## Incorporating External Extensions {#external-extensions} + +Some TLS 1.3 extensions can be quite large +and having them both in the inner and outer ClientHello wil lead to +a very large overall size. One particularly pathological example +is "key_share" with post-quantum algorithms. In order to reduce +the impact of duplicated extensions, the client may use the +"external_extension" extension. + +~~~ + enum { + esni_extension(TBD), (65535) + } ExtensionType; + + struct { + ExtensionType extension; + uint8 hash<32..255>; + } ExternalExtension; +~~~~ + +This extension MUST only be used in ClientHelloInner and contains +a digest of the corresponding extension in ClientHelloOuter. +When sending ClientHello, the client first computes ClientHelloInner, +including the PSK binders, and then MAY substitute any extensions +which it knows will be duplicated in ClientHelloOuter with +the corresponding "external_extension". The hash value is computed +over the entire extension, including the type and length field +and uses the same hash as for the KDF used to encrypt ClienHelloInner. +This process is reversed by client-facing server upon receipt. + +Clients SHOULD only use this mechanism for extensions which are +large. All other extensions SHOULD appear in both ClientHelloInner +and ClientHelloOuter even if they have identical values. + +Multiple "external_extension" extensions MAY appear in a ClientHelloInner +(this is a violation of normal TLS rules, but the resulting ClientHelloInner +is never processed directly). However, there MUST NOT be +multiple "external_extension" extensions with the same extension code point. + + # Client Behavior {#client-behavior} ## Sending an encrypted ClientHello {#send-esni} @@ -386,6 +426,10 @@ across ClientHello retries.) the best way to deal with HRR. See https://github.com/tlswg/draft-ietf-tls-esni/issues/121 and https://github.com/tlswg/draft-ietf-tls-esni/pull/170 for more details.]] +The client MAY replace any large, duplicated, extensions in ClientHelloInner +with the corresponding "external_extensions" extension, as described in +{{external-extensions}}. + The encrypted ClientHello value is then computed as: ~~~~ @@ -607,6 +651,13 @@ Assuming these checks succeed, the server then computes K_sni and decrypts the ClientHelloInner value. If decryption fails, the server MUST abort the connection with a "decrypt_error" alert. +Once the ClientHelloInner has been decrypted, the server MUST +scan it for any "external_extension" extensions and substitute their +values with the values in ClientHelloOuter. It MUST first verify that +the hash found in the extension matches the hash of the extension +to be interpolated in and if it does not, abort the connection +with a "decrypt_error" alert. + Upon determining the true SNI, the client-facing server then either serves the connection directly (if in Shared Mode), in which case it executes the steps in the following section, or forwards From 9c3974be15a93e10759cfd4a33652907502dd9c3 Mon Sep 17 00:00:00 2001 From: EKR Date: Sat, 16 Nov 2019 19:07:36 -0800 Subject: [PATCH 08/26] external -> outer --- draft-ietf-tls-esni.md | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/draft-ietf-tls-esni.md b/draft-ietf-tls-esni.md index 2b089de7..e7673132 100644 --- a/draft-ietf-tls-esni.md +++ b/draft-ietf-tls-esni.md @@ -334,14 +334,14 @@ connection, whether the connection negotiated this extension with the Otherwise, it is a "non-ESNI PSK". This may be implemented by adding a new field to client and server session states. -## Incorporating External Extensions {#external-extensions} +## Incorporating Outer Extensions {#outer-extensions} Some TLS 1.3 extensions can be quite large and having them both in the inner and outer ClientHello wil lead to a very large overall size. One particularly pathological example is "key_share" with post-quantum algorithms. In order to reduce the impact of duplicated extensions, the client may use the -"external_extension" extension. +"outer_extension" extension. ~~~ enum { @@ -351,7 +351,7 @@ the impact of duplicated extensions, the client may use the struct { ExtensionType extension; uint8 hash<32..255>; - } ExternalExtension; + } OuterExtension; ~~~~ This extension MUST only be used in ClientHelloInner and contains @@ -359,7 +359,7 @@ a digest of the corresponding extension in ClientHelloOuter. When sending ClientHello, the client first computes ClientHelloInner, including the PSK binders, and then MAY substitute any extensions which it knows will be duplicated in ClientHelloOuter with -the corresponding "external_extension". The hash value is computed +the corresponding "outer_extension". The hash value is computed over the entire extension, including the type and length field and uses the same hash as for the KDF used to encrypt ClienHelloInner. This process is reversed by client-facing server upon receipt. @@ -368,10 +368,10 @@ Clients SHOULD only use this mechanism for extensions which are large. All other extensions SHOULD appear in both ClientHelloInner and ClientHelloOuter even if they have identical values. -Multiple "external_extension" extensions MAY appear in a ClientHelloInner +Multiple "outer_extension" extensions MAY appear in a ClientHelloInner (this is a violation of normal TLS rules, but the resulting ClientHelloInner is never processed directly). However, there MUST NOT be -multiple "external_extension" extensions with the same extension code point. +multiple "outer_extension" extensions with the same extension code point. # Client Behavior {#client-behavior} @@ -427,8 +427,8 @@ the best way to deal with HRR. See https://github.com/tlswg/draft-ietf-tls-esni/ and https://github.com/tlswg/draft-ietf-tls-esni/pull/170 for more details.]] The client MAY replace any large, duplicated, extensions in ClientHelloInner -with the corresponding "external_extensions" extension, as described in -{{external-extensions}}. +with the corresponding "outer_extensions" extension, as described in +{{outer-extensions}}. The encrypted ClientHello value is then computed as: @@ -652,7 +652,7 @@ and decrypts the ClientHelloInner value. If decryption fails, the server MUST abort the connection with a "decrypt_error" alert. Once the ClientHelloInner has been decrypted, the server MUST -scan it for any "external_extension" extensions and substitute their +scan it for any "outer_extension" extensions and substitute their values with the values in ClientHelloOuter. It MUST first verify that the hash found in the extension matches the hash of the extension to be interpolated in and if it does not, abort the connection From 44a7d7dc0706c30932f645abd19fecfba3ef5883 Mon Sep 17 00:00:00 2001 From: EKR Date: Sat, 16 Nov 2019 19:08:36 -0800 Subject: [PATCH 09/26] Remove text that no longer applies --- draft-ietf-tls-esni.md | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/draft-ietf-tls-esni.md b/draft-ietf-tls-esni.md index e7673132..3596f643 100644 --- a/draft-ietf-tls-esni.md +++ b/draft-ietf-tls-esni.md @@ -901,20 +901,6 @@ registry for Resource Record (RR) TYPEs (defined in {{!RFC6895}}) with --- back -# Communicating SNI and Nonce to Backend Server {#communicating-sni} - -When operating in Split Mode, backend servers will not have access -to PaddedServerNameList.sni or ClientESNIInner.nonce without -access to the ESNI keys or a way to decrypt ClientEncryptedSNI.encrypted_sni. - -One way to address this for a single connection, at the cost of having -communication not be unmodified TLS 1.3, is as follows. -Assume there is a shared (symmetric) key between the -client-facing server and the backend server and use it to AEAD-encrypt Z -and send the encrypted blob at the beginning of the connection before -the ClientHello. The backend server can then decrypt ESNI to recover -the true SNI and nonce. - # Alternative SNI Protection Designs Alternative approaches to encrypted SNI may be implemented at the TLS or From cc171fd25ae04fdd91aa952068a10fd6ba706c2e Mon Sep 17 00:00:00 2001 From: Christopher Wood Date: Sun, 23 Feb 2020 20:09:39 -0800 Subject: [PATCH 10/26] s/ESNI/ECHO and a quick pass. --- draft-ietf-tls-esni.md | 355 ++++++++++++++++++++--------------------- 1 file changed, 172 insertions(+), 183 deletions(-) diff --git a/draft-ietf-tls-esni.md b/draft-ietf-tls-esni.md index bfbf50d5..62fd4e0b 100644 --- a/draft-ietf-tls-esni.md +++ b/draft-ietf-tls-esni.md @@ -1,7 +1,7 @@ --- title: Encrypted Server Name Indication for TLS 1.3 abbrev: TLS 1.3 SNI Encryption -docname: draft-ietf-tls-esni-latest +docname: draft-ietf-tls-echo-latest category: exp ipr: trust200902 @@ -96,8 +96,9 @@ coverage, or both. The design in this document takes a different approach: it assumes that private origins will co-locate with or hide behind a provider (CDN, app server, -etc.) which is able to activate encrypted SNI (ESNI) for all of the domains -it hosts. Thus, the use of encrypted SNI does not indicate that the +etc.) which is able to activate encrypted SNI, by encrypting the entire +ClientHello (ECHO), for all of the domains it hosts. +As a result, the use of ECHO to protect the SNI does not indicate that the client is attempting to reach a private origin, but only that it is going to a particular service provider, which the observer could already tell from the IP address. @@ -154,21 +155,23 @@ and the provider's server relays the connection back to the backend server, which is the true origin server. The provider does not have access to the plaintext of the connection. -## SNI Encryption +## ClientHello Encryption -SNI encryption requires that each provider publish a public key and -metadata which is used for SNI encryption for all the domains for +SNI encryption works by encrypting the entire ClientHello, including +the SNI and any additional extensions such as ALPN. +This requires that each provider publish a public key and +metadata which is used for ClientHello encryption for all the domains for which it serves directly or indirectly (via Split Mode). This document defines the format of the SNI encryption public key and metadata, -referred to as an ESNI configuration, and delegates DNS publication +referred to as an ECHO configuration, and delegates DNS publication details to {{!HTTPSSVC=I-D.nygren-dnsop-svcb-httpssvc}}, though other delivery mechanisms are possible. In particular, if some of the clients of a private server are applications rather than web browsers, those applications might have the public key and metadata preconfigured. -When a client wants to form a TLS connection to any of the domains -served by an ESNI-supporting provider, it constructs a ClientHello in +When a client wants to create a TLS connection to any of the domains +served by an ECHO-supporting provider, it constructs a ClientHello in the regular fashion containing the true SNI value (ClientHelloInner) and then encrypts it using the public key for the provider. It then constructs a new ClientHello (ClientHelloOuter) with an innocuous SNI @@ -181,10 +184,10 @@ ClientHelloInner and either terminate the connection (in Shared Mode) or forward it to the backend server (in Split Mode). -# Encrypted SNI Configuration {#esni-configuration} +# Encrypted SNI Configuration {#echo-configuration} SNI Encryption configuration information is conveyed with the following -ESNIConfigs structure. +ECHOConfigs structure. ~~~~ opaque HPKEPublicKey<1..2^16-1>; @@ -193,7 +196,7 @@ ESNIConfigs structure. struct { uint16 version; opaque contents<1..2^16-1>; - } ESNIConfig; + } ECHOConfig; struct { opaque public_name<1..2^16-1>; @@ -204,28 +207,28 @@ ESNIConfigs structure. uint16 maximum_name_length; Extension extensions<0..2^16-1>; - } ESNIConfigContents; + } ECHOConfigContents; - ESNIConfig ESNIConfigs<1..2^16-1>; + ECHOConfig ECHOConfigs<1..2^16-1>; ~~~~ -The ESNIConfigs structure contains one or more ESNIConfig structures in +The ECHOConfigs structure contains one or more ECHOConfig structures in decreasing order of preference. This allows a server to support multiple -versions of ESNI and multiple sets of ESNI parameters. +versions of ECHO and multiple sets of ECHO parameters. -The ESNIConfig structure contains the following fields: +The ECHOConfig structure contains the following fields: version : The version of the structure. For this specification, that value -SHALL be 0xff03. Clients MUST ignore any ESNIConfig structure with a +SHALL be 0xff03. Clients MUST ignore any ECHOConfig structure with a version they do not understand. [[NOTE: This means that the RFC will presumably have a nonzero value.]] contents : An opaque byte string whose contents depend on the version of the structure. -For this specification, the contents are an ESNIConfigContents structure. +For this specification, the contents are an ECHOConfigContents structure. -The ESNIConfigContents structure contains the following fields: +The ECHOConfigContents structure contains the following fields: public_name : The non-empty name of the entity trusted to update these encryption keys. @@ -234,8 +237,11 @@ This is used to repair misconfigurations, as described in public_key : The HPKE {{!I-D.irtf-cfrg-hpke}} public key which can be used by the client to -encrypt the SNI. Clients MUST ignore any ESNIConfig structure with a key using a -KEM they do not support (as identified by the kem_id field). +encrypt the SNI. + +kem_id +: The HPKE {{!I-D.irtf-cfrg-hpke}} KEM identifier corresponding to public_key. +Clients MUST ignore any ECHOConfig structure with a key using a KEM they do not support. cipher_suites : The list of cipher suites which can be used by the client to encrypt the SNI. @@ -246,7 +252,7 @@ maximum_name_length : the largest name 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 -256. Clients SHOULD reject ESNIConfig as invalid if maximum_name_length is +256. Clients SHOULD reject ECHOConfig as invalid if maximum_name_length is greater than 256. extensions @@ -257,11 +263,11 @@ Section 4.2. The same interpretation rules apply: extensions MAY appear in any order, but there MUST NOT be more than one extension of the same type in the extensions block. An extension may be tagged as mandatory by using an extension type codepoint with the high order bit set to 1. A client which receives a -mandatory extension they do not understand must reject the ESNIConfig content. +mandatory extension they do not understand must reject the ECHOConfig content. 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. +present, clients MUST reject the ECHOConfig value. # The "encrypted_client_hello" extension {#encrypted-client-hello} @@ -281,6 +287,7 @@ ClientEncryptedCH structure: struct { CipherSuite suite; opaque record_digest<0..2^16-1>; + opaque enc<0..2^16-1>; opaque encrypted_ch<0..2^16-1>; } ClientEncryptedCH; ~~~~ @@ -288,68 +295,64 @@ ClientEncryptedCH structure: suite : The cipher suite used to encrypt ClientHelloInner. -key_share -: The KeyShareEntry carrying the client's public ephemeral key share -used to derive the ESNI key. - record_digest -: A cryptographic hash of the ESNIConfig structure from which the ESNI +: A cryptographic hash of the ECHOConfig structure from which the ECHO key was obtained, i.e., from the first byte of "version" to the end of the structure. This hash is computed using the hash function associated with `suite`. -encrypted_sni -: The serialized ClientHelloInner structure, AEAD-encrypted using +enc +: The HPKE encapsulated key, used by servers to decrypt the corresponding +encrypted_ch field. + +encrypted_ch +: The serialized and encrypted ClientHelloInner structure, AEAD-encrypted using cipher suite "suite" and the key generated as described below. {:br} -If the server accepts ESNI, it does not send this extension. -If it rejects ESNI, then it sends the following structure in +If the server accepts ECHO, it does not send this extension. +If it rejects ECHO, then it sends the following structure in EncryptedExtensions: ~~~ struct { - ESNIConfig retry_keys<1..2^16-1>; + ECHOConfig retry_keys<1..2^16-1>; } ServerEncryptedCH; ~~~ retry_keys -: One or more ESNIConfig structures containing the keys that the client should use on -subsequent connections to encrypt the ClientESNIInner structure. +: One or more ECHOConfig structures containing the keys that the client should use on +subsequent connections to encrypt the ClientECHOInner structure. -This protocol also defines the "esni_required" alert, which is sent by the +This protocol also defines the "echo_required" alert, which is sent by the client when it offered an "encrypted_server_name" extension which was not accepted by the server. +# The "echo_nonce" extension {#echo-nonce} -# The "esni_nonce" extension {#esni-nonce} - -When using ESNI, the client MUST also add an extension of type -"esni_nonce" to the ClientHelloInner (but not to the outer -ClientHello). - -The encrypted SNI is carried in an "encrypted_server_name" -extension, defined as follows: +When using ECHO, the client MUST also add an extension of type +"echo_nonce" to the ClientHelloInner (but not to the outer +ClientHello). This extension is defined as follows: ~~~ enum { - esni_nonce(0xffce), (65535) + echo_nonce(0xffce), (65535) } ExtensionType; struct { uint8 nonce[16]; - } ESNINonce; + } ECHONonce; ~~~~ nonce -: A 16-byte nonce exported from the HPKE encryption context. See {{send-esni}} +: A 16-byte nonce exported from the HPKE encryption context. See {{send-echo}} for details about its computation. Finally, requirements in {{client-behavior}} and {{server-behavior}} require implementations to track, alongside each PSK established by a previous connection, whether the connection negotiated this extension with the -"esni_accept" response type. If so, this is referred to as an "ESNI PSK". -Otherwise, it is a "non-ESNI PSK". This may be implemented by adding a new field +"echo_accept" response type. If so, this is referred to as an "ECHO PSK". +Otherwise, it is a "non-ECHO PSK". This may be implemented by adding a new field to client and server session states. ## Incorporating Outer Extensions {#outer-extensions} @@ -394,40 +397,40 @@ multiple "outer_extension" extensions with the same extension code point. # Client Behavior {#client-behavior} -## Sending an encrypted ClientHello {#send-esni} +## Sending an encrypted ClientHello {#send-echo} -In order to send an encrypted SNI, the client MUST first generate its +In order to send an encrypted ClientHello, the client MUST first generate its ClientHelloInner value. In addition to the normal values, ClientHelloInner MUST also contain: - - an "esni_nonce" extension - - a TLS padding {{!RFC7685}}. This SHOULD contain X bytes of padding - where X + the actual server name is equal to ESNIConfig.maximum_name_length - -Then, the client MUST 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_client_hello" -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. - -When offering an encrypted SNI, the client MUST NOT offer to resume any non-ESNI + - an "echo_nonce" extension + - TLS padding {{!RFC7685}}. This SHOULD contain X bytes of padding + where X + the actual server name is equal to ECHOConfig.maximum_name_length + +[[OPEN ISSUE: should we adjust padding based on the complete CH, rather +than just the SNI?]] + +Then, the client determines if it supports the server's chosen KEM, as +identified by ECHOConfig.kem_id. If one is supported, the client MUST +select an appropriate cipher suite from the list of +suites offered by the server. If the client does not support the corresponding +KEM or is unable to select an appropriate group or suite, it SHOULD ignore +that ECHOConfig value and MAY attempt to use another value provided by +the server. The client MUST NOT send ECHO using HPKE algorithms not advertised +by the server. + +When offering an ECHO, the client MUST NOT offer to resume any non-ECHO PSKs. It additionally MUST NOT offer to resume any sessions for TLS 1.2 or below. -Given an ESNIConfig with fields public_key and kem_id, carrying the the HPKEPublicKey and +Given an ECHOConfig with fields public_key and kem_id, carrying the the HPKEPublicKey and KEM identifier corresponding to the server, clients compute an HPKE encryption context as follows: ~~~ -pkR = HPKE.KEM.Unmarshal(InitKey.init_key) +pkR = HPKE.KEM.Unmarshal(ECHOConfig.public_key) enc, context = SetupBaseI(pkR, "tls13-echo") -echo_nonce = context.Export("tls13-echo-nonce", 32) +echo_nonce = context.Export("tls13-echo-nonce", 16) ~~~ Note that the HPKE algorithm identifiers are those which match the client's @@ -438,8 +441,7 @@ extension, as described in {{outer-extensions}}. The encrypted ClientHello value is then computed as: ~~~~ - encrypted_ch_inner = context.Seal("", ClientHelloInner) - encrypted_ch = enc, encrypted_ch_inner + encrypted_ch = context.Seal("", ClientHelloInner) ~~~~ Finally, the client MUST generate a ClientHelloOuter message @@ -447,10 +449,11 @@ containing the "encrypted_client_hello" extension with the values as indicated above. In particular, - suite contains the client's chosen ciphersuite; -- record_digest contains the digest of the corresponding ESNIConfig structure; and +- record_digest contains the digest of the corresponding ECHOConfig structure; +- enc contains the encapsulated key as output by SetupBaseI; and - encrypted_ch contains the HPKE encapsulated key (enc) and the ClientHelloInner ciphertext (encrypted_ch_inner). -The client MUST place the value of ESNIConfig.public_name in the +The client MUST place the value of ECHOConfig.public_name in the ClientHelloOuter "server_name" extension. The remaining contents of the ClientHelloOuter MAY be identical to those in ClientHelloInner but MAY also differ. The ClientHelloOuter MUST NOT @@ -460,43 +463,43 @@ would divulge the true server name. ## Handling the server response {#handle-server-response} -As described in {{server-behavior}}, the server MAY either accept ESNI +As described in {{server-behavior}}, the server MAY either accept ECHO and use ClientHelloInner or reject it and use ClientHelloOuter. However, there is no indication in ServerHello of which one the server has done and the client must therefore use trial decryption in order to determine this. -### Accepted ESNI +### Accepted ECHO If the server used ClientHelloInner, the client proceeds with the connection as usual, authenticating the connection for the origin server. -### Rejected ESNI +### Rejected ECHO If the server used ClientHelloOuter, the client proceeds with the handshake, -authenticating for ESNIConfig.public_name as described in +authenticating for ECHOConfig.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. Otherwise, when the handshake completes successfully with the public name -authenticated, the client MUST abort the connection with an "esni_required" +authenticated, the client MUST abort the connection with an "echo_required" alert. It then processes the "retry_keys" field from the server's "encrypted_server_name" extension. If one of the values contains a version supported by the client, it can regard -the ESNI keys as securely replaced by the server. It SHOULD retry the +the ECHO keys as securely replaced by the server. It SHOULD retry the handshake with a new transport connection, using that value to encrypt the -SNI. The value may only be applied to the retry connection. The client +ClientHello. The value may only be applied to the retry connection. The client MUST continue to use the previously-advertised keys for subsequent connections. This avoids introducing pinning concerns or a tracking vector, should a malicious server present client-specific retry keys to identify clients. If none of the values provided in "retry_keys" contains a supported version, -the client can regard ESNI as securely disabled by the server. As below, it -SHOULD then retry the handshake with a new transport connection and ESNI +the client can regard ECHO as securely disabled by the server. As below, it +SHOULD then retry the handshake with a new transport connection and ECHO disabled. If the field contains any other value, the client MUST abort the connection @@ -505,22 +508,22 @@ with an "illegal_parameter" alert. 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 +ECHOConfigContents.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. +as a secure signal to disable ECHO. Otherwise, when the handshake completes successfully with the public name -authenticated, the client MUST abort the connection with an "esni_required" -alert. The client can then regard ESNI as securely disabled by the server. It -SHOULD retry the handshake with a new transport connection and ESNI disabled. +authenticated, the client MUST abort the connection with an "echo_required" +alert. The client can then regard ECHO as securely disabled by the server. It +SHOULD retry the handshake with a new transport connection and ECHO disabled. -[[TODO: Key replacement is significantly less scary than saying that ESNI-naive - servers bounce ESNI off. Is it worth defining a strict mode toggle in the ESNI +[[TODO: Key replacement is significantly less scary than saying that ECHO-naive + servers bounce ECHO off. Is it worth defining a strict mode toggle in the ECHO keys, for a deployment to indicate it is ready for that? ]] -Clients SHOULD implement a limit on retries caused by "esni_retry_request" or +Clients SHOULD implement a limit on retries caused by "echo_retry_request" or servers which do not acknowledge the "encrypted_server_name" extension. If the client does not retry in either scenario, it MUST report an error to the calling application. @@ -529,17 +532,17 @@ calling application. When the server cannot decrypt or does not process the "encrypted_server_name" extension, it continues with the handshake using the cleartext "server_name" -extension instead (see {{server-behavior}}). Clients that offer ESNI then +extension instead (see {{server-behavior}}). Clients that offer ECHO then authenticate the connection with the public name, as follows: - If the server resumed a session or negotiated a session that did not use a certificate for authentication, the client MUST abort the connection with an - "illegal_parameter" alert. This case is invalid because {{send-esni}} requires - the client to only offer ESNI-established sessions, and {{server-behavior}} - requires the server to decline ESNI-established sessions if it did not accept - ESNI. + "illegal_parameter" alert. This case is invalid because {{send-echo}} requires + the client to only offer ECHO-established sessions, and {{server-behavior}} + requires the server to decline ECHO-established sessions if it did not accept + ECHO. -- The client MUST verify that the certificate is valid for ESNIConfigContents.public_name. +- The client MUST verify that the certificate is valid for ECHOConfigContents.public_name. If invalid, it MUST abort the connection with the appropriate alert. - If the server requests a client certificate, the client MUST respond with an @@ -568,15 +571,15 @@ rules for HRR processing apply. [[OPEN ISSUE: This, along with trial decryption is pretty gross. It would just be a lot easier if we were willing to -have the server indicate whether ESNI had been accepted or not. -Given that the server is supposed to only reject ESNI when it doesn't +have the server indicate whether ECHO had been accepted or not. +Given that the server is supposed to only reject ECHO when it doesn't know the key, and this is easy to probe for, can we just instead have an extension to indicate what has happened.]] ## GREASE extensions {#grease-extensions} -If the client attempts to connect to a server and does not have an ESNIConfig +If the client attempts to connect to a server and does not have an ECHOConfig structure available for the server, it SHOULD send a GREASE {{I-D.ietf-tls-grease}} "encrypted_client_hello" extension as follows: @@ -601,9 +604,9 @@ If the server sends an "encrypted_client_hello" extension, the client MUST check the extension syntactically and abort the connection with a "decode_error" alert if it is invalid. -Offering a GREASE extension is not considered offering an encrypted SNI for +Offering a GREASE extension is not considered offering an encrypted ClientHello for purposes of requirements in {{client-behavior}}. In particular, the client MAY -offer to resume sessions established without ESNI. +offer to resume sessions established without ECHO. # Client-Facing Server Behavior {#server-behavior} @@ -611,48 +614,48 @@ Upon receiving an "encrypted_client_hello" extension, the client-facing server MUST check that it is able to negotiate TLS 1.3 or greater. If not, it MUST abort the connection with a "handshake_failure" alert. -The ClientEncryptedSNI value is said to match a known ESNIConfig if there exists -an ESNIConfig that can be used to successfully decrypt ClientEncryptedSNI.encrypted_sni. +The ClientEncryptedCH value is said to match a known ECHOConfig if there exists +an ECHOConfig that can be used to successfully decrypt ClientEncryptedCH.encrypted_sni. This matching procedure should be done using one of the following two checks: -1. Compare ClientEncryptedSNI.record_digest against cryptographic hashes of known ESNIConfig +1. Compare ClientEncryptedCH.record_digest against cryptographic hashes of known ECHOConfig and choose the one that matches. -2. Use trial decryption of ClientEncryptedSNI.encrypted_sni with known ESNIConfig and choose +2. Use trial decryption of ClientEncryptedCH.encrypted_sni with known ECHOConfig and choose the one that succeeds. -Some uses of ESNI, such as local discovery mode, may omit the ClientEncryptedSNI.record_digest +Some uses of ECHO, such as local discovery mode, may omit the ClientEncryptedCH.record_digest since it can be used as a tracking vector. In such cases, trial decryption should be -used for matching ClientEncryptedSNI to known ESNIConfig. Unless specified by the application +used for matching ClientEncryptedCH to known ECHOConfig. Unless specified by the application using (D)TLS or externally configured on both sides, implementations MUST use the first method. -If the ClientEncryptedSNI value does not match any known ESNIConfig +If the ClientEncryptedCH value does not match any known ECHOConfig structure, it MUST ignore the extension and proceed with the connection, with the following added behavior: - It MUST include the "encrypted_client_hello" extension with the - "retry_keys" field set to one or more ESNIConfig structures with - up-to-date keys. Servers MAY supply multiple ESNIConfig values of + "retry_keys" field set to one or more ECHOConfig structures with + up-to-date keys. Servers MAY supply multiple ECHOConfig values of different versions. This allows a server to support multiple versions at once. - The server MUST ignore all PSK identities in the ClientHello which correspond - to ESNI PSKs. ESNI PSKs offered by the client are associated with the ESNI - name. The server was unable to decrypt then ESNI name, so it should not resume + to ECHO PSKs. ECHO PSKs offered by the client are associated with the ECHO + name. The server was unable to decrypt then ECHO name, so it should not resume them when using the cleartext SNI name. This restriction allows a client to reject resumptions in {{auth-public-name}}. -Note that an unrecognized ClientEncryptedSNI.record_digest value may be -a GREASE ESNI extension (see {{grease-extensions}}), so it is necessary +Note that an unrecognized ClientEncryptedCH.record_digest value may be +a GREASE ECHO extension (see {{grease-extensions}}), so it is necessary for servers to proceed with the connection and rely on the client to abort if -ESNI was required. In particular, the unrecognized value alone does not -indicate a misconfigured ESNI advertisement ({{misconfiguration}}). Instead, -servers can measure occurrences of the "esni_required" alert to detect this +ECHO was required. In particular, the unrecognized value alone does not +indicate a misconfigured ECHO advertisement ({{misconfiguration}}). Instead, +servers can measure occurrences of the "echo_required" alert to detect this case. -If the ClientEncryptedSNI value does match a known ESNIConfig, the server +If the ClientEncryptedCH value does match a known ECHOConfig, the server performs the following checks: -- If the ClientEncryptedSNI.key_share group does not match ESNIConfigContents.key, +- If the ClientEncryptedCH.key_share group does not match ECHOConfigContents.key, it MUST abort the connection with an "illegal_parameter" alert. Assuming these checks succeed, the server then computes K_sni @@ -673,49 +676,49 @@ the TLS connection to the backend server (if in Split Mode). In the latter case, it does not make any changes to the TLS messages, but just blindly forwards them. -If the server sends a NewSessionTicket message, the corresponding ESNI PSK MUST -be ignored by all other servers in the deployment when not negotiating ESNI, +If the server sends a NewSessionTicket message, the corresponding ECHO PSK MUST +be ignored by all other servers in the deployment when not negotiating ECHO, including servers which do not implement this specification (in Split mode, -the server can detect this case by the presence of the "esni_info" extension). +the server can detect this case by the presence of the "echo_info" extension). # Compatibility Issues -Unlike most TLS extensions, placing the SNI value in an ESNI extension +Unlike most TLS extensions, placing the SNI value in an ECHO extension is not interoperable with existing servers, which expect the value in the existing cleartext extension. Thus server operators SHOULD ensure -servers understand a given set of ESNI keys before advertising them. +servers understand a given set of ECHO keys before advertising them. Additionally, servers SHOULD retain support for any previously-advertised keys for the duration of their validity However, in more complex deployment scenarios, this may be difficult to fully guarantee. Thus this protocol was designed to be robust in case -of inconsistencies between systems that advertise ESNI keys and servers, at the +of inconsistencies between systems that advertise ECHO keys and servers, at the cost of extra round-trips due to a retry. Two specific scenarios are detailed below. ## Misconfiguration and Deployment Concerns {#misconfiguration} -It is possible for ESNI advertisements and servers to become inconsistent. This +It is possible for ECHO advertisements and servers to become inconsistent. This may occur, for instance, from DNS misconfiguration, caching issues, or an incomplete rollout in a multi-server deployment. This may also occur if a server -loses its ESNI keys, or if a deployment of ESNI must be rolled back on the +loses its ECHO keys, or if a deployment of ECHO must be rolled back on the server. The retry mechanism repairs inconsistencies, provided the server is authoritative for the public name. If server and advertised keys mismatch, -the server will respond with esni_retry_requested. If the server does not understand the +the server will respond with echo_retry_requested. If the server does not understand the "encrypted_server_name" extension at all, it will ignore it as required by {{RFC8446}}; Section 4.1.2. Provided the server can present a certificate valid for the public name, the client can safely retry with updated settings, as described in {{handle-server-response}}. -Unless ESNI is disabled as a result of successfully establishing a connection to -the public name, the client MUST NOT fall back to cleartext SNI, as this allows -a network attacker to disclose the SNI. It MAY attempt to use another server -from the DNS results, if one is provided. +Unless ECHO is disabled as a result of successfully establishing a connection to +the public name, the client MUST NOT fall back to using unencrypted ClientHellos, as this allows +a network attacker to disclose the contents of this ClientHello, including the SNI. +It MAY attempt to use another server from the DNS results, if one is provided. Client-facing servers with non-uniform cryptographic configurations across backend -origin servers segment the ESNI anonymity set based on these configurations. For example, +origin servers segment the ECHO anonymity set based on these configurations. For example, if a client-facing server hosts k backend origin servers, and exactly one of those backend origin servers supports a different set of cryptographic algorithms than the other (k - 1) servers, it may be possible to identify this single server based on @@ -732,9 +735,9 @@ then present a certificate based on the public name, without echoing the Depending on whether the client is configured to accept the proxy's certificate as authoritative for the public name, this may trigger the retry logic described in {{handle-server-response}} or result in a connection failure. A proxy which -is not authoritative for the public name cannot forge a signal to disable ESNI. +is not authoritative for the public name cannot forge a signal to disable ECHO. -A non-conformant MITM proxy which instead forwards the ESNI extension, +A non-conformant MITM proxy which instead forwards the ECHO extension, substituting its own KeyShare value, will result in the client-facing server recognizing the key, but failing to decrypt the SNI. This causes a hard failure. Clients SHOULD NOT attempt to repair the @@ -759,48 +762,35 @@ the mapping between ciphersuites and HPKE identifiers. ## Why is cleartext DNS OK? {#cleartext-dns} In comparison to {{?I-D.kazuho-protected-sni}}, wherein DNS Resource -Records are signed via a server private key, ESNI records have no +Records are signed via a server private key, ECHO records have no authenticity or provenance information. This means that any attacker which can inject DNS responses or poison DNS caches, which is a common scenario in client access networks, can supply clients with fake -ESNI records (so that the client encrypts SNI to them) or strip the -ESNI record from the response. However, in the face of an attacker that -controls DNS, no SNI encryption scheme can work because the attacker +ECHO records (so that the client encrypts data to them) or strip the +ECHO record from the response. However, in the face of an attacker that +controls DNS, no encryption scheme can work because the attacker can replace the IP address, thus blocking client connections, or substituting a unique IP address which is 1:1 with the DNS name that -was looked up (modulo DNS wildcards). Thus, allowing the ESNI records in +was looked up (modulo DNS wildcards). Thus, allowing the ECHO records in the clear does not make the situation significantly worse. Clearly, DNSSEC (if the client validates and hard fails) is a defense against this form of attack, but DoH/DPRIVE are also defenses against DNS attacks -by attackers on the local network, which is a common case where SNI is -desired. -Moreover, as noted in the introduction, SNI encryption is less useful -without encryption of DNS queries in transit via DoH or DPRIVE mechanisms. +by attackers on the local network, which is a common case where ClientHello and SNI +encryption are desired. Moreover, as noted in the introduction, SNI encryption is +less useful without encryption of DNS queries in transit via DoH or DPRIVE mechanisms. ## Optional Record Digests and Trial Decryption Supporting optional record digests and trial decryption opens oneself up to DoS attacks. Specifically, an adversary may send malicious ClientHello messages, i.e., -those which will not decrypt with any known ESNI key, in order to force +those which will not decrypt with any known ECHO key, in order to force decryption. Servers that support this feature should, for example, implement some form of rate limiting mechanism to limit the damage caused by such attacks. -## Encrypting other Extensions - -ESNI protects only the SNI in transit. Other ClientHello extensions, -such as ALPN, might also reveal privacy-sensitive information to the -network. As such, it might be desirable to encrypt other extensions -alongside the SNI. However, the SNI extension is unique in that -non-TLS-terminating servers or load balancers may act on its contents. -Thus, using keys specifically for SNI encryption promotes key separation -between client-facing servers and endpoints party to TLS connections. -Moreover, the ESNI design described herein does not preclude a mechanism -for generic ClientHello extension encryption. - ## Related Privacy Leaks -ESNI requires encrypted DNS to be an effective privacy protection mechanism. +ECHO requires encrypted DNS to be an effective privacy protection mechanism. However, verifying the server's identity from the Certificate message, particularly when using the X509 CertificateType, may result in additional network traffic that may reveal the server identity. Examples of this traffic may include requests @@ -821,50 +811,49 @@ certificate validation. {{?I-D.ietf-tls-sni-encryption}} lists several requirements for SNI encryption. In this section, we re-iterate these requirements and assess -the ESNI design against them. +the ECHO design against them. ### Mitigate against replay attacks -Since the SNI encryption key is derived from a (EC)DH operation -between the client's ephemeral and server's semi-static ESNI key, the ESNI -encryption is bound to the Client Hello. It is not possible for -an attacker to "cut and paste" the ESNI value in a different Client -Hello, with a different ephemeral key share, as the terminating server -will fail to decrypt and verify the ESNI value. +Since servers process either ClientHelloInner or ClientHelloOuter, and ClientHelloInner +contains an HPKE-derived nonce, it is not possible for an attacker to "cut and paste" +the ECHO value in a different Client Hello and learn information from ClientHelloInner. +This is because attacker lacks access to the HPKE-derived nonce used to derive the +handshake secrets. ### Avoid widely-deployed shared secrets This design depends upon DNS as a vehicle for semi-static public key distribution. Server operators may partition their private keys however they see fit provided each server behind an IP address has the corresponding private key to decrypt -a key. Thus, when one ESNI key is provided, sharing is optimally bound by the number +a key. Thus, when one ECHO key is provided, sharing is optimally bound by the number of hosts that share an IP address. Server operators may further limit sharing -by publishing different DNS records containing ESNIConfig values with different +by publishing different DNS records containing ECHOConfig values with different keys using a short TTL. ### Prevent SNI-based DoS attacks -This design requires servers to decrypt ClientHello messages with ClientEncryptedSNI +This design requires servers to decrypt ClientHello messages with ClientEncryptedCH extensions carrying valid digests. Thus, it is possible for an attacker to force decryption operations on the server. This attack is bound by the number of valid TCP connections an attacker can open. ### Do not stick out -As more clients enable ESNI support, e.g., as normal part of Web browser +As more clients enable ECHO support, e.g., as normal part of Web browser functionality, with keys supplied by shared hosting providers, the presence -of ESNI extensions becomes less suspicious and part of common or predictable -client behavior. In other words, if all Web browsers start using ESNI, +of ECHO extensions becomes less suspicious and part of common or predictable +client behavior. In other words, if all Web browsers start using ECHO, the presence of this value does not signal suspicious behavior to passive eavesdroppers. -Additionally, this specification allows for clients to send GREASE ESNI +Additionally, this specification allows for clients to send GREASE ECHO extensions (see {{grease-extensions}}), which helps ensure the ecosystem handles the values correctly. ### Forward secrecy -This design is not forward secret because the server's ESNI key is static. +This design is not forward secret because the server's ECHO key is static. However, the window of exposure is bound by the key lifetime. It is RECOMMENDED that servers rotate keys frequently. @@ -875,12 +864,12 @@ directly to backend origin servers, thereby avoiding unnecessary MiTM attacks. ### Split server spoofing -Assuming ESNI records retrieved from DNS are authenticated, e.g., via DNSSEC or fetched +Assuming ECHO records retrieved from DNS are authenticated, e.g., via DNSSEC or fetched from a trusted Recursive Resolver, spoofing a server operating in Split Mode is not possible. See {{cleartext-dns}} for more details regarding cleartext DNS. -Authenticating the ESNIConfigs structure naturally authenticates the +Authenticating the ECHOConfigs 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. @@ -903,7 +892,7 @@ still complete). However, the client is still responsible for verifying the server's identity in its certificate. [[TODO: Some more analysis needed in this case, as it is a little -odd, and probably some precise rules about handling ESNI and no +odd, and probably some precise rules about handling ECHO and no SNI uniformly?]] # IANA Considerations @@ -917,7 +906,7 @@ in the existing registry for ExtensionType (defined in ## Update of the TLS Alert Registry -IANA is requested to create an entry, esni_required(121) in the +IANA is requested to create an entry, echo_required(121) in the existing registry for Alerts (defined in {{!RFC8446}}), with the "DTLS-OK" column being set to "Y". @@ -979,7 +968,7 @@ SNI induces no additional round trip and operates below the application layer. The design described here only provides encryption for the SNI, but not for other extensions, such as ALPN. Another potential design would be to encrypt all of the extensions using the same basic -structure as we use here for ESNI. That design has the following +structure as we use here for ECHO. That design has the following advantages: - It protects all the extensions from ordinary eavesdroppers @@ -1005,6 +994,6 @@ It also has the following disadvantages: This document draws extensively from ideas in {{?I-D.kazuho-protected-sni}}, but is a much more limited mechanism because it depends on the DNS for the -protection of the ESNI key. Richard Barnes, Christian Huitema, Patrick McManus, +protection of the ECHO key. Richard Barnes, Christian Huitema, Patrick McManus, Matthew Prince, Nick Sullivan, Martin Thomson, and David Benjamin also provided important ideas and contributions. From 6d76e9f9f4b9a5b3a02bd8f774e53744e0bd1a3b Mon Sep 17 00:00:00 2001 From: Christopher Wood Date: Tue, 25 Feb 2020 20:08:52 -0800 Subject: [PATCH 11/26] Updates based on review. --- draft-ietf-tls-esni.md | 79 +++++++++++++++++++++--------------------- 1 file changed, 40 insertions(+), 39 deletions(-) diff --git a/draft-ietf-tls-esni.md b/draft-ietf-tls-esni.md index 62fd4e0b..2c67cec9 100644 --- a/draft-ietf-tls-esni.md +++ b/draft-ietf-tls-esni.md @@ -166,7 +166,7 @@ defines the format of the SNI encryption public key and metadata, referred to as an ECHO configuration, and delegates DNS publication details to {{!HTTPSSVC=I-D.nygren-dnsop-svcb-httpssvc}}, though other delivery mechanisms are possible. In particular, if some of the -clients of a private server are applications rather than web browsers, +clients of a private server are applications rather than Web browsers, those applications might have the public key and metadata preconfigured. @@ -193,11 +193,6 @@ ECHOConfigs structure. opaque HPKEPublicKey<1..2^16-1>; uint16 HPKEKEMID; // Defined in I-D.irtf-cfrg-hpke - struct { - uint16 version; - opaque contents<1..2^16-1>; - } ECHOConfig; - struct { opaque public_name<1..2^16-1>; @@ -205,10 +200,18 @@ ECHOConfigs structure. HPKEKEMID kem_id; Ciphersuite cipher_suites<2..2^16-2>; - uint16 maximum_name_length; + uint16 maximum_message_length; Extension extensions<0..2^16-1>; } ECHOConfigContents; + struct { + uint16 version; + uint16 length; + select (ECHOConfig. version) { + case 0xff03: ECHOConfigContents; + } + } ECHOConfig; + ECHOConfig ECHOConfigs<1..2^16-1>; ~~~~ @@ -248,12 +251,9 @@ cipher_suites See {{hpke-map}} for information on how a Ciphersuite maps to corresponding HPKE algorithm identifiers. -maximum_name_length -: the largest name 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 -256. Clients SHOULD reject ECHOConfig as invalid if maximum_name_length is -greater than 256. +maximum_message_length +: the largest ClientHello the server expects to support rounded up the nearest +multiple of 16. extensions : A list of extensions that the client can take into consideration when @@ -287,8 +287,8 @@ ClientEncryptedCH structure: struct { CipherSuite suite; opaque record_digest<0..2^16-1>; - opaque enc<0..2^16-1>; - opaque encrypted_ch<0..2^16-1>; + opaque enc<1..2^16-1>; + opaque encrypted_ch<1..2^16-1>; } ClientEncryptedCH; ~~~~ @@ -405,10 +405,8 @@ MUST also contain: - an "echo_nonce" extension - TLS padding {{!RFC7685}}. This SHOULD contain X bytes of padding - where X + the actual server name is equal to ECHOConfig.maximum_name_length - -[[OPEN ISSUE: should we adjust padding based on the complete CH, rather -than just the SNI?]] + where X + the length of the non-padding bytes in ClientHelloInner + is equal to ECHOConfig.maximum_message_length. Then, the client determines if it supports the server's chosen KEM, as identified by ECHOConfig.kem_id. If one is supported, the client MUST @@ -590,8 +588,8 @@ structure available for the server, it SHOULD send a GREASE SHOULD vary to exercise all supported configurations, but MAY be held constant for successive connections to the same server in the same session. -- Set the "key_share" field to a randomly-generated valid public key - for the named group. +- Set the "enc" field to a randomly-generated valid encapsulated public key + corresponding to the HPKE KEM. - Set the "record_digest" field to a randomly-generated string of hash_length bytes, where hash_length is the length of the hash function associated with @@ -615,12 +613,12 @@ server MUST check that it is able to negotiate TLS 1.3 or greater. If not, it MUST abort the connection with a "handshake_failure" alert. The ClientEncryptedCH value is said to match a known ECHOConfig if there exists -an ECHOConfig that can be used to successfully decrypt ClientEncryptedCH.encrypted_sni. +an ECHOConfig that can be used to successfully decrypt ClientEncryptedCH.encrypted_ch. This matching procedure should be done using one of the following two checks: 1. Compare ClientEncryptedCH.record_digest against cryptographic hashes of known ECHOConfig and choose the one that matches. -2. Use trial decryption of ClientEncryptedCH.encrypted_sni with known ECHOConfig and choose +2. Use trial decryption of ClientEncryptedCH.encrypted_ch with known ECHOConfig and choose the one that succeeds. Some uses of ECHO, such as local discovery mode, may omit the ClientEncryptedCH.record_digest @@ -653,20 +651,23 @@ servers can measure occurrences of the "echo_required" alert to detect this case. If the ClientEncryptedCH value does match a known ECHOConfig, the server -performs the following checks: - -- If the ClientEncryptedCH.key_share group does not match ECHOConfigContents.key, - it MUST abort the connection with an "illegal_parameter" alert. +then decrypts ClientEncryptedCH.encrypted_ch, using the private key skR +corresponding to ESNIConfig, as follows: -Assuming these checks succeed, the server then computes K_sni -and decrypts the ClientHelloInner value. If decryption fails, the server -MUST abort the connection with a "decrypt_error" alert. +~~~ +context = SetupBaseR(ClientEncryptedCH.enc, skR, "tls13-echo") +ClientHelloInner = context.Open("", ClientEncryptedCH.encrypted_ch) +echo_nonce = context.Export("tls13-echo-nonce", 16) +~~~ -Once the ClientHelloInner has been decrypted, the server MUST -scan it for any "outer_extension" extensions and substitute their -values with the values in ClientHelloOuter. It MUST first verify that -the hash found in the extension matches the hash of the extension -to be interpolated in and if it does not, abort the connection +If decryption fails, the server MUST abort the connection with +a "decrypt_error" alert. Moreover, if there is no "echo_nonce" +extension, and if its value does not match the derived echo_nonce, +the server MUST abort the connection with a "decrypt_error" alert. +Next, the server MUST scan ClientHelloInner for any "outer_extension" +extensions and substitute their values with the values in ClientHelloOuter. +It MUST first verify that the hash found in the extension matches the hash +of the extension to be interpolated in and if it does not, abort the connections with a "decrypt_error" alert. Upon determining the true SNI, the client-facing server then either @@ -747,15 +748,15 @@ connection in this case. Per {{RFC8446}, TLS ciphersuites define an AEAD and hash algorithm. In contrast, HPKE composes AEAD algorithms and key derivation functions. The table below lists -the mapping between ciphersuites and HPKE identifiers. +the mapping between ciphersuites and HPKE identifiers. TLS_AES_128_CCM_SHA256 and +TLS_AES_128_CCM_8_SHA256 are not supported ECHO ciphersuites as they have no HPKE +equivalent. | TLS Ciphersuite | HPKE AEAD | HPKE KDF | |:-------|:------------------------|:----------| | TLS_AES_128_GCM_SHA256 | AES-GCM-128 | HKDF-SHA256 | | TLS_AES_256_GCM_SHA384 | AES-GCM-256 | HKDF-SHA256 | | TLS_CHACHA20_POLY1305_SHA256 | ChaCha20Poly1305 | HKDF-SHA256 | -| TLS_AES_128_CCM_SHA256 | AES-CCM-128 | HKDF-SHA256 | -| TLS_AES_128_CCM_8_SHA256 | AES-CCM-128-8 | HKDF-SHA256 | # Security Considerations @@ -818,7 +819,7 @@ the ECHO design against them. Since servers process either ClientHelloInner or ClientHelloOuter, and ClientHelloInner contains an HPKE-derived nonce, it is not possible for an attacker to "cut and paste" the ECHO value in a different Client Hello and learn information from ClientHelloInner. -This is because attacker lacks access to the HPKE-derived nonce used to derive the +This is because the attacker lacks access to the HPKE-derived nonce used to derive the handshake secrets. ### Avoid widely-deployed shared secrets From 68ca0777322ac15548b512f50c3fdfe3a2021306 Mon Sep 17 00:00:00 2001 From: Christopher Wood Date: Tue, 25 Feb 2020 20:10:11 -0800 Subject: [PATCH 12/26] create->form. --- draft-ietf-tls-esni.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/draft-ietf-tls-esni.md b/draft-ietf-tls-esni.md index 2c67cec9..364529ae 100644 --- a/draft-ietf-tls-esni.md +++ b/draft-ietf-tls-esni.md @@ -170,7 +170,7 @@ clients of a private server are applications rather than Web browsers, those applications might have the public key and metadata preconfigured. -When a client wants to create a TLS connection to any of the domains +When a client wants to form a TLS connection to any of the domains served by an ECHO-supporting provider, it constructs a ClientHello in the regular fashion containing the true SNI value (ClientHelloInner) and then encrypts it using the public key for the provider. It then From 844dee32924660f482defbc4033e4ea0f73915b7 Mon Sep 17 00:00:00 2001 From: Christopher Wood Date: Sun, 1 Mar 2020 16:55:45 -0800 Subject: [PATCH 13/26] More updates based on review. --- draft-ietf-tls-esni.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/draft-ietf-tls-esni.md b/draft-ietf-tls-esni.md index 364529ae..62c1442c 100644 --- a/draft-ietf-tls-esni.md +++ b/draft-ietf-tls-esni.md @@ -1,7 +1,7 @@ --- title: Encrypted Server Name Indication for TLS 1.3 abbrev: TLS 1.3 SNI Encryption -docname: draft-ietf-tls-echo-latest +docname: draft-ietf-tls-esni-latest category: exp ipr: trust200902 @@ -207,7 +207,7 @@ ECHOConfigs structure. struct { uint16 version; uint16 length; - select (ECHOConfig. version) { + select (ECHOConfig.version) { case 0xff03: ECHOConfigContents; } } ECHOConfig; @@ -589,7 +589,7 @@ structure available for the server, it SHOULD send a GREASE for successive connections to the same server in the same session. - Set the "enc" field to a randomly-generated valid encapsulated public key - corresponding to the HPKE KEM. + output by the HPKE KEM. - Set the "record_digest" field to a randomly-generated string of hash_length bytes, where hash_length is the length of the hash function associated with @@ -662,7 +662,7 @@ echo_nonce = context.Export("tls13-echo-nonce", 16) If decryption fails, the server MUST abort the connection with a "decrypt_error" alert. Moreover, if there is no "echo_nonce" -extension, and if its value does not match the derived echo_nonce, +extension, or if its value does not match the derived echo_nonce, the server MUST abort the connection with a "decrypt_error" alert. Next, the server MUST scan ClientHelloInner for any "outer_extension" extensions and substitute their values with the values in ClientHelloOuter. From 2fecaa793d81a18d056b674806de86d08cab9008 Mon Sep 17 00:00:00 2001 From: Christopher Wood Date: Sun, 1 Mar 2020 18:15:51 -0800 Subject: [PATCH 14/26] Fix camel casing and some other things. --- draft-ietf-tls-esni.md | 29 +++++++++++++++++------------ 1 file changed, 17 insertions(+), 12 deletions(-) diff --git a/draft-ietf-tls-esni.md b/draft-ietf-tls-esni.md index 62c1442c..0ad1995d 100644 --- a/draft-ietf-tls-esni.md +++ b/draft-ietf-tls-esni.md @@ -183,21 +183,25 @@ Upon receiving ClientHelloOuter, the server can then decrypt ClientHelloInner and either terminate the connection (in Shared Mode) or forward it to the backend server (in Split Mode). +Note that both ClientHelloInner and ClientHelloOuter are both valid +ClientHello messages. ClientHelloOuter carries an encrypted representation +of ClientHelloInner in a "encrypted_client_hello" extension, defined +in {{encrypted-client-hello}}. -# Encrypted SNI Configuration {#echo-configuration} +# Encrypted ClientHello Configuration {#echo-configuration} -SNI Encryption configuration information is conveyed with the following -ECHOConfigs structure. +ClientHello encryption configuration information is conveyed with the +following ECHOConfigs structure. ~~~~ - opaque HPKEPublicKey<1..2^16-1>; - uint16 HPKEKEMID; // Defined in I-D.irtf-cfrg-hpke + opaque HpkePublicKey<1..2^16-1>; + uint16 HkpeKemId; // Defined in I-D.irtf-cfrg-hpke struct { opaque public_name<1..2^16-1>; - HPKEPublicKey public_key; - HPKEKEMID kem_id; + HpkePublicKey public_key; + HkpeKemId kem_id; Ciphersuite cipher_suites<2..2^16-2>; uint16 maximum_message_length; @@ -316,13 +320,14 @@ EncryptedExtensions: ~~~ struct { - ECHOConfig retry_keys<1..2^16-1>; + ECHOConfigs retry_configs; } ServerEncryptedCH; ~~~ -retry_keys -: One or more ECHOConfig structures containing the keys that the client should use on -subsequent connections to encrypt the ClientECHOInner structure. +retry_configs +: An ESNIConfigs structure containing one or more ECHOConfig structures in +decreasing order of preference that the client should use on subsequent +connections to encrypt the ClientHelloInner structure. This protocol also defines the "echo_required" alert, which is sent by the client when it offered an "encrypted_server_name" extension which was not @@ -421,7 +426,7 @@ When offering an ECHO, the client MUST NOT offer to resume any non-ECHO PSKs. It additionally MUST NOT offer to resume any sessions for TLS 1.2 or below. -Given an ECHOConfig with fields public_key and kem_id, carrying the the HPKEPublicKey and +Given an ECHOConfig with fields public_key and kem_id, carrying the the HpkePublicKey and KEM identifier corresponding to the server, clients compute an HPKE encryption context as follows: From eb360a168b2b91ac3e104430464842a3ec71e8d7 Mon Sep 17 00:00:00 2001 From: Christopher Wood Date: Sun, 1 Mar 2020 18:18:36 -0800 Subject: [PATCH 15/26] Clarify that the inner and outer CHs are complete CHs. --- draft-ietf-tls-esni.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/draft-ietf-tls-esni.md b/draft-ietf-tls-esni.md index 0ad1995d..27941497 100644 --- a/draft-ietf-tls-esni.md +++ b/draft-ietf-tls-esni.md @@ -183,7 +183,7 @@ Upon receiving ClientHelloOuter, the server can then decrypt ClientHelloInner and either terminate the connection (in Shared Mode) or forward it to the backend server (in Split Mode). -Note that both ClientHelloInner and ClientHelloOuter are both valid +Note that both ClientHelloInner and ClientHelloOuter are both valid, complete ClientHello messages. ClientHelloOuter carries an encrypted representation of ClientHelloInner in a "encrypted_client_hello" extension, defined in {{encrypted-client-hello}}. From e4e577cd1a3f6b95ccd446cc4fd33d235d9456d1 Mon Sep 17 00:00:00 2001 From: Christopher Wood Date: Mon, 2 Mar 2020 09:46:23 -0800 Subject: [PATCH 16/26] Bind CH1 and CH2 across HRR, using an HPKE-derived secret. --- draft-ietf-tls-esni.md | 47 +++++++++++++++++++++++++++++++----------- 1 file changed, 35 insertions(+), 12 deletions(-) diff --git a/draft-ietf-tls-esni.md b/draft-ietf-tls-esni.md index 27941497..08269486 100644 --- a/draft-ietf-tls-esni.md +++ b/draft-ietf-tls-esni.md @@ -432,8 +432,9 @@ context as follows: ~~~ pkR = HPKE.KEM.Unmarshal(ECHOConfig.public_key) -enc, context = SetupBaseI(pkR, "tls13-echo") +enc, context = SetupBaseS(pkR, "tls13-echo") echo_nonce = context.Export("tls13-echo-nonce", 16) +echo_hrr_key = context.Export("tls13-echo-hrr-key", 16) ~~~ Note that the HPKE algorithm identifiers are those which match the client's @@ -453,7 +454,7 @@ indicated above. In particular, - suite contains the client's chosen ciphersuite; - record_digest contains the digest of the corresponding ECHOConfig structure; -- enc contains the encapsulated key as output by SetupBaseI; and +- enc contains the encapsulated key as output by SetupBaseS; and - encrypted_ch contains the HPKE encapsulated key (enc) and the ClientHelloInner ciphertext (encrypted_ch_inner). The client MUST place the value of ECHOConfig.public_name in the @@ -561,16 +562,37 @@ error code. ### HelloRetryRequest -If the server sends a HelloRetryRequest in response to the ClientHello -and the client sends a second updated ClientHello per the rules in -{{RFC8446}}. However, at this point, the client does not know whether the -server processed ClientHelloOuter or ClientHelloInner, and MUST -regenerate both values to be acceptable. Note: if the inner and outer -ClientHellos use different groups for their key shares or differ in -some other way, then the HRR may actually be invalid for one or the -other ClientHello. In that case, the Client MUST continue the -handshake without changing the unaffected CH. Otherwise, the usual -rules for HRR processing apply. +If the server sends a HelloRetryRequest in response to the ClientHello, +the client sends a second updated ClientHello per the rules in {{RFC8446}}. +However, at this point, the client does not know whether the server processed +ClientHelloOuter or ClientHelloInner, and MUST regenerate both values to +be acceptable. Note: if the inner and outer ClientHellos use different groups +for their key shares or differ in some other way, then the HRR may actually be +invalid for one or the other ClientHello. In that case, the Client MUST continue +the handshake without changing the unaffected CH. Otherwise, the usual rules for +HRR processing apply. + +Clients bind encryption of the second ClientHelloInner to encryption of the first +ClientHelloInner via the derived echo_hrr_key by modifying HPKE setup as follows: + +~~~ +pkR = HPKE.KEM.Unmarshal(ECHOConfig.public_key) +enc, context = SetupPSKS(pkR, "tls13-echo-hrr", echo_hrr_key, "") +echo_nonce = context.Export("tls13-echo-hrr-nonce", 16) +~~~ + +Clients then encrypt the second ClientHelloInner using this new HPKE context. +In doing so, the encrypted value is also authenticated by the echo_hrr_key. +Client-facing servers perform the corresponding process when decrypting second +ClientHelloInner messages. In particular, upon receipt of a second ClientHello +message with a ClientEncryptedCH value, servers setup their HPKE context and +decrypt ClientEncryptedCH as follows: + +~~~ +context = SetupPSKR(ClientEncryptedCH.enc, skR, "tls13-echo-hrr", echo_hrr_key, "") +ClientHelloInner = context.Open("", ClientEncryptedCH.encrypted_ch) +echo_nonce = context.Export("tls13-echo-hrr-nonce", 16) +~~~ [[OPEN ISSUE: This, along with trial decryption is pretty gross. It would just be a lot easier if we were willing to @@ -663,6 +685,7 @@ corresponding to ESNIConfig, as follows: context = SetupBaseR(ClientEncryptedCH.enc, skR, "tls13-echo") ClientHelloInner = context.Open("", ClientEncryptedCH.encrypted_ch) echo_nonce = context.Export("tls13-echo-nonce", 16) +echo_hrr_key = context.Export("tls13-echo-hrr-key", 16) ~~~ If decryption fails, the server MUST abort the connection with From bdde7166e423d9097c0d566ce886d510b429268b Mon Sep 17 00:00:00 2001 From: Christopher Wood Date: Thu, 5 Mar 2020 12:50:17 -0800 Subject: [PATCH 17/26] Apply updates based on PR. --- draft-ietf-tls-esni.md | 77 ++++++++++++++++++++++-------------------- 1 file changed, 41 insertions(+), 36 deletions(-) diff --git a/draft-ietf-tls-esni.md b/draft-ietf-tls-esni.md index 08269486..28ba5385 100644 --- a/draft-ietf-tls-esni.md +++ b/draft-ietf-tls-esni.md @@ -202,9 +202,9 @@ following ECHOConfigs structure. HpkePublicKey public_key; HkpeKemId kem_id; - Ciphersuite cipher_suites<2..2^16-2>; + CipherSuite cipher_suites<2..2^16-2>; - uint16 maximum_message_length; + uint16 maximum_name_length; Extension extensions<0..2^16-1>; } ECHOConfigContents; @@ -244,20 +244,21 @@ This is used to repair misconfigurations, as described in public_key : The HPKE {{!I-D.irtf-cfrg-hpke}} public key which can be used by the client to -encrypt the SNI. +encrypt the ClientHello. kem_id : The HPKE {{!I-D.irtf-cfrg-hpke}} KEM identifier corresponding to public_key. Clients MUST ignore any ECHOConfig structure with a key using a KEM they do not support. cipher_suites -: The list of cipher suites which can be used by the client to encrypt the SNI. -See {{hpke-map}} for information on how a Ciphersuite maps to corresponding +: The list of cipher suites which can be used by the client to encrypt the ClientHello. +See {{hpke-map}} for information on how a CipherSuite maps to corresponding HPKE algorithm identifiers. -maximum_message_length -: the largest ClientHello the server expects to support rounded up the nearest -multiple of 16. +maximum_name_length +: the largest name the server expects to support. If the server supports arbitrary +wildcard names, it SHOULD set this value to 256. Clients SHOULD reject ESNIConfig as +invalid if maximum_name_length is greater than 256. extensions : A list of extensions that the client can take into consideration when @@ -404,30 +405,17 @@ multiple "outer_extension" extensions with the same extension code point. ## Sending an encrypted ClientHello {#send-echo} -In order to send an encrypted ClientHello, the client MUST first generate its -ClientHelloInner value. In addition to the normal values, ClientHelloInner -MUST also contain: - - - an "echo_nonce" extension - - TLS padding {{!RFC7685}}. This SHOULD contain X bytes of padding - where X + the length of the non-padding bytes in ClientHelloInner - is equal to ECHOConfig.maximum_message_length. - -Then, the client determines if it supports the server's chosen KEM, as -identified by ECHOConfig.kem_id. If one is supported, the client MUST -select an appropriate cipher suite from the list of +In order to send an encrypted ClientHello, the client first determines if it +supports the server's chosen KEM, as identified by ECHOConfig.kem_id. If one +is supported, the client MUST select an appropriate cipher suite from the list of suites offered by the server. If the client does not support the corresponding KEM or is unable to select an appropriate group or suite, it SHOULD ignore that ECHOConfig value and MAY attempt to use another value provided by the server. The client MUST NOT send ECHO using HPKE algorithms not advertised by the server. -When offering an ECHO, the client MUST NOT offer to resume any non-ECHO -PSKs. It additionally MUST NOT offer to resume any sessions for TLS 1.2 or -below. - -Given an ECHOConfig with fields public_key and kem_id, carrying the the HpkePublicKey and -KEM identifier corresponding to the server, clients compute an HPKE encryption +Given a compatible ECHOConfig with fields public_key and kem_id, carrying the HpkePublicKey +and KEM identifier corresponding to the server, clients compute an HPKE encryption context as follows: ~~~ @@ -438,10 +426,24 @@ echo_hrr_key = context.Export("tls13-echo-hrr-key", 16) ~~~ Note that the HPKE algorithm identifiers are those which match the client's -chosen Ciphersuite, according to {{hpke-map}}. The client MAY replace any large, +chosen CipherSuite, according to {{hpke-map}}. The client MAY replace any large, duplicated extensions in ClientHelloInner with the corresponding "outer_extensions" extension, as described in {{outer-extensions}}. +The client then generates a ClientHelloInner value. In addition to the normal +values, ClientHelloInner MUST also contain: + + - an "echo_nonce" extension + - TLS padding {{!RFC7685}} + +The padding SHOULD contain X bytes of padding, where X = ECHOConfig.maximum_name_length - length(dns_name), +rounded up to the nearest multiple of 16, and dns_name is the DNS name in the +ClientHelloInner "server_name" extension. + +When offering an encrypted ClientHello, the client MUST NOT offer to resume any +non-ECHO PSKs. It additionally MUST NOT offer to resume any sessions for TLS 1.2 +or below. + The encrypted ClientHello value is then computed as: ~~~~ @@ -567,10 +569,14 @@ the client sends a second updated ClientHello per the rules in {{RFC8446}}. However, at this point, the client does not know whether the server processed ClientHelloOuter or ClientHelloInner, and MUST regenerate both values to be acceptable. Note: if the inner and outer ClientHellos use different groups -for their key shares or differ in some other way, then the HRR may actually be -invalid for one or the other ClientHello. In that case, the Client MUST continue -the handshake without changing the unaffected CH. Otherwise, the usual rules for -HRR processing apply. +for their key shares or differ in some other way, then the HelloRetryRequest may +actually be invalid for one or the other ClientHello. If the inner ClientHello +is unaffected by this retry, then the client only changes the outer ClientHello. +In contrast, if the outer ClientHello is unaffected by this retry, then the client +changes the inner ClientHello and recomputes any fields necessary in the outer +ClientHello ("encrypted_client_hello" extension contents, PSK binders, etc.) + +Otherwise, the usual rules for HelloRetryRequest processing apply. Clients bind encryption of the second ClientHelloInner to encryption of the first ClientHelloInner via the derived echo_hrr_key by modifying HPKE setup as follows: @@ -582,7 +588,7 @@ echo_nonce = context.Export("tls13-echo-hrr-nonce", 16) ~~~ Clients then encrypt the second ClientHelloInner using this new HPKE context. -In doing so, the encrypted value is also authenticated by the echo_hrr_key. +In doing so, the encrypted value is also authenticated by echo_hrr_key. Client-facing servers perform the corresponding process when decrypting second ClientHelloInner messages. In particular, upon receipt of a second ClientHello message with a ClientEncryptedCH value, servers setup their HPKE context and @@ -707,8 +713,7 @@ messages, but just blindly forwards them. If the server sends a NewSessionTicket message, the corresponding ECHO PSK MUST be ignored by all other servers in the deployment when not negotiating ECHO, -including servers which do not implement this specification (in Split mode, -the server can detect this case by the presence of the "echo_info" extension). +including servers which do not implement this specification. # Compatibility Issues @@ -772,7 +777,7 @@ the client-facing server recognizing the key, but failing to decrypt the SNI. This causes a hard failure. Clients SHOULD NOT attempt to repair the connection in this case. -# TLS and HPKE Ciphersuite Mapping {#hpke-map} +# TLS and HPKE CipherSuite Mapping {#hpke-map} Per {{RFC8446}, TLS ciphersuites define an AEAD and hash algorithm. In contrast, HPKE composes AEAD algorithms and key derivation functions. The table below lists @@ -780,7 +785,7 @@ the mapping between ciphersuites and HPKE identifiers. TLS_AES_128_CCM_SHA256 an TLS_AES_128_CCM_8_SHA256 are not supported ECHO ciphersuites as they have no HPKE equivalent. -| TLS Ciphersuite | HPKE AEAD | HPKE KDF | +| TLS CipherSuite | HPKE AEAD | HPKE KDF | |:-------|:------------------------|:----------| | TLS_AES_128_GCM_SHA256 | AES-GCM-128 | HKDF-SHA256 | | TLS_AES_256_GCM_SHA384 | AES-GCM-256 | HKDF-SHA256 | From 6dd1e7ec8da14f61d955bca977e55e2b68a827d3 Mon Sep 17 00:00:00 2001 From: Christopher Wood Date: Thu, 5 Mar 2020 13:00:56 -0800 Subject: [PATCH 18/26] Simplify (?) extension compression. --- draft-ietf-tls-esni.md | 33 ++++++++++++++++++++++----------- 1 file changed, 22 insertions(+), 11 deletions(-) diff --git a/draft-ietf-tls-esni.md b/draft-ietf-tls-esni.md index 28ba5385..eed3d0b8 100644 --- a/draft-ietf-tls-esni.md +++ b/draft-ietf-tls-esni.md @@ -368,7 +368,7 @@ and having them both in the inner and outer ClientHello will lead to a very large overall size. One particularly pathological example is "key_share" with post-quantum algorithms. In order to reduce the impact of duplicated extensions, the client may use the -"outer_extension" extension. +"outer_extensions" extension. ~~~ enum { @@ -376,20 +376,31 @@ the impact of duplicated extensions, the client may use the } ExtensionType; struct { - ExtensionType extension; + ExtensionType outer_extensions<2..254>; uint8 hash<32..255>; - } OuterExtension; + } OuterExtensions; ~~~~ -This extension MUST only be used in ClientHelloInner and contains -a digest of the corresponding extension in ClientHelloOuter. +OuterExtensions MUST only be used in ClientHelloInner. It consists +of one or more ExtensionType values, each of which reference an +extension in ClientHelloOuter, and a digest of the complete +ClientHelloInner. + When sending ClientHello, the client first computes ClientHelloInner, -including the PSK binders, and then MAY substitute any extensions -which it knows will be duplicated in ClientHelloOuter with -the corresponding "outer_extension". The hash value is computed -over the entire extension, including the type and length field -and uses the same hash as for the KDF used to encrypt ClienHelloInner. -This process is reversed by client-facing server upon receipt. +including any PSK binders, and then MAY substitute extensions which +it knows will be duplicated in ClientHelloOuter. To do so, the client +computes a hash H of the entire ClientHelloInner message with the same +hash as for the KDF used to encrypt ClienHelloInner. Then, the client +removes and and replaces extensions from ClientHelloInner with a single +"outer_extensions" extension. The list of outer_extensions include those +which were removed from ClientHelloInner, in the order in which they were +removed. The hash contains full ClientHelloInner hash H computed above. + +This process is reversed by client-facing servers upon receipt. Specifically, +the server replaces the "outer_extensions" with extensions contained in +ClientHelloOuter. The server then computes a hash H' of the reconstructed +ClientHelloInner. If H' does not equal OuterExtensions.hash, the server aborts +the connection with an "illegal_parameter" alert. Clients SHOULD only use this mechanism for extensions which are large. All other extensions SHOULD appear in both ClientHelloInner From 7ddeff562f2037be71b8426d5970dea978aca501 Mon Sep 17 00:00:00 2001 From: Christopher Wood Date: Thu, 5 Mar 2020 13:26:33 -0800 Subject: [PATCH 19/26] Try to simplify, again. --- draft-ietf-tls-esni.md | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/draft-ietf-tls-esni.md b/draft-ietf-tls-esni.md index eed3d0b8..62d113db 100644 --- a/draft-ietf-tls-esni.md +++ b/draft-ietf-tls-esni.md @@ -447,9 +447,10 @@ values, ClientHelloInner MUST also contain: - an "echo_nonce" extension - TLS padding {{!RFC7685}} -The padding SHOULD contain X bytes of padding, where X = ECHOConfig.maximum_name_length - length(dns_name), -rounded up to the nearest multiple of 16, and dns_name is the DNS name in the -ClientHelloInner "server_name" extension. +Padding SHOULD be P = L - D bytes, where + +- L = ECHOConfig.maximum_name_length, rounded up to the nearest multiple of 16 +- D = len(dns_name), where dns_nae is the DNS name in the ClientHelloInner "server_name" extension When offering an encrypted ClientHello, the client MUST NOT offer to resume any non-ECHO PSKs. It additionally MUST NOT offer to resume any sessions for TLS 1.2 From 6273d6f2a9ec6f5bde3582c2dec5d5862645d880 Mon Sep 17 00:00:00 2001 From: ekr Date: Sat, 7 Mar 2020 15:54:38 -0800 Subject: [PATCH 20/26] Update draft-ietf-tls-esni.md --- draft-ietf-tls-esni.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/draft-ietf-tls-esni.md b/draft-ietf-tls-esni.md index 62d113db..c5d94e91 100644 --- a/draft-ietf-tls-esni.md +++ b/draft-ietf-tls-esni.md @@ -251,8 +251,8 @@ kem_id Clients MUST ignore any ECHOConfig structure with a key using a KEM they do not support. cipher_suites -: The list of cipher suites which can be used by the client to encrypt the ClientHello. -See {{hpke-map}} for information on how a CipherSuite maps to corresponding +: The list of TLS cipher suites which can be used by the client to encrypt the ClientHello. +See {{hpke-map}} for information on how a cipher suite maps to corresponding HPKE algorithm identifiers. maximum_name_length From a65808db3cb7c0dad0b8d524a34b49077e9f346c Mon Sep 17 00:00:00 2001 From: ekr Date: Sat, 7 Mar 2020 15:54:44 -0800 Subject: [PATCH 21/26] Update draft-ietf-tls-esni.md --- draft-ietf-tls-esni.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/draft-ietf-tls-esni.md b/draft-ietf-tls-esni.md index c5d94e91..b09add00 100644 --- a/draft-ietf-tls-esni.md +++ b/draft-ietf-tls-esni.md @@ -338,7 +338,11 @@ accepted by the server. When using ECHO, the client MUST also add an extension of type "echo_nonce" to the ClientHelloInner (but not to the outer -ClientHello). This extension is defined as follows: +ClientHello). This nonce ensures that the server's encrypted +Certificate can only be read by the entity which sent this +ClientHello. [[TODO: Describe HRR cut-and-paste 1 in +Security Considerations.]] +This extension is defined as follows: ~~~ enum { From 37ee8aa2dcfb6051d197582105851c11c4e4b8fb Mon Sep 17 00:00:00 2001 From: ekr Date: Sat, 7 Mar 2020 15:54:51 -0800 Subject: [PATCH 22/26] Update draft-ietf-tls-esni.md --- draft-ietf-tls-esni.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/draft-ietf-tls-esni.md b/draft-ietf-tls-esni.md index b09add00..09a5f904 100644 --- a/draft-ietf-tls-esni.md +++ b/draft-ietf-tls-esni.md @@ -454,7 +454,7 @@ values, ClientHelloInner MUST also contain: Padding SHOULD be P = L - D bytes, where - L = ECHOConfig.maximum_name_length, rounded up to the nearest multiple of 16 -- D = len(dns_name), where dns_nae is the DNS name in the ClientHelloInner "server_name" extension +- D = len(dns_name), where dns_name is the DNS name in the ClientHelloInner "server_name" extension When offering an encrypted ClientHello, the client MUST NOT offer to resume any non-ECHO PSKs. It additionally MUST NOT offer to resume any sessions for TLS 1.2 From 5a87f75f26e7e611afb1e1e42dbfe45e8dd967f4 Mon Sep 17 00:00:00 2001 From: ekr Date: Sat, 7 Mar 2020 15:54:58 -0800 Subject: [PATCH 23/26] Update draft-ietf-tls-esni.md --- draft-ietf-tls-esni.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/draft-ietf-tls-esni.md b/draft-ietf-tls-esni.md index 09a5f904..26404f7f 100644 --- a/draft-ietf-tls-esni.md +++ b/draft-ietf-tls-esni.md @@ -398,7 +398,7 @@ hash as for the KDF used to encrypt ClienHelloInner. Then, the client removes and and replaces extensions from ClientHelloInner with a single "outer_extensions" extension. The list of outer_extensions include those which were removed from ClientHelloInner, in the order in which they were -removed. The hash contains full ClientHelloInner hash H computed above. +removed. The hash contains the full ClientHelloInner hash H computed above. This process is reversed by client-facing servers upon receipt. Specifically, the server replaces the "outer_extensions" with extensions contained in From b4eb7e9c79762c1158ec507f4feb038ad9e7770b Mon Sep 17 00:00:00 2001 From: ekr Date: Sat, 7 Mar 2020 15:55:20 -0800 Subject: [PATCH 24/26] Update draft-ietf-tls-esni.md --- draft-ietf-tls-esni.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/draft-ietf-tls-esni.md b/draft-ietf-tls-esni.md index 26404f7f..80d11c8f 100644 --- a/draft-ietf-tls-esni.md +++ b/draft-ietf-tls-esni.md @@ -796,7 +796,7 @@ connection in this case. # TLS and HPKE CipherSuite Mapping {#hpke-map} Per {{RFC8446}, TLS ciphersuites define an AEAD and hash algorithm. In contrast, -HPKE composes AEAD algorithms and key derivation functions. The table below lists +HPKE defines separate AEAD algorithms and key derivation functions. The table below lists the mapping between ciphersuites and HPKE identifiers. TLS_AES_128_CCM_SHA256 and TLS_AES_128_CCM_8_SHA256 are not supported ECHO ciphersuites as they have no HPKE equivalent. From 2c7bfc13bc579fa1c232f7de9b44aef183940eab Mon Sep 17 00:00:00 2001 From: EKR Date: Sat, 7 Mar 2020 15:58:24 -0800 Subject: [PATCH 25/26] Editorial/todos --- draft-ietf-tls-esni.md | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/draft-ietf-tls-esni.md b/draft-ietf-tls-esni.md index 80d11c8f..08c352eb 100644 --- a/draft-ietf-tls-esni.md +++ b/draft-ietf-tls-esni.md @@ -157,7 +157,7 @@ not have access to the plaintext of the connection. ## ClientHello Encryption -SNI encryption works by encrypting the entire ClientHello, including +ECHO works by encrypting the entire ClientHello, including the SNI and any additional extensions such as ALPN. This requires that each provider publish a public key and metadata which is used for ClientHello encryption for all the domains for @@ -229,7 +229,6 @@ version : The version of the structure. For this specification, that value SHALL be 0xff03. Clients MUST ignore any ECHOConfig structure with a version they do not understand. -[[NOTE: This means that the RFC will presumably have a nonzero value.]] contents : An opaque byte string whose contents depend on the version of the structure. @@ -616,6 +615,10 @@ ClientHelloInner = context.Open("", ClientEncryptedCH.encrypted_ch) echo_nonce = context.Export("tls13-echo-hrr-nonce", 16) ~~~ +[[OPEN ISSUE: Should we be using the PSK input or the info input? +On the one hand, the requirements on info seem weaker, but maybe +actually this needs to be secret? Analysis needed.]] + [[OPEN ISSUE: This, along with trial decryption is pretty gross. It would just be a lot easier if we were willing to have the server indicate whether ECHO had been accepted or not. From 1f7ca544712754c7e7d992db51bbbb4f59c28276 Mon Sep 17 00:00:00 2001 From: EKR Date: Sat, 7 Mar 2020 16:09:39 -0800 Subject: [PATCH 26/26] Clarify --- draft-ietf-tls-esni.md | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/draft-ietf-tls-esni.md b/draft-ietf-tls-esni.md index 08c352eb..51253e5d 100644 --- a/draft-ietf-tls-esni.md +++ b/draft-ietf-tls-esni.md @@ -585,12 +585,8 @@ However, at this point, the client does not know whether the server processed ClientHelloOuter or ClientHelloInner, and MUST regenerate both values to be acceptable. Note: if the inner and outer ClientHellos use different groups for their key shares or differ in some other way, then the HelloRetryRequest may -actually be invalid for one or the other ClientHello. If the inner ClientHello -is unaffected by this retry, then the client only changes the outer ClientHello. -In contrast, if the outer ClientHello is unaffected by this retry, then the client -changes the inner ClientHello and recomputes any fields necessary in the outer -ClientHello ("encrypted_client_hello" extension contents, PSK binders, etc.) - +actually be invalid for one or the other ClientHello, in which case a +fresh ClientHello MUST be generated, ignoring the instructions in HelloRetryRequest. Otherwise, the usual rules for HelloRetryRequest processing apply. Clients bind encryption of the second ClientHelloInner to encryption of the first @@ -604,6 +600,7 @@ echo_nonce = context.Export("tls13-echo-hrr-nonce", 16) Clients then encrypt the second ClientHelloInner using this new HPKE context. In doing so, the encrypted value is also authenticated by echo_hrr_key. + Client-facing servers perform the corresponding process when decrypting second ClientHelloInner messages. In particular, upon receipt of a second ClientHello message with a ClientEncryptedCH value, servers setup their HPKE context and