| rfc9849v1.md | rfc9849.md | |||
|---|---|---|---|---|
| --- | --- | |||
| title: TLS Encrypted Client Hello | title: TLS Encrypted Client Hello | |||
| abbrev: TLS Encrypted Client Hello | abbrev: TLS Encrypted Client Hello | |||
| docname: draft-ietf-tls-esni-25 | docname: draft-ietf-tls-esni-25 | |||
| category: std | category: std | |||
| number: 9849 | number: 9849 | |||
| ipr: trust200902 | ipr: trust200902 | |||
| submissiontype: IETF | submissiontype: IETF | |||
| updates: | updates: | |||
| obsoletes: | obsoletes: | |||
| date: 2025-11 | date: 2025-12 | |||
| consensus: true | consensus: true | |||
| v: 3 | v: 3 | |||
| area: SEC | area: SEC | |||
| workgroup: tls | workgroup: tls | |||
| keyword: | keyword: | |||
| stand_alone: yes | stand_alone: yes | |||
| pi: [toc, sortrefs, symrefs] | pi: [toc, sortrefs, symrefs] | |||
| author: | author: | |||
| - name: Eric Rescorla | - name: Eric Rescorla | |||
| ins: E. Rescorla | ins: E. Rescorla | |||
| org: Independent | org: Knight-Georgetown Institute | |||
| email: ekr@rtfm.com | email: ekr@rtfm.com | |||
| - name: Kazuho Oku | - name: Kazuho Oku | |||
| ins: K. Oku | ins: K. Oku | |||
| org: Fastly | org: Fastly | |||
| email: kazuhooku@gmail.com | email: kazuhooku@gmail.com | |||
| - name: Nick Sullivan | - name: Nick Sullivan | |||
| ins: N. Sullivan | ins: N. Sullivan | |||
| org: Cryptography Consulting LLC | org: Cryptography Consulting LLC | |||
| email: nicholas.sullivan+ietf@gmail.com | email: nicholas.sullivan+ietf@gmail.com | |||
| - name: Christopher A. Wood | - name: Christopher A. Wood | |||
| ins: C. A. Wood | ins: C. A. Wood | |||
| org: Cloudflare | org: Cloudflare | |||
| email: caw@heapingbits.net | email: caw@heapingbits.net | |||
| normative: | normative: | |||
| RFC2119: | RFC2119: | |||
| RFC7918: | RFC7918: | |||
| RFC9180: | ||||
| display: HPKE | ||||
| RFCYYY1: | RFCYYY1: | |||
| title: > | title: > | |||
| Bootstrapping TLS Encrypted ClientHello with DNS Service Bindings | Bootstrapping TLS Encrypted ClientHello with DNS Service Bindings | |||
| target: https://www.rfc-editor.org/info/rfcYYY1 | target: https://www.rfc-editor.org/info/rfcYYY1 | |||
| seriesinfo: | seriesinfo: | |||
| RFC: YYY1 | RFC: YYY1 | |||
| DOI: 10.17487/RFCYYY1 | DOI: 10.17487/RFCYYY1 | |||
| date: November 2025 | date: December 2025 | |||
| author: | author: | |||
| - | - | |||
| ins: B. Schwartz | ins: B. Schwartz | |||
| surname: Schwartz | surname: Schwartz | |||
| fullname: Benjamin M. Schwartz | fullname: Benjamin M. Schwartz | |||
| - | - | |||
| ins: M. Bishop | ins: M. Bishop | |||
| surname: Bishop | surname: Bishop | |||
| fullname: Mike Bishop | fullname: Mike Bishop | |||
| - | - | |||
| ins: E. Nygren | ins: E. Nygren | |||
| surname: Nygren | surname: Nygren | |||
| fullname: Erik Nygren | fullname: Erik Nygren | |||
| RFC9180: | ||||
| display: HPKE | ||||
| informative: | informative: | |||
| WHATWG-IPV4: | WHATWG-IPV4: | |||
| author: | author: | |||
| - | - | |||
| org: WHATWG | org: WHATWG | |||
| title: "URL - IPv4 Parser" | title: "URL - IPv4 Parser" | |||
| target: https://url.spec.whatwg.org/#concept-ipv4-parser | target: https://url.spec.whatwg.org/#concept-ipv4-parser | |||
| date: May 2021 | date: May 2021 | |||
| refcontent: | refcontent: | |||
| "WHATWG Living Standard" | "WHATWG Living Standard" | |||
| skipping to change at line 128 ¶ | skipping to change at line 129 ¶ | |||
| We recommend updating this reference to the most current version of the WHATWG | We recommend updating this reference to the most current version of the WHATWG | |||
| Living Standard, replacing the URL with the more general URL to the standard | Living Standard, replacing the URL with the more general URL to the standard | |||
| (https://url.spec.whatwg.org/), and adding a "commit snapshot" URL to the | (https://url.spec.whatwg.org/), and adding a "commit snapshot" URL to the | |||
| reference. | reference. | |||
| Current: | Current: | |||
| [WHATWG-IPV4] | [WHATWG-IPV4] | |||
| WHATWG, "URL - IPv4 Parser", WHATWG Living Standard, May | WHATWG, "URL - IPv4 Parser", WHATWG Living Standard, May | |||
| 2021, <https://url.spec.whatwg.org/#concept-ipv4-parser>. | 2021, <https://url.spec.whatwg.org/#concept-ipv4-parser>. | |||
| b) RFC 6125 has been obsoleted by RFC 9525. May we replace RFC 6125 | ||||
| with RFC 9525? | ||||
| c) Informative Reference RFC 5077 has been obsoleted by RFC 8446. We | ||||
| recommend replacing RFC 5077 with RFC 8446. However, if RFC 5077 must be | ||||
| referenced, we suggest mentioning RFC 8446 (e.g., RFC 5077 has been obsoleted | ||||
| by RFC 8446). See Section 4.8.6 in the RFC Style Guide (RFC 7322). | ||||
| d) FYI, RFCYYY1 (draft-ietf-tls-svcb-ech) will be updated during the XML stage. | d) FYI, RFCYYY1 (draft-ietf-tls-svcb-ech) will be updated during the XML stage. | |||
| OK. | ||||
| --> | --> | |||
| <!-- [rfced] Please insert any keywords (beyond those that appear in | <!-- [rfced] Please insert any keywords (beyond those that appear in | |||
| the title) for use on https://www.rfc-editor.org/search. --> | the title) for use on https://www.rfc-editor.org/search. --> | |||
| This document describes a mechanism in Transport Layer Security (TLS) for | This document describes a mechanism in Transport Layer Security (TLS) for | |||
| encrypting a ClientHello message under a server public key. | encrypting a `ClientHello` message under a server public key. | |||
| --- middle | --- middle | |||
| # Introduction {#intro} | # Introduction {#intro} | |||
| Although TLS 1.3 {{!RFC8446}} encrypts most of the handshake, including the | Although TLS 1.3 {{!RFC8446}} encrypts most of the handshake, including the | |||
| server certificate, there are several ways in which an on-path attacker can | server certificate, there are several ways in which an on-path attacker can | |||
| learn private information about the connection. The plaintext Server Name | learn private information about the connection. The plaintext Server Name | |||
| Indication (SNI) extension in ClientHello messages, which leaks the target | Indication (SNI) extension in `ClientHello` messages, which leaks the target | |||
| domain for a given connection, is perhaps the most sensitive information | domain for a given connection, is perhaps the most sensitive information | |||
| left unencrypted in TLS 1.3. | left unencrypted in TLS 1.3. | |||
| This document specifies a new TLS extension called Encrypted Client Hello | This document specifies a new TLS extension called Encrypted Client | |||
| (ECH) that allows clients to encrypt their ClientHello to the TLS server. | Hello (ECH) that allows clients to encrypt their `ClientHello` to the | |||
| This protects the SNI and other potentially sensitive fields, such as the | TLS server. This protects the SNI and other potentially sensitive | |||
| Application-Layer Protocol Negotiation (ALPN) list {{?RFC7301}}. Co-located servers w | fields, such as the Application-Layer Protocol Negotiation (ALPN) list | |||
| ith consistent externally visible TLS | {{?RFC7301}}. Co-located servers with consistent externally visible | |||
| configurations and behavior, including supported versions and cipher suites and | TLS configurations and behavior, including supported versions and cipher suites and | |||
| how they respond to incoming client connections, form an anonymity set. (Note | how they respond to incoming client connections, form an anonymity set. (Note | |||
| that implementation-specific choices, such as extension ordering within TLS | that implementation-specific choices, such as extension ordering within TLS | |||
| messages or division of data into record-layer boundaries, can result in | messages or division of data into record-layer boundaries, can result in | |||
| different externally visible behavior, even for servers with consistent TLS | different externally visible behavior, even for servers with consistent TLS | |||
| configurations.) Usage of this mechanism reveals that a client is connecting | configurations.) Usage of this mechanism reveals that a client is connecting | |||
| to a particular service provider, but does not reveal which server from the | to a particular service provider, but does not reveal which server from the | |||
| anonymity set terminates the connection. Deployment implications of this | anonymity set terminates the connection. Deployment implications of this | |||
| feature are discussed in {{deployment}}. | feature are discussed in {{deployment}}. | |||
| ECH is not in itself sufficient to protect the identity of the server. | ECH is not in itself sufficient to protect the identity of the server. | |||
| skipping to change at line 217 ¶ | skipping to change at line 213 ¶ | |||
| Client <-----> | private.example.org | | Client <-----> | private.example.org | | |||
| | | | | | | |||
| | public.example.com | | | public.example.com | | |||
| | | | | | | |||
| +---------------------+ | +---------------------+ | |||
| Server | Server | |||
| (Client-Facing and Backend Combined) | (Client-Facing and Backend Combined) | |||
| ~~~~ | ~~~~ | |||
| {: #shared-mode title="Shared Mode Topology"} | {: #shared-mode title="Shared Mode Topology"} | |||
| In Shared Mode, the provider is the origin server for all the domains whose DNS | In shared mode, the provider is the origin server for all the domains whose DNS | |||
| records point to it. In this mode, the TLS connection is terminated by the | records point to it. In this mode, the TLS connection is terminated by the | |||
| provider. | provider. | |||
| ~~~~ | ~~~~ | |||
| +--------------------+ +---------------------+ | +--------------------+ +---------------------+ | |||
| | | | | | | | | | | |||
| | 2001:DB8::1111 | | 2001:DB8::EEEE | | | 2001:DB8::1111 | | 2001:DB8::EEEE | | |||
| Client <----------------------------->| | | Client <----------------------------->| | | |||
| | public.example.com | | private.example.org | | | public.example.com | | private.example.org | | |||
| | | | | | | | | | | |||
| +--------------------+ +---------------------+ | +--------------------+ +---------------------+ | |||
| Client-Facing Server Backend Server | Client-Facing Server Backend Server | |||
| ~~~~ | ~~~~ | |||
| {: #split-mode title="Split Mode Topology"} | {: #split-mode title="Split Mode Topology"} | |||
| In Split Mode, the provider is not the origin server for private domains. | In split mode, the provider is not the origin server for private domains. | |||
| Rather, the DNS records for private domains point to the provider, and the | Rather, the DNS records for private domains point to the provider, and the | |||
| provider's server relays the connection back to the origin server, who | provider's server relays the connection back to the origin server, who | |||
| terminates the TLS connection with the client. Importantly, the service provider | terminates the TLS connection with the client. Importantly, the service provider | |||
| does not have access to the plaintext of the connection beyond the unencrypted | does not have access to the plaintext of the connection beyond the unencrypted | |||
| portions of the handshake. | portions of the handshake. | |||
| In the remainder of this document, we will refer to the ECH-service provider as | In the remainder of this document, we will refer to the ECH-service provider as | |||
| the "client-facing server" and the TLS terminator as the "backend server". | the "client-facing server" and to the TLS terminator as the "backend server". | |||
| These are the same entity in Shared Mode, but in Split Mode, the client-facing | These are the same entity in shared mode, but in split mode, the client-facing | |||
| and backend servers are physically separated. | and backend servers are physically separated. | |||
| See {{security-considerations}} for more discussion about the ECH threat model | See {{security-considerations}} for more discussion about the ECH threat model | |||
| and how it relates to the client, client-facing server, and backend server. | and how it relates to the client, client-facing server, and backend server. | |||
| ## Encrypted ClientHello (ECH) | ## Encrypted ClientHello (ECH) | |||
| A client-facing server enables ECH by publishing an ECH configuration, which | A client-facing server enables ECH by publishing an ECH configuration, which | |||
| is an encryption public key and associated metadata. Domains which wish to | is an encryption public key and associated metadata. Domains which wish to | |||
| use ECH must publish this configuration, using the key associated | use ECH must publish this configuration, using the key associated | |||
| with the client-facing server. This document | with the client-facing server. This document | |||
| defines the ECH configuration's format, but delegates DNS publication details | defines the ECH configuration's format, but delegates DNS publication details | |||
| to {{!RFC9460}}. See | to {{!RFC9460}}. See | |||
| {{RFCYYY1}} for specifics about how ECH configurations | {{RFCYYY1}} for specifics about how ECH configurations | |||
| are advertised in SVCB and HTTPS records. Other delivery mechanisms are | are advertised in SVCB and HTTPS records. Other delivery mechanisms are | |||
| also possible. For example, the client may have the ECH configuration | also possible. For example, the client may have the ECH configuration | |||
| preconfigured. | preconfigured. | |||
| When a client wants to establish a TLS session with some backend server, it | When a client wants to establish a TLS session with some backend server, it | |||
| constructs a private ClientHello, referred to as the ClientHelloInner. | constructs a private `ClientHello`, referred to as the `ClientHelloInner`. | |||
| The client then constructs a public ClientHello, referred to as the | The client then constructs a public `ClientHello`, referred to as the | |||
| ClientHelloOuter. The ClientHelloOuter contains innocuous values for | `ClientHelloOuter`. The `ClientHelloOuter` contains innocuous values for | |||
| sensitive extensions and an "encrypted_client_hello" extension | sensitive extensions and an "encrypted_client_hello" extension | |||
| ({{encrypted-client-hello}}), which carries the encrypted ClientHelloInner. | ({{encrypted-client-hello}}), which carries the encrypted `ClientHelloInner`. | |||
| Finally, the client sends ClientHelloOuter to the server. | Finally, the client sends `ClientHelloOuter` to the server. | |||
| The server takes one of the following actions: | The server takes one of the following actions: | |||
| 1. If it does not support ECH or cannot decrypt the extension, it completes | 1. If it does not support ECH or cannot decrypt the extension, it completes | |||
| the handshake with ClientHelloOuter. This is referred to as rejecting ECH. | the handshake with `ClientHelloOuter`. This is referred to as rejecting ECH. | |||
| 1. If it successfully decrypts the extension, it forwards the ClientHelloInner | 1. If it successfully decrypts the extension, it forwards the `ClientHelloInner` | |||
| to the backend server, which completes the handshake. This is referred to | to the backend server, which completes the handshake. This is referred to | |||
| as accepting ECH. | as accepting ECH. | |||
| Upon receiving the server's response, the client determines whether or not ECH | Upon receiving the server's response, the client determines whether or not ECH | |||
| was accepted ({{determining-ech-acceptance}}) and proceeds with the handshake | was accepted ({{determining-ech-acceptance}}) and proceeds with the handshake | |||
| accordingly. When ECH is rejected, the resulting connection is not usable by | accordingly. When ECH is rejected, the resulting connection is not usable by | |||
| the client for application data. Instead, ECH rejection allows the client to | the client for application data. Instead, ECH rejection allows the client to | |||
| retry with up-to-date configuration ({{rejected-ech}}). | retry with up-to-date configuration ({{rejected-ech}}). | |||
| The primary goal of ECH is to ensure that connections to servers in the same | The primary goal of ECH is to ensure that connections to servers in the same | |||
| skipping to change at line 344 ¶ | skipping to change at line 340 ¶ | |||
| version: | version: | |||
| : The version of ECH for which this configuration is used. The version | : The version of ECH for which this configuration is used. The version | |||
| is the same as the code point for the | is the same as the code point for the | |||
| "encrypted_client_hello" extension. Clients MUST ignore any `ECHConfig` | "encrypted_client_hello" extension. Clients MUST ignore any `ECHConfig` | |||
| structure with a version they do not support. | structure with a version they do not support. | |||
| length: | length: | |||
| : The length, in bytes, of the next field. This length field allows | : The length, in bytes, of the next field. This length field allows | |||
| implementations to skip over the elements in such a list where they cannot | implementations to skip over the elements in such a list where they cannot | |||
| parse the specific version of ECHConfig. | parse the specific version of `ECHConfig`. | |||
| contents: | contents: | |||
| : An opaque byte string whose contents depend on the version. For this | : An opaque byte string whose contents depend on the version. For this | |||
| specification, the contents are an `ECHConfigContents` structure. | specification, the contents are an `ECHConfigContents` structure. | |||
| The `ECHConfigContents` structure contains the following fields: | The `ECHConfigContents` structure contains the following fields: | |||
| key_config: | key_config: | |||
| : A `HpkeKeyConfig` structure carrying the configuration information | : A `HpkeKeyConfig` structure carrying the configuration information | |||
| associated with the HPKE public key (an "ECH key"). Note that this | associated with the HPKE public key (an "ECH key"). Note that this | |||
| structure contains the `config_id` field, which applies to the entire | structure contains the `config_id` field, which applies to the entire | |||
| ECHConfigContents. | `ECHConfigContents`. | |||
| maximum_name_length: | `maximum_name_length`: | |||
| : The longest name of a backend server, if known. If not known, this value can | : The longest name of a backend server, if known. If not known, this value can | |||
| be set to zero. It is used to compute padding ({{padding}}) and does not | be set to zero. It is used to compute padding ({{padding}}) and does not | |||
| constrain server name lengths. Names may exceed this length if, e.g., | constrain server name lengths. Names may exceed this length if, e.g., | |||
| the server uses wildcard names or added new names to the anonymity set. | the server uses wildcard names or added new names to the anonymity set. | |||
| public_name: | public_name: | |||
| : The DNS name of the client-facing server, i.e., the entity trusted | : The DNS name of the client-facing server, i.e., the entity trusted | |||
| to update the ECH configuration. This is used to correct misconfigured clients, | to update the ECH configuration. This is used to correct misconfigured clients, | |||
| as described in {{rejected-ech}}. | as described in {{rejected-ech}}. | |||
| : See {{auth-public-name}} for how the client interprets and validates the | : See {{auth-public-name}} for how the client interprets and validates the | |||
| public_name. | public_name. | |||
| extensions: | extensions: | |||
| : A list of ECHConfigExtension values that the client must take into | : A list of ECHConfigExtension values that the client must take into | |||
| consideration when generating a ClientHello message. Each ECHConfigExtension | consideration when generating a `ClientHello` message. Each ECHConfigExtension | |||
| has a 2-octet type and opaque data value, where the data value is encoded | has a 2-octet type and opaque data value, where the data value is encoded | |||
| with a 2-octet integer representing the length of the data, in network byte | with a 2-octet integer representing the length of the data, in network byte | |||
| order. ECHConfigExtension values are described below ({{config-extensions}}). | order. ECHConfigExtension values are described below ({{config-extensions}}). | |||
| The `HpkeKeyConfig` structure contains the following fields: | The `HpkeKeyConfig` structure contains the following fields: | |||
| config_id: | `config_id`: | |||
| : A one-byte identifier for the given HPKE key configuration. This is used by | : A one-byte identifier for the given HPKE key configuration. This is used by | |||
| clients to indicate the key used for ClientHello encryption. {{config-ids}} | clients to indicate the key used for `ClientHello` encryption. {{config-ids}} | |||
| describes how client-facing servers allocate this value. | describes how client-facing servers allocate this value. | |||
| kem_id: | kem_id: | |||
| : The HPKE Key Encapsulation Mechanism (KEM) identifier corresponding | : The HPKE Key Encapsulation Mechanism (KEM) identifier corresponding | |||
| to `public_key`. Clients MUST ignore any `ECHConfig` structure with a | to `public_key`. Clients MUST ignore any `ECHConfig` structure with a | |||
| key using a KEM they do not support. | key using a KEM they do not support. | |||
| public_key: | `public_key`: | |||
| : The HPKE public key used by the client to encrypt ClientHelloInner. | : The HPKE public key used by the client to encrypt `ClientHelloInner`. | |||
| cipher_suites: | cipher_suites: | |||
| : The list of HPKE Key Derivation Function (KDF) and Authenticated Encryption with As sociated Data (AEAD) identifier pairs clients can use for encrypting | : The list of HPKE Key Derivation Function (KDF) and Authenticated Encryption with As sociated Data (AEAD) identifier pairs clients can use for encrypting | |||
| ClientHelloInner. See {{real-ech}} for how clients choose from this list. | `ClientHelloInner`. See {{real-ech}} for how clients choose from this list. | |||
| The client-facing server advertises a sequence of ECH configurations to clients, | The client-facing server advertises a sequence of ECH configurations to clients, | |||
| serialized as follows. | serialized as follows. | |||
| ~~~~ | ~~~~ | |||
| ECHConfig ECHConfigList<4..2^16-1>; | ECHConfig ECHConfigList<4..2^16-1>; | |||
| ~~~~ | ~~~~ | |||
| The `ECHConfigList` structure contains one or more `ECHConfig` structures in | The `ECHConfigList` structure contains one or more `ECHConfig` structures in | |||
| decreasing order of preference. This allows a server to support multiple | decreasing order of preference. This allows a server to support multiple | |||
| versions of ECH and multiple sets of ECH parameters. | versions of ECH and multiple sets of ECH parameters. | |||
| ## Configuration Identifiers {#config-ids} | ## Configuration Identifiers {#config-ids} | |||
| A client-facing server has a set of known ECHConfig values with corresponding | A client-facing server has a set of known `ECHConfig` values with corresponding | |||
| private keys. This set SHOULD contain the currently published values, as well as | private keys. This set SHOULD contain the currently published values, as well as | |||
| previous values that may still be in use, since clients may cache DNS records | previous values that may still be in use, since clients may cache DNS records | |||
| up to a TTL or longer. | up to a TTL or longer. | |||
| {{client-facing-server}} describes a trial decryption process for decrypting the | {{client-facing-server}} describes a trial decryption process for decrypting the | |||
| ClientHello. This can impact performance when the client-facing server maintains | `ClientHello`. This can impact performance when the client-facing server maintains | |||
| many known ECHConfig values. To avoid this, the client-facing server SHOULD | many known `ECHConfig` values. To avoid this, the client-facing server SHOULD | |||
| allocate distinct `config_id` values for each ECHConfig in its known set. The | allocate distinct `config_id` values for each `ECHConfig` in its known set. The | |||
| RECOMMENDED strategy is via rejection sampling, i.e., to randomly select | RECOMMENDED strategy is via rejection sampling, i.e., to randomly select | |||
| `config_id` repeatedly until it does not match any known ECHConfig. | `config_id` repeatedly until it does not match any known `ECHConfig`. | |||
| It is not necessary for `config_id` values across different client-facing | It is not necessary for `config_id` values across different client-facing | |||
| servers to be distinct. A backend server may be hosted behind two different | servers to be distinct. A backend server may be hosted behind two different | |||
| client-facing servers with colliding `config_id` values without any performance | client-facing servers with colliding `config_id` values without any performance | |||
| impact. Values may also be reused if the previous ECHConfig is no longer in the | impact. Values may also be reused if the previous `ECHConfig` is no longer in the | |||
| known set. | known set. | |||
| ## Configuration Extensions {#config-extensions} | ## Configuration Extensions {#config-extensions} | |||
| ECH configuration extensions are used to provide room for additional | ECH configuration extensions are used to provide room for additional | |||
| functionality as needed. The format is as defined in | functionality as needed. The format is as defined in | |||
| {{ech-configuration}} and mirrors {{Section 4.2 of RFC8446}}. However, | {{ech-configuration}} and mirrors {{Section 4.2 of RFC8446}}. However, | |||
| ECH configuration extension types are maintained by IANA as described | ECH configuration extension types are maintained by IANA as described | |||
| in {{config-extensions-iana}}. ECH configuration extensions follow | in {{config-extensions-iana}}. ECH configuration extensions follow | |||
| the same interpretation rules as TLS extensions: extensions MAY appear | the same interpretation rules as TLS extensions: extensions MAY appear | |||
| in any order, but there MUST NOT be more than one extension of the | in any order, but there MUST NOT be more than one extension of the | |||
| same type in the extensions block. Unlike TLS extensions, an extension | same type in the extensions block. Unlike TLS extensions, an extension | |||
| can be tagged as mandatory by using an extension type codepoint with | can be tagged as mandatory by using an extension type codepoint with | |||
| the high order bit set to 1. | the high order bit set to 1. | |||
| Clients MUST parse the extension list and check for unsupported mandatory | Clients MUST parse the extension list and check for unsupported mandatory | |||
| extensions. If an unsupported mandatory extension is present, clients MUST | extensions. If an unsupported mandatory extension is present, clients MUST | |||
| ignore the `ECHConfig`. | ignore the `ECHConfig`. | |||
| Any future information or hints that influence ClientHelloOuter SHOULD be | Any future information or hints that influence `ClientHelloOuter` SHOULD be | |||
| specified as ECHConfig extensions. This is primarily because the outer | specified as `ECHConfig` extensions. This is primarily because the outer | |||
| ClientHello exists only in support of ECH. Namely, it is both an envelope for | `ClientHello` exists only in support of ECH. Namely, it is both an envelope for | |||
| the encrypted inner ClientHello and an enabler for authenticated key mismatch | the encrypted inner `ClientHello` and an enabler for authenticated key mismatch | |||
| signals (see {{server-behavior}}). In contrast, the inner ClientHello is the | signals (see {{server-behavior}}). In contrast, the inner `ClientHello` is the | |||
| true ClientHello used upon ECH negotiation. | true `ClientHello` used upon ECH negotiation. | |||
| # The "encrypted_client_hello" Extension {#encrypted-client-hello} | # The "encrypted_client_hello" Extension {#encrypted-client-hello} | |||
| To offer ECH, the client sends an "encrypted_client_hello" extension in the | To offer ECH, the client sends an "encrypted_client_hello" extension in the | |||
| ClientHelloOuter. When it does, it MUST also send the extension in | `ClientHelloOuter`. When it does, it MUST also send the extension in | |||
| ClientHelloInner. | `ClientHelloInner`. | |||
| ~~~ | ~~~ | |||
| enum { | enum { | |||
| encrypted_client_hello(0xfe0d), (65535) | encrypted_client_hello(0xfe0d), (65535) | |||
| } ExtensionType; | } ExtensionType; | |||
| ~~~ | ~~~ | |||
| The payload of the extension has the following structure: | The payload of the extension has the following structure: | |||
| ~~~~ | ~~~~ | |||
| skipping to change at line 487 ¶ | skipping to change at line 483 ¶ | |||
| opaque payload<1..2^16-1>; | opaque payload<1..2^16-1>; | |||
| case inner: | case inner: | |||
| Empty; | Empty; | |||
| }; | }; | |||
| } ECHClientHello; | } ECHClientHello; | |||
| ~~~~ | ~~~~ | |||
| The outer extension uses the `outer` variant and the inner extension uses the | The outer extension uses the `outer` variant and the inner extension uses the | |||
| `inner` variant. The inner extension has an empty payload, which is included | `inner` variant. The inner extension has an empty payload, which is included | |||
| because TLS servers are not allowed to provide extensions in ServerHello | because TLS servers are not allowed to provide extensions in ServerHello | |||
| which were not included in ClientHello. The outer extension has the following | which were not included in `ClientHello`. The outer extension has the following | |||
| fields: | fields: | |||
| config_id: | `config_id`: | |||
| : The ECHConfigContents.key_config.config_id for the chosen ECHConfig. | : The `ECHConfigContents.key_config.config_id` for the chosen `ECHConfig`. | |||
| cipher_suite: | `cipher_suite`: | |||
| : The cipher suite used to encrypt ClientHelloInner. This MUST match a value | : The cipher suite used to encrypt `ClientHelloInner`. This MUST match a value | |||
| provided in the corresponding `ECHConfigContents.cipher_suites` list. | provided in the corresponding `ECHConfigContents.cipher_suites` list. | |||
| enc: | enc: | |||
| : The HPKE encapsulated key used by servers to decrypt the corresponding | : The HPKE encapsulated key used by servers to decrypt the corresponding | |||
| `payload` field. This field is empty in a ClientHelloOuter sent in response to | `payload` field. This field is empty in a `ClientHelloOuter` sent in response to | |||
| HelloRetryRequest. | HelloRetryRequest. | |||
| payload: | payload: | |||
| : The serialized and encrypted EncodedClientHelloInner structure, encrypted | : The serialized and encrypted `EncodedClientHelloInner` structure, encrypted | |||
| using HPKE as described in {{real-ech}}. | using HPKE as described in {{real-ech}}. | |||
| When a client offers the `outer` version of an "encrypted_client_hello" | When a client offers the `outer` version of an "encrypted_client_hello" | |||
| extension, the server MAY include an "encrypted_client_hello" extension in its | extension, the server MAY include an "encrypted_client_hello" extension in its | |||
| EncryptedExtensions message, as described in {{client-facing-server}}, with the | EncryptedExtensions message, as described in {{client-facing-server}}, with the | |||
| following payload: | following payload: | |||
| ~~~ | ~~~ | |||
| struct { | struct { | |||
| ECHConfigList retry_configs; | ECHConfigList retry_configs; | |||
| } ECHEncryptedExtensions; | } ECHEncryptedExtensions; | |||
| ~~~ | ~~~ | |||
| The response is valid only when the server used the ClientHelloOuter. If the | The response is valid only when the server used the `ClientHelloOuter`. If the | |||
| server sent this extension in response to the `inner` variant, then the client | server sent this extension in response to the `inner` variant, then the client | |||
| MUST abort with an "unsupported_extension" alert. | MUST abort with an "unsupported_extension" alert. | |||
| retry_configs: | retry_configs: | |||
| : An ECHConfigList structure containing one or more ECHConfig structures, in | : An `ECHConfigList` structure containing one or more `ECHConfig` structures, in | |||
| decreasing order of preference, to be used by the client as described in | decreasing order of preference, to be used by the client as described in | |||
| {{rejected-ech}}. These are known as the server's "retry configurations". | {{rejected-ech}}. These are known as the server's "retry configurations". | |||
| Finally, when the client offers the "encrypted_client_hello", if the payload is | Finally, when the client offers the "encrypted_client_hello", if the payload is | |||
| the `inner` variant and the server responds with HelloRetryRequest, it MUST | the `inner` variant and the server responds with HelloRetryRequest, it MUST | |||
| include an "encrypted_client_hello" extension with the following payload: | include an "encrypted_client_hello" extension with the following payload: | |||
| ~~~ | ~~~ | |||
| struct { | struct { | |||
| opaque confirmation[8]; | opaque confirmation[8]; | |||
| skipping to change at line 545 ¶ | skipping to change at line 541 ¶ | |||
| The value of ECHHelloRetryRequest.confirmation is set to | The value of ECHHelloRetryRequest.confirmation is set to | |||
| `hrr_accept_confirmation` as described in {{backend-server-hrr}}. | `hrr_accept_confirmation` as described in {{backend-server-hrr}}. | |||
| This document also defines the "ech_required" alert, which the client MUST send | This document also defines the "ech_required" alert, which the client MUST send | |||
| when it offered an "encrypted_client_hello" extension that was not accepted by | when it offered an "encrypted_client_hello" extension that was not accepted by | |||
| the server. (See {{alerts}}.) | the server. (See {{alerts}}.) | |||
| ## Encoding the ClientHelloInner {#encoding-inner} | ## Encoding the ClientHelloInner {#encoding-inner} | |||
| Before encrypting, the client pads and optionally compresses ClientHelloInner | Before encrypting, the client pads and optionally compresses `ClientHelloInner` | |||
| into an EncodedClientHelloInner structure, defined below: | into an `EncodedClientHelloInner` structure, defined below: | |||
| ~~~ | ~~~ | |||
| struct { | struct { | |||
| ClientHello client_hello; | ClientHello client_hello; | |||
| uint8 zeros[length_of_padding]; | uint8 zeros[length_of_padding]; | |||
| } EncodedClientHelloInner; | } EncodedClientHelloInner; | |||
| ~~~ | ~~~ | |||
| The `client_hello` field is computed by first making a copy of ClientHelloInner | The `client_hello` field is computed by first making a copy of `ClientHelloInner` | |||
| and setting the `legacy_session_id` field to the empty string. In TLS, this | and setting the `legacy_session_id` field to the empty string. In TLS, this | |||
| field uses the ClientHello structure defined in {{Section 4.1.2 of RFC8446}}. | field uses the `ClientHello` structure defined in {{Section 4.1.2 of RFC8446}}. | |||
| In DTLS, it uses the ClientHello structured defined in | In DTLS, it uses the `ClientHello` structured defined in | |||
| {{Section 5.3 of RFC9147}}. This does not include Handshake structure's | {{Section 5.3 of RFC9147}}. This does not include Handshake structure's | |||
| four-byte header in TLS, nor twelve-byte header in DTLS. The `zeros` field MUST | four-byte header in TLS, nor twelve-byte header in DTLS. The `zeros` field MUST | |||
| be all zeroes of length `length_of_padding` (see {{padding}}). | be all zeroes of length `length_of_padding` (see {{padding}}). | |||
| Repeating large extensions, such as "key_share" with post-quantum algorithms, | Repeating large extensions, such as "key_share" with post-quantum algorithms, | |||
| between ClientHelloInner and ClientHelloOuter can lead to excessive size. To | between `ClientHelloInner` and `ClientHelloOuter` can lead to excessive size. To | |||
| reduce the size impact, the client MAY substitute extensions which it knows | reduce the size impact, the client MAY substitute extensions which it knows | |||
| will be duplicated in ClientHelloOuter. It does so by removing and replacing | will be duplicated in `ClientHelloOuter`. It does so by removing and replacing | |||
| extensions from EncodedClientHelloInner with a single "ech_outer_extensions" | extensions from `EncodedClientHelloInner` with a single "ech_outer_extensions" | |||
| extension, defined as follows: | extension, defined as follows: | |||
| ~~~ | ~~~ | |||
| enum { | enum { | |||
| ech_outer_extensions(0xfd00), (65535) | ech_outer_extensions(0xfd00), (65535) | |||
| } ExtensionType; | } ExtensionType; | |||
| ExtensionType OuterExtensions<2..254>; | ExtensionType OuterExtensions<2..254>; | |||
| ~~~ | ~~~ | |||
| OuterExtensions contains the removed ExtensionType values. Each value references | OuterExtensions contains the removed ExtensionType values. Each value references | |||
| the matching extension in ClientHelloOuter. The values MUST be ordered | the matching extension in `ClientHelloOuter`. The values MUST be ordered | |||
| contiguously in ClientHelloInner, and the "ech_outer_extensions" extension MUST | contiguously in `ClientHelloInner`, and the "ech_outer_extensions" extension MUST | |||
| be inserted in the corresponding position in EncodedClientHelloInner. | be inserted in the corresponding position in `EncodedClientHelloInner`. | |||
| Additionally, the extensions MUST appear in ClientHelloOuter in the same | Additionally, the extensions MUST appear in `ClientHelloOuter` in the same | |||
| relative order. However, there is no requirement that they be contiguous. For | relative order. However, there is no requirement that they be contiguous. For | |||
| example, OuterExtensions may contain extensions A, B, and C, while ClientHelloOuter | example, OuterExtensions may contain extensions A, B, and C, while `ClientHelloOuter` | |||
| contains extensions A, D, B, C, E, and F. | contains extensions A, D, B, C, E, and F. | |||
| The "ech_outer_extensions" extension can only be included in | The "ech_outer_extensions" extension can only be included in | |||
| EncodedClientHelloInner and MUST NOT appear in either ClientHelloOuter or | `EncodedClientHelloInner` and MUST NOT appear in either `ClientHelloOuter` or | |||
| ClientHelloInner. | `ClientHelloInner`. | |||
| Finally, the client pads the message by setting the `zeros` field to a byte | Finally, the client pads the message by setting the `zeros` field to a byte | |||
| string whose contents are all zeros and whose length is the amount of padding | string whose contents are all zeros and whose length is the amount of padding | |||
| to add. {{padding}} describes a recommended padding scheme. | to add. {{padding}} describes a recommended padding scheme. | |||
| The client-facing server computes ClientHelloInner by reversing this process. | The client-facing server computes `ClientHelloInner` by reversing this process. | |||
| First, it parses EncodedClientHelloInner, interpreting all bytes after | First, it parses `EncodedClientHelloInner`, interpreting all bytes after | |||
| `client_hello` as padding. If any padding byte is non-zero, the server MUST | `client_hello` as padding. If any padding byte is non-zero, the server MUST | |||
| abort the connection with an "illegal_parameter" alert. | abort the connection with an "illegal_parameter" alert. | |||
| Next, it makes a copy of the `client_hello` field and copies the | Next, it makes a copy of the `client_hello` field and copies the | |||
| `legacy_session_id` field from ClientHelloOuter. It then looks for an | `legacy_session_id` field from `ClientHelloOuter`. It then looks for an | |||
| "ech_outer_extensions" extension. If found, it replaces the extension with the | "ech_outer_extensions" extension. If found, it replaces the extension with the | |||
| corresponding sequence of extensions in the ClientHelloOuter. The server MUST | corresponding sequence of extensions in the `ClientHelloOuter`. The server MUST | |||
| abort the connection with an "illegal_parameter" alert if any of the following | abort the connection with an "illegal_parameter" alert if any of the following | |||
| are true: | are true: | |||
| * Any referenced extension is missing in ClientHelloOuter. | * Any referenced extension is missing in `ClientHelloOuter`. | |||
| * Any extension is referenced in OuterExtensions more than once. | * Any extension is referenced in OuterExtensions more than once. | |||
| * "encrypted_client_hello" is referenced in OuterExtensions. | * "encrypted_client_hello" is referenced in OuterExtensions. | |||
| * The extensions in ClientHelloOuter corresponding to those in OuterExtensions | * The extensions in `ClientHelloOuter` corresponding to those in OuterExtensions | |||
| do not occur in the same order. | do not occur in the same order. | |||
| These requirements prevent an attacker from performing a packet amplification | These requirements prevent an attacker from performing a packet amplification | |||
| attack by crafting a ClientHelloOuter which decompresses to a much larger | attack by crafting a `ClientHelloOuter` which decompresses to a much larger | |||
| ClientHelloInner. This is discussed further in {{decompression-amp}}. | `ClientHelloInner`. This is discussed further in {{decompression-amp}}. | |||
| Implementations SHOULD construct the ClientHelloInner in linear | Implementations SHOULD construct the `ClientHelloInner` in linear | |||
| time. Quadratic time implementations (such as may happen via naive | time. Quadratic time implementations (such as may happen via naive | |||
| copying) create a denial-of-service risk. | copying) create a denial-of-service risk. | |||
| {{linear-outer-extensions}} describes a linear-time procedure that may be used | {{linear-outer-extensions}} describes a linear-time procedure that may be used | |||
| for this purpose. | for this purpose. | |||
| ## Authenticating the ClientHelloOuter {#authenticating-outer} | ## Authenticating the ClientHelloOuter {#authenticating-outer} | |||
| To prevent a network attacker from modifying the `ClientHelloOuter` | To prevent a network attacker from modifying the `ClientHelloOuter` | |||
| while keeping the same encrypted `ClientHelloInner` | while keeping the same encrypted `ClientHelloInner` | |||
| (see {{flow-clienthello-malleability}}), ECH authenticates ClientHelloOuter | (see {{flow-clienthello-malleability}}), ECH authenticates `ClientHelloOuter` | |||
| by passing ClientHelloOuterAAD as the associated data for HPKE sealing | by passing `ClientHelloOuterAAD` as the associated data for HPKE sealing | |||
| and opening operations. The ClientHelloOuterAAD is a serialized | and opening operations. The `ClientHelloOuterAAD` is a serialized | |||
| ClientHello structure, defined in {{Section 4.1.2 of RFC8446}} for TLS and | `ClientHello` structure, defined in {{Section 4.1.2 of RFC8446}} for TLS and | |||
| {{Section 5.3 of RFC9147}} for DTLS, which matches the ClientHelloOuter except | {{Section 5.3 of RFC9147}} for DTLS, which matches the `ClientHelloOuter` except | |||
| that the `payload` field of the "encrypted_client_hello" is replaced with a byte | that the `payload` field of the "encrypted_client_hello" is replaced with a byte | |||
| string of the same length but whose contents are zeros. This value does not | string of the same length but whose contents are zeros. This value does not | |||
| include Handshake structure's four-byte header in TLS nor twelve-byte header in | include Handshake structure's four-byte header in TLS nor twelve-byte header in | |||
| DTLS. | DTLS. | |||
| # Client Behavior | # Client Behavior | |||
| Clients that implement the ECH extension behave in one of two ways: either they | Clients that implement the ECH extension behave in one of two ways: either they | |||
| offer a real ECH extension, as described in {{real-ech}}, or they send a | offer a real ECH extension, as described in {{real-ech}}, or they send a | |||
| Generate Random Extensions And Sustain Extensibility (GREASE) {{?RFC8701}} | Generate Random Extensions And Sustain Extensibility (GREASE) {{?RFC8701}} | |||
| ECH extension, as described in {{grease-ech}}. Clients of the latter type do not | ECH extension, as described in {{grease-ech}}. Clients of the latter type do not | |||
| negotiate ECH. Instead, they generate a dummy ECH extension that is ignored by | negotiate ECH. Instead, they generate a dummy ECH extension that is ignored by | |||
| the server. (See {{dont-stick-out}} for an explanation.) The client offers ECH | the server. (See {{dont-stick-out}} for an explanation.) The client offers ECH | |||
| if it is in possession of a compatible ECH configuration and sends GREASE ECH | if it is in possession of a compatible ECH configuration and sends GREASE ECH | |||
| (see {{grease-ech}}) otherwise. | (see {{grease-ech}}) otherwise. | |||
| ## Offering ECH {#real-ech} | ## Offering ECH {#real-ech} | |||
| To offer ECH, the client first chooses a suitable ECHConfig from the server's | To offer ECH, the client first chooses a suitable `ECHConfig` from the server's | |||
| ECHConfigList. To determine if a given `ECHConfig` is suitable, it checks that | `ECHConfigList`. To determine if a given `ECHConfig` is suitable, it checks that | |||
| it supports the KEM algorithm identified by `ECHConfig.contents.kem_id`, at | it supports the KEM algorithm identified by `ECHConfig.contents.kem_id`, at | |||
| least one KDF/AEAD algorithm identified by `ECHConfig.contents.cipher_suites`, | least one KDF/AEAD algorithm identified by `ECHConfig.contents.cipher_suites`, | |||
| and the version of ECH indicated by `ECHConfig.contents.version`. Once a | and the version of ECH indicated by `ECHConfig.contents.version`. Once a | |||
| suitable configuration is found, the client selects the cipher suite it will | suitable configuration is found, the client selects the cipher suite it will | |||
| use for encryption. It MUST NOT choose a cipher suite or version not advertised | use for encryption. It MUST NOT choose a cipher suite or version not advertised | |||
| by the configuration. If no compatible configuration is found, then the client | by the configuration. If no compatible configuration is found, then the client | |||
| SHOULD proceed as described in {{grease-ech}}. | SHOULD proceed as described in {{grease-ech}}. | |||
| Next, the client constructs the ClientHelloInner message just as it does a | Next, the client constructs the `ClientHelloInner` message just as it does a | |||
| standard ClientHello, with the exception of the following rules: | standard `ClientHello`, with the exception of the following rules: | |||
| 1. It MUST NOT offer to negotiate TLS 1.2 or below. This is necessary to ensure | 1. It MUST NOT offer to negotiate TLS 1.2 or below. This is necessary to ensure | |||
| the backend server does not negotiate a TLS version that is incompatible with | the backend server does not negotiate a TLS version that is incompatible with | |||
| ECH. | ECH. | |||
| 1. It MUST NOT offer to resume any session for TLS 1.2 and below. | 1. It MUST NOT offer to resume any session for TLS 1.2 and below. | |||
| 1. If it intends to compress any extensions (see {{encoding-inner}}), it MUST | 1. If it intends to compress any extensions (see {{encoding-inner}}), it MUST | |||
| order those extensions consecutively. | order those extensions consecutively. | |||
| 1. It MUST include the "encrypted_client_hello" extension of type `inner` as | 1. It MUST include the "encrypted_client_hello" extension of type `inner` as | |||
| described in {{encrypted-client-hello}}. (This requirement is not applicable | described in {{encrypted-client-hello}}. (This requirement is not applicable | |||
| when the "encrypted_client_hello" extension is generated as described in | when the "encrypted_client_hello" extension is generated as described in | |||
| {{grease-ech}}.) | {{grease-ech}}.) | |||
| The client then constructs EncodedClientHelloInner as described in | The client then constructs `EncodedClientHelloInner` as described in | |||
| {{encoding-inner}}. It also computes an HPKE encryption context and `enc` value | {{encoding-inner}}. It also computes an HPKE encryption context and `enc` value | |||
| as: | as: | |||
| ~~~ | ~~~ | |||
| pkR = DeserializePublicKey(ECHConfig.contents.public_key) | pkR = DeserializePublicKey(ECHConfig.contents.public_key) | |||
| enc, context = SetupBaseS(pkR, | enc, context = SetupBaseS(pkR, | |||
| "tls ech" || 0x00 || ECHConfig) | "tls ech" || 0x00 || ECHConfig) | |||
| ~~~ | ~~~ | |||
| Next, it constructs a partial ClientHelloOuterAAD as it does a standard | Next, it constructs a partial `ClientHelloOuterAAD` as it does a standard | |||
| ClientHello, with the exception of the following rules: | `ClientHello`, with the exception of the following rules: | |||
| 1. It MUST offer to negotiate TLS 1.3 or above. | 1. It MUST offer to negotiate TLS 1.3 or above. | |||
| 1. If it compressed any extensions in EncodedClientHelloInner, it MUST copy the | 1. If it compressed any extensions in `EncodedClientHelloInner`, it MUST copy the | |||
| corresponding extensions from ClientHelloInner. The copied extensions | corresponding extensions from `ClientHelloInner`. The copied extensions | |||
| additionally MUST be in the same relative order as in ClientHelloInner. | additionally MUST be in the same relative order as in `ClientHelloInner`. | |||
| 1. It MUST copy the legacy\_session\_id field from ClientHelloInner. This | 1. It MUST copy the legacy\_session\_id field from `ClientHelloInner`. This | |||
| allows the server to echo the correct session ID for TLS 1.3's compatibility | allows the server to echo the correct session ID for TLS 1.3's compatibility | |||
| mode (see {{Appendix D.4 of RFC8446}}) when ECH is negotiated. Note that | mode (see {{Appendix D.4 of RFC8446}}) when ECH is negotiated. Note that | |||
| compatibility mode is not used in DTLS 1.3, but following this rule will | compatibility mode is not used in DTLS 1.3, but following this rule will | |||
| produce the correct results for both TLS 1.3 and DTLS 1.3. | produce the correct results for both TLS 1.3 and DTLS 1.3. | |||
| 1. It MAY copy any other field from the ClientHelloInner except | 1. It MAY copy any other field from the `ClientHelloInner` except | |||
| ClientHelloInner.random. Instead, It MUST generate a fresh | `ClientHelloInner.random`. Instead, It MUST generate a fresh | |||
| ClientHelloOuter.random using a secure random number generator. (See | `ClientHelloOuter.random` using a secure random number generator. (See | |||
| {{flow-client-reaction}}.) | {{flow-client-reaction}}.) | |||
| 1. It SHOULD place the value of `ECHConfig.contents.public_name` in the | 1. It SHOULD place the value of `ECHConfig.contents.public_name` in the | |||
| "server_name" extension. Clients that do not follow this step, or place a | "server_name" extension. Clients that do not follow this step, or place a | |||
| different value in the "server_name" extension, risk breaking the retry | different value in the "server_name" extension, risk breaking the retry | |||
| mechanism described in {{rejected-ech}} or failing to interoperate with | mechanism described in {{rejected-ech}} or failing to interoperate with | |||
| servers that require this step to be done; see {{client-facing-server}}. | servers that require this step to be done; see {{client-facing-server}}. | |||
| 1. When the client offers the "pre_shared_key" extension in ClientHelloInner, it | 1. When the client offers the "pre_shared_key" extension in `ClientHelloInner`, it | |||
| SHOULD also include a GREASE "pre_shared_key" extension in ClientHelloOuter, | SHOULD also include a GREASE "pre_shared_key" extension in `ClientHelloOuter`, | |||
| generated in the manner described in {{grease-psk}}. The client MUST NOT use | generated in the manner described in {{grease-psk}}. The client MUST NOT use | |||
| this extension to advertise a Pre-Shared Key (PSK) to the client-facing server. (S ee | this extension to advertise a PSK to the client-facing server. (See | |||
| {{flow-clienthello-malleability}}.) When the client includes a GREASE | {{flow-clienthello-malleability}}.) When the client includes a GREASE | |||
| "pre_shared_key" extension, it MUST also copy the "psk_key_exchange_modes" | "pre_shared_key" extension, it MUST also copy the "psk_key_exchange_modes" | |||
| from the ClientHelloInner into the ClientHelloOuter. | from the `ClientHelloInner` into the `ClientHelloOuter`. | |||
| 1. When the client offers the "early_data" extension in ClientHelloInner, it | 1. When the client offers the "early_data" extension in `ClientHelloInner`, it | |||
| MUST also include the "early_data" extension in ClientHelloOuter. This | MUST also include the "early_data" extension in `ClientHelloOuter`. This | |||
| allows servers that reject ECH and use ClientHelloOuter to safely ignore any | allows servers that reject ECH and use `ClientHelloOuter` to safely ignore any | |||
| early data sent by the client per {{RFC8446, Section 4.2.10}}. | early data sent by the client per {{RFC8446, Section 4.2.10}}. | |||
| The client might duplicate non-sensitive extensions in both messages. However, | The client might duplicate non-sensitive extensions in both messages. However, | |||
| implementations need to take care to ensure that sensitive extensions are not | implementations need to take care to ensure that sensitive extensions are not | |||
| offered in the ClientHelloOuter. See {{outer-clienthello}} for additional | offered in the `ClientHelloOuter`. See {{outer-clienthello}} for additional | |||
| guidance. | guidance. | |||
| Finally, the client encrypts the EncodedClientHelloInner with the above values, | Finally, the client encrypts the `EncodedClientHelloInner` with the above values, | |||
| as described in {{encrypting-clienthello}}, to construct a ClientHelloOuter. It | as described in {{encrypting-clienthello}}, to construct a `ClientHelloOuter`. It | |||
| sends this to the server and processes the response as described in | sends this to the server and processes the response as described in | |||
| {{determining-ech-acceptance}}. | {{determining-ech-acceptance}}. | |||
| ### Encrypting the ClientHello {#encrypting-clienthello} | ### Encrypting the ClientHello {#encrypting-clienthello} | |||
| Given an EncodedClientHelloInner, an HPKE encryption context and `enc` value, | Given an `EncodedClientHelloInner`, an HPKE encryption context and `enc` value, | |||
| and a partial ClientHelloOuterAAD, the client constructs a ClientHelloOuter as | and a partial `ClientHelloOuterAAD`, the client constructs a `ClientHelloOuter` as | |||
| follows. | follows. | |||
| First, the client determines the length L of encrypting EncodedClientHelloInner | First, the client determines the length L of encrypting `EncodedClientHelloInner` | |||
| with the selected HPKE AEAD. This is typically the sum of the plaintext length | with the selected HPKE AEAD. This is typically the sum of the plaintext length | |||
| and the AEAD tag length. The client then completes the ClientHelloOuterAAD with | and the AEAD tag length. The client then completes the `ClientHelloOuterAAD` with | |||
| an "encrypted_client_hello" extension. This extension value contains the outer | an "encrypted_client_hello" extension. This extension value contains the outer | |||
| variant of ECHClientHello with the following fields: | variant of `ECHClientHello` with the following fields: | |||
| - `config_id`, the identifier corresponding to the chosen ECHConfig structure; | - `config_id`, the identifier corresponding to the chosen `ECHConfig` structure; | |||
| - `cipher_suite`, the client's chosen cipher suite; | - `cipher_suite`, the client's chosen cipher suite; | |||
| - `enc`, as given above; and | - `enc`, as given above; and | |||
| - `payload`, a placeholder byte string containing L zeros. | - `payload`, a placeholder byte string containing L zeros. | |||
| If configuration identifiers (see {{ignored-configs}}) are to be | If configuration identifiers (see {{ignored-configs}}) are to be | |||
| ignored, `config_id` SHOULD be set to a randomly generated byte in the | ignored, `config_id` SHOULD be set to a randomly generated byte in the | |||
| first ClientHelloOuter and, in the event of a HelloRetryRequest (HRR), | first `ClientHelloOuter` and, in the event of a HelloRetryRequest (HRR), | |||
| MUST be left unchanged for the second ClientHelloOuter. | MUST be left unchanged for the second `ClientHelloOuter`. | |||
| The client serializes this structure to construct the ClientHelloOuterAAD. | The client serializes this structure to construct the `ClientHelloOuterAAD`. | |||
| It then computes the final payload as: | It then computes the final payload as: | |||
| ~~~ | ~~~ | |||
| final_payload = context.Seal(ClientHelloOuterAAD, | final_payload = context.Seal(ClientHelloOuterAAD, | |||
| EncodedClientHelloInner) | EncodedClientHelloInner) | |||
| ~~~ | ~~~ | |||
| Including `ClientHelloOuterAAD` as the HPKE AAD binds the `ClientHelloOuter` | Including `ClientHelloOuterAAD` as the HPKE AAD binds the `ClientHelloOuter` | |||
| to the `ClientHelloInner`, thus preventing attackers from modifying | to the `ClientHelloInner`, thus preventing attackers from modifying | |||
| `ClientHelloOuter` while keeping the same `ClientHelloInner`, as described in | `ClientHelloOuter` while keeping the same `ClientHelloInner`, as described in | |||
| {{flow-clienthello-malleability}}. | {{flow-clienthello-malleability}}. | |||
| Finally, the client replaces `payload` with `final_payload` to obtain | Finally, the client replaces `payload` with `final_payload` to obtain | |||
| ClientHelloOuter. The two values have the same length, so it is not necessary | `ClientHelloOuter`. The two values have the same length, so it is not necessary | |||
| to recompute length prefixes in the serialized structure. | to recompute length prefixes in the serialized structure. | |||
| Note this construction requires the "encrypted_client_hello" be computed after | Note this construction requires the "encrypted_client_hello" be computed after | |||
| all other extensions. This is possible because the ClientHelloOuter's | all other extensions. This is possible because the `ClientHelloOuter`'s | |||
| "pre_shared_key" extension is either omitted or uses a random binder | "pre_shared_key" extension is either omitted or uses a random binder | |||
| ({{grease-psk}}). | ({{grease-psk}}). | |||
| ### GREASE PSK {#grease-psk} | ### GREASE PSK {#grease-psk} | |||
| When offering ECH, the client is not permitted to advertise PSK identities in | When offering ECH, the client is not permitted to advertise PSK identities in | |||
| the ClientHelloOuter. However, the client can send a "pre_shared_key" extension | the `ClientHelloOuter`. However, the client can send a "pre_shared_key" extension | |||
| in the ClientHelloInner. In this case, when resuming a session with the client, | in the `ClientHelloInner`. In this case, when resuming a session with the client, | |||
| the backend server sends a "pre_shared_key" extension in its ServerHello. This | the backend server sends a "pre_shared_key" extension in its ServerHello. This | |||
| would appear to a network observer as if the server were sending this | would appear to a network observer as if the server were sending this | |||
| extension without solicitation, which would violate the extension rules | extension without solicitation, which would violate the extension rules | |||
| described in {{RFC8446}}. When offering a PSK in ClientHelloInner, | described in {{RFC8446}}. When offering a PSK in `ClientHelloInner`, | |||
| clients SHOULD send a GREASE "pre_shared_key" extension in the | clients SHOULD send a GREASE "pre_shared_key" extension in the | |||
| ClientHelloOuter to make it appear to the network as if the extension were | `ClientHelloOuter` to make it appear to the network as if the extension were | |||
| negotiated properly. | negotiated properly. | |||
| The client generates the extension payload by constructing an `OfferedPsks` | The client generates the extension payload by constructing an `OfferedPsks` | |||
| structure (see {{RFC8446, Section 4.2.11}}) as follows. For each PSK identity | structure (see {{RFC8446, Section 4.2.11}}) as follows. For each PSK identity | |||
| advertised in the ClientHelloInner, the client generates a random PSK identity | advertised in the `ClientHelloInner`, the client generates a random PSK identity | |||
| with the same length. It also generates a random, 32-bit, unsigned integer to | with the same length. It also generates a random, 32-bit, unsigned integer to | |||
| use as the `obfuscated_ticket_age`. Likewise, for each inner PSK binder, the | use as the `obfuscated_ticket_age`. Likewise, for each inner PSK binder, the | |||
| client generates a random string of the same length. | client generates a random string of the same length. | |||
| Per the rules of {{real-ech}}, the server is not permitted to resume a | Per the rules of {{real-ech}}, the server is not permitted to resume a | |||
| connection in the outer handshake. If ECH is rejected and the client-facing | connection in the outer handshake. If ECH is rejected and the client-facing | |||
| server replies with a "pre_shared_key" extension in its ServerHello, then the | server replies with a "pre_shared_key" extension in its ServerHello, then the | |||
| client MUST abort the handshake with an "illegal_parameter" alert. | client MUST abort the handshake with an "illegal_parameter" alert. | |||
| ### Recommended Padding Scheme {#padding} | ### Recommended Padding Scheme {#padding} | |||
| If the ClientHelloInner is encrypted without padding, then the length of | If the `ClientHelloInner` is encrypted without padding, then the length of | |||
| the `ClientHelloOuter.payload` can leak information about `ClientHelloInner`. | the `ClientHelloOuter.payload` can leak information about `ClientHelloInner`. | |||
| In order to prevent this, the `EncodedClientHelloInner` structure | In order to prevent this, the `EncodedClientHelloInner` structure | |||
| has a padding field. This section describes a deterministic mechanism for | has a padding field. This section describes a deterministic mechanism for | |||
| computing the required amount of padding based on the following | computing the required amount of padding based on the following | |||
| observation: individual extensions can reveal sensitive information through | observation: individual extensions can reveal sensitive information through | |||
| their length. Thus, each extension in the inner ClientHello may require | their length. Thus, each extension in the inner `ClientHello` may require | |||
| different amounts of padding. This padding may be fully determined by the | different amounts of padding. This padding may be fully determined by the | |||
| client's configuration or may require server input. | client's configuration or may require server input. | |||
| By way of example, clients typically support a small number of application | By way of example, clients typically support a small number of application | |||
| profiles. For instance, a browser might support HTTP with ALPN values | profiles. For instance, a browser might support HTTP with ALPN values | |||
| ["http/1.1", "h2"] and WebRTC media with ALPNs ["webrtc", "c-webrtc"]. Clients | ["http/1.1", "h2"] and WebRTC media with ALPNs ["webrtc", "c-webrtc"]. Clients | |||
| SHOULD pad this extension by rounding up to the total size of the longest ALPN | SHOULD pad this extension by rounding up to the total size of the longest ALPN | |||
| extension across all application profiles. The target padding length of most | extension across all application profiles. The target padding length of most | |||
| ClientHello extensions can be computed in this way. | `ClientHello` extensions can be computed in this way. | |||
| In contrast, clients do not know the longest SNI value in the client-facing | In contrast, clients do not know the longest SNI value in the client-facing | |||
| server's anonymity set without server input. Clients SHOULD use the ECHConfig's | server's anonymity set without server input. Clients SHOULD use the `ECHConfig`'s | |||
| `maximum_name_length` field as follows, where L is the `maximum_name_length` | `maximum_name_length` field as follows, where L is the `maximum_name_length` | |||
| value. | value. | |||
| 1. If the ClientHelloInner contained a "server_name" extension with a name of | 1. If the `ClientHelloInner` contained a "server_name" extension with a name of | |||
| length D, add max(0, L - D) bytes of padding. | length D, add max(0, L - D) bytes of padding. | |||
| 2. If the ClientHelloInner did not contain a "server_name" extension (e.g., if | 2. If the `ClientHelloInner` did not contain a "server_name" extension (e.g., if | |||
| the client is connecting to an IP address), add L + 9 bytes of padding. This | the client is connecting to an IP address), add L + 9 bytes of padding. This | |||
| is the length of a "server_name" extension with an L-byte name. | is the length of a "server_name" extension with an L-byte name. | |||
| Finally, the client SHOULD pad the entire message as follows: | Finally, the client SHOULD pad the entire message as follows: | |||
| 1. Let L be the length of the EncodedClientHelloInner with all the padding | 1. Let L be the length of the `EncodedClientHelloInner` with all the padding | |||
| computed so far. | computed so far. | |||
| 2. Let N = 31 - ((L - 1) % 32) and add N bytes of padding. | 2. Let N = 31 - ((L - 1) % 32) and add N bytes of padding. | |||
| This rounds the length of EncodedClientHelloInner up to a multiple of 32 bytes, | This rounds the length of `EncodedClientHelloInner` up to a multiple of 32 bytes, | |||
| reducing the set of possible lengths across all clients. | reducing the set of possible lengths across all clients. | |||
| In addition to padding ClientHelloInner, clients and servers will also need to | In addition to padding `ClientHelloInner`, clients and servers will also need to | |||
| pad all other handshake messages that have sensitive-length fields. For example, | pad all other handshake messages that have sensitive-length fields. For example, | |||
| if a client proposes ALPN values in ClientHelloInner, the server-selected value | if a client proposes ALPN values in `ClientHelloInner`, the server-selected value | |||
| will be returned in an EncryptedExtension, so that handshake message also needs | will be returned in an EncryptedExtension, so that handshake message also needs | |||
| to be padded using TLS record layer padding. | to be padded using TLS record layer padding. | |||
| ### Determining ECH Acceptance {#determining-ech-acceptance} | ### Determining ECH Acceptance {#determining-ech-acceptance} | |||
| As described in {{server-behavior}}, the server may either accept ECH and use | As described in {{server-behavior}}, the server may either accept ECH and use | |||
| ClientHelloInner or reject it and use ClientHelloOuter. This is determined by | `ClientHelloInner` or reject it and use `ClientHelloOuter`. This is determined by | |||
| the server's initial message. | the server's initial message. | |||
| If the message does not negotiate TLS 1.3 or higher, the server has rejected | If the message does not negotiate TLS 1.3 or higher, the server has rejected | |||
| ECH. Otherwise, it is either a ServerHello or HelloRetryRequest. | ECH. Otherwise, it is either a ServerHello or HelloRetryRequest. | |||
| If the message is a ServerHello, the client computes `accept_confirmation` as | If the message is a ServerHello, the client computes `accept_confirmation` as | |||
| described in {{backend-server}}. If this value matches the last 8 bytes of | described in {{backend-server}}. If this value matches the last 8 bytes of | |||
| `ServerHello.random`, the server has accepted ECH. Otherwise, it has rejected | `ServerHello.random`, the server has accepted ECH. Otherwise, it has rejected | |||
| ECH. | ECH. | |||
| If the message is a HelloRetryRequest, the client checks for the | If the message is a HelloRetryRequest, the client checks for the | |||
| "encrypted_client_hello" extension. If none is found, the server has rejected | "encrypted_client_hello" extension. If none is found, the server has rejected | |||
| ECH. Otherwise, if it has a length other than 8, the client aborts the handshake | ECH. Otherwise, if it has a length other than 8, the client aborts the handshake | |||
| with a "decode_error" alert. Otherwise, the client computes | with a "decode_error" alert. Otherwise, the client computes | |||
| `hrr_accept_confirmation` as described in {{backend-server-hrr}}. If this value | `hrr_accept_confirmation` as described in {{backend-server-hrr}}. If this value | |||
| matches the extension payload, the server has accepted ECH. Otherwise, it has | matches the extension payload, the server has accepted ECH. Otherwise, it has | |||
| rejected ECH. | rejected ECH. | |||
| If the server accepts ECH, the client handshakes with ClientHelloInner as | If the server accepts ECH, the client handshakes with `ClientHelloInner` as | |||
| described in {{accepted-ech}}. Otherwise, the client handshakes with | described in {{accepted-ech}}. Otherwise, the client handshakes with | |||
| ClientHelloOuter as described in {{rejected-ech}}. | `ClientHelloOuter` as described in {{rejected-ech}}. | |||
| <!-- [rfced] In the following sentence, does "length other than 8" refer to | <!-- [rfced] In the following sentence, does "length other than 8" refer to | |||
| bytes? If yes, may we update as follows? | bytes? If yes, may we update as follows? | |||
| Current: | Current: | |||
| Otherwise, if it has a length other than 8, the client aborts the | Otherwise, if it has a length other than 8, the client aborts the | |||
| handshake with a "decode_error" alert. | handshake with a "decode_error" alert. | |||
| Perhaps: | Perhaps: | |||
| Otherwise, if it has a length other than 8 bytes, the client aborts | Otherwise, if it has a length other than 8 bytes, the client aborts | |||
| the handshake with a "decode_error" alert. --> | the handshake with a "decode_error" alert. --> | |||
| ### Handshaking with ClientHelloInner {#accepted-ech} | ### Handshaking with ClientHelloInner {#accepted-ech} | |||
| If the server accepts ECH, the client proceeds with the connection as in | If the server accepts ECH, the client proceeds with the connection as in | |||
| {{RFC8446}}, with the following modifications: | {{RFC8446}}, with the following modifications: | |||
| The client behaves as if it had sent ClientHelloInner as the ClientHello. That | The client behaves as if it had sent `ClientHelloInner` as the `ClientHello`. That | |||
| is, it evaluates the handshake using the ClientHelloInner's preferences, and, | is, it evaluates the handshake using the `ClientHelloInner`'s preferences, and, | |||
| when computing the transcript hash ({{Section 4.4.1 of RFC8446}}), it uses | when computing the transcript hash ({{Section 4.4.1 of RFC8446}}), it uses | |||
| ClientHelloInner as the first ClientHello. | `ClientHelloInner` as the first `ClientHello`. | |||
| If the server responds with a HelloRetryRequest, the client computes the updated | If the server responds with a HelloRetryRequest, the client computes the updated | |||
| ClientHello message as follows: | `ClientHello` message as follows: | |||
| 1. It computes a second ClientHelloInner based on the first ClientHelloInner, as | 1. It computes a second `ClientHelloInner` based on the first `ClientHelloInner`, as | |||
| in {{Section 4.1.4 of RFC8446}}. The ClientHelloInner's | in {{Section 4.1.4 of RFC8446}}. The `ClientHelloInner`'s | |||
| "encrypted_client_hello" extension is left unmodified. | "encrypted_client_hello" extension is left unmodified. | |||
| 1. It constructs EncodedClientHelloInner as described in {{encoding-inner}}. | 1. It constructs `EncodedClientHelloInner` as described in {{encoding-inner}}. | |||
| 1. It constructs a second partial ClientHelloOuterAAD message. This message MUST | 1. It constructs a second partial `ClientHelloOuterAAD` message. This message MUST | |||
| be syntactically valid. The extensions MAY be copied from the original | be syntactically valid. The extensions MAY be copied from the original | |||
| ClientHelloOuter unmodified or omitted. If not sensitive, the client MAY | `ClientHelloOuter` unmodified or omitted. If not sensitive, the client MAY | |||
| copy updated extensions from the second ClientHelloInner for compression. | copy updated extensions from the second `ClientHelloInner` for compression. | |||
| 1. It encrypts EncodedClientHelloInner as described in | 1. It encrypts `EncodedClientHelloInner` as described in | |||
| {{encrypting-clienthello}}, using the second partial ClientHelloOuterAAD, to | {{encrypting-clienthello}}, using the second partial `ClientHelloOuterAAD`, to | |||
| obtain a second ClientHelloOuter. It reuses the original HPKE encryption | obtain a second `ClientHelloOuter`. It reuses the original HPKE encryption | |||
| context computed in {{real-ech}} and uses the empty string for `enc`. | context computed in {{real-ech}} and uses the empty string for `enc`. | |||
| The HPKE context maintains a sequence number, so this operation internally | The HPKE context maintains a sequence number, so this operation internally | |||
| uses a fresh nonce for each AEAD operation. Reusing the HPKE context avoids | uses a fresh nonce for each AEAD operation. Reusing the HPKE context avoids | |||
| an attack described in {{flow-hrr-hijack}}. | an attack described in {{flow-hrr-hijack}}. | |||
| The client then sends the second ClientHelloOuter to the server. However, as | The client then sends the second `ClientHelloOuter` to the server. However, as | |||
| above, it uses the second ClientHelloInner for preferences, and both the | above, it uses the second `ClientHelloInner` for preferences, and both the | |||
| ClientHelloInner messages for the transcript hash. Additionally, it checks the | `ClientHelloInner` messages for the transcript hash. Additionally, it checks the | |||
| resulting ServerHello for ECH acceptance as in {{determining-ech-acceptance}}. | resulting ServerHello for ECH acceptance as in {{determining-ech-acceptance}}. | |||
| If the ServerHello does not also indicate ECH acceptance, the client MUST | If the ServerHello does not also indicate ECH acceptance, the client MUST | |||
| terminate the connection with an "illegal_parameter" alert. | terminate the connection with an "illegal_parameter" alert. | |||
| ### Handshaking with ClientHelloOuter {#rejected-ech} | ### Handshaking with ClientHelloOuter {#rejected-ech} | |||
| If the server rejects ECH, the client proceeds with the handshake, | If the server rejects ECH, the client proceeds with the handshake, | |||
| authenticating for ECHConfig.contents.public_name as described in | authenticating for `ECHConfig.contents.public_name` as described in | |||
| {{auth-public-name}}. If authentication or the handshake fails, the client MUST | {{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 | return a failure to the calling application. It MUST NOT use the retry | |||
| configurations. It MUST NOT treat this as a secure signal to | configurations. It MUST NOT treat this as a secure signal to | |||
| disable ECH. | disable ECH. | |||
| If the server supplied an "encrypted_client_hello" extension in its | If the server supplied an "encrypted_client_hello" extension in its | |||
| EncryptedExtensions message, the client MUST check that it is syntactically | EncryptedExtensions message, the client MUST check that it is syntactically | |||
| valid and the client MUST abort the connection with a "decode_error" alert | valid and the client MUST abort the connection with a "decode_error" alert | |||
| otherwise. If an earlier TLS version was negotiated, the client MUST NOT enable | otherwise. If an earlier TLS version was negotiated, the client MUST NOT enable | |||
| the False Start optimization {{RFC7918}} for this handshake. If both | the False Start optimization {{RFC7918}} for this handshake. If both | |||
| skipping to change at line 981 ¶ | skipping to change at line 977 ¶ | |||
| a node with configuration B in the second. Note that this guidance | a node with configuration B in the second. Note that this guidance | |||
| does not apply to the cases in the previous paragraph where the server | does not apply to the cases in the previous paragraph where the server | |||
| has securely disabled ECH. | has securely disabled ECH. | |||
| If a client does not retry, it MUST report an error to the calling | If a client does not retry, it MUST report an error to the calling | |||
| application. | application. | |||
| ### Authenticating for the Public Name {#auth-public-name} | ### Authenticating for the Public Name {#auth-public-name} | |||
| When the server rejects ECH, it continues with the handshake using the plaintext | When the server rejects ECH, it continues with the handshake using the plaintext | |||
| "server_name" extension instead (see {{server-behavior}}). Then, clients that offer | "server_name" extension instead (see {{server-behavior}}). Clients that offer | |||
| ECH authenticate the connection with the public name as follows: | ECH then authenticate the connection with the public name as follows: | |||
| - The client MUST verify that the certificate is valid for | - The client MUST verify that the certificate is valid for | |||
| ECHConfig.contents.public_name. If invalid, it MUST abort the connection with | `ECHConfig.contents.public_name`. If invalid, it MUST abort the connection with | |||
| the appropriate alert. | the appropriate alert. | |||
| - If the server requests a client certificate, the client MUST respond with an | - If the server requests a client certificate, the client MUST respond with an | |||
| empty Certificate message, denoting no client certificate. | empty Certificate message, denoting no client certificate. | |||
| In verifying the client-facing server certificate, the client MUST | In verifying the client-facing server certificate, the client MUST | |||
| interpret the public name as a DNS-based reference identity | interpret the public name as a DNS-based reference identity | |||
| {{!RFC6125}}. Clients that incorporate DNS names and IP addresses into | {{!RFC9525}}. Clients that incorporate DNS names and IP addresses into | |||
| the same syntax (e.g. {{Section 7.4 of ?RFC3986}} and {{WHATWG-IPV4}}) | the same syntax (e.g. {{Section 7.4 of ?RFC3986}} and {{WHATWG-IPV4}}) | |||
| MUST reject names that would be interpreted as IPv4 addresses. | MUST reject names that would be interpreted as IPv4 addresses. | |||
| Clients that enforce this by checking ECHConfig.contents.public_name | Clients that enforce this by checking `ECHConfig.contents.public_name` | |||
| do not need to repeat the check when processing ECH rejection. | do not need to repeat the check when processing ECH rejection. | |||
| Note that authenticating a connection for the public name does not authenticate | Note that authenticating a connection for the public name does not authenticate | |||
| it for the origin. The TLS implementation MUST NOT report such connections as | it for the origin. The TLS implementation MUST NOT report such connections as | |||
| successful to the application. It additionally MUST ignore all session tickets | successful to the application. It additionally MUST ignore all session tickets | |||
| and session IDs presented by the server. These connections are only used to | and session IDs presented by the server. These connections are only used to | |||
| trigger retries, as described in {{rejected-ech}}. This may be implemented, for | trigger retries, as described in {{rejected-ech}}. This may be implemented, for | |||
| instance, by reporting a failed connection with a dedicated error code. | instance, by reporting a failed connection with a dedicated error code. | |||
| Prior to attempting a connection, a client SHOULD validate the `ECHConfig`. | Prior to attempting a connection, a client SHOULD validate the `ECHConfig`. | |||
| skipping to change at line 1037 ¶ | skipping to change at line 1033 ¶ | |||
| being forced to retry. However, they MUST handle ECH rejection for | being forced to retry. However, they MUST handle ECH rejection for | |||
| those connections as if it were a fresh connection, rather than | those connections as if it were a fresh connection, rather than | |||
| enforcing the single retry limit from {{rejected-ech}}. The reason | enforcing the single retry limit from {{rejected-ech}}. The reason | |||
| for this requirement is that if the server sends a "retry_config" | for this requirement is that if the server sends a "retry_config" | |||
| and then immediately rejects the resulting connection, it is | and then immediately rejects the resulting connection, it is | |||
| most likely misconfigured. However, if the server sends a "retry_config" | most likely misconfigured. However, if the server sends a "retry_config" | |||
| and then the client tries to use that to connect some time | and then the client tries to use that to connect some time | |||
| later, it is possible that the server has changed | later, it is possible that the server has changed | |||
| its configuration again and is now trying to recover. | its configuration again and is now trying to recover. | |||
| Any persisted information MUST be associated with the ECHConfig source | Any persisted information MUST be associated with the `ECHConfig` source | |||
| used to bootstrap the connection, such as a DNS SVCB ServiceMode record | used to bootstrap the connection, such as a DNS SVCB ServiceMode record | |||
| {{RFCYYY1}}. Clients MUST limit any sharing of persisted ECH-related | {{RFCYYY1}}. Clients MUST limit any sharing of persisted ECH-related | |||
| state to connections that use the same ECHConfig source. Otherwise, it | state to connections that use the same `ECHConfig` source. Otherwise, it | |||
| might become possible for the client to have the wrong public name for | might become possible for the client to have the wrong public name for | |||
| the server, making recovery impossible. | the server, making recovery impossible. | |||
| ECHConfigs learned from ECH rejection can be used as a tracking | ECHConfigs learned from ECH rejection can be used as a tracking | |||
| vector. Clients SHOULD impose the same lifetime and scope restrictions | vector. Clients SHOULD impose the same lifetime and scope restrictions | |||
| that they apply to other server-based | that they apply to other server-based | |||
| tracking vectors such as PSKs. | tracking vectors such as PSKs. | |||
| In general, the safest way for clients to minimize ECH retries is to | In general, the safest way for clients to minimize ECH retries is to | |||
| comply with any freshness rules (e.g., DNS TTLs) imposed by the ECH | comply with any freshness rules (e.g., DNS TTLs) imposed by the ECH | |||
| configuration. | configuration. | |||
| ## GREASE ECH {#grease-ech} | ## GREASE ECH {#grease-ech} | |||
| The GREASE ECH mechanism allows a connection between an ECH-capable client | The GREASE ECH mechanism allows a connection between an ECH-capable client | |||
| and a non-ECH server to appear to use ECH, thus reducing the extent to | and a non-ECH server to appear to use ECH, thus reducing the extent to | |||
| which ECH connections stick out (see {{dont-stick-out}}). | which ECH connections stick out (see {{dont-stick-out}}). | |||
| ### Client Greasing | ### Client Greasing | |||
| If the client attempts to connect to a server and does not have an ECHConfig | If the client attempts to connect to a server and does not have an `ECHConfig` | |||
| structure available for the server, it SHOULD send a GREASE {{?RFC8701}} | structure available for the server, it SHOULD send a GREASE {{?RFC8701}} | |||
| "encrypted_client_hello" extension in the first ClientHello as follows: | "encrypted_client_hello" extension in the first `ClientHello` as follows: | |||
| - Set the `config_id` field to a random byte. | - Set the `config_id` field to a random byte. | |||
| - Set the `cipher_suite` field to a supported HpkeSymmetricCipherSuite. The | - Set the `cipher_suite` field to a supported HpkeSymmetricCipherSuite. The | |||
| selection SHOULD vary to exercise all supported configurations, but MAY be | selection SHOULD vary to exercise all supported configurations, but MAY be | |||
| held constant for successive connections to the same server in the same | held constant for successive connections to the same server in the same | |||
| session. | session. | |||
| - Set the `enc` field to a randomly generated valid encapsulated public key | - Set the `enc` field to a randomly generated valid encapsulated public key | |||
| output by the HPKE KEM. | output by the HPKE KEM. | |||
| - Set the `payload` field to a randomly generated string of L+C bytes, where C | - Set the `payload` field to a randomly generated string of L+C bytes, where C | |||
| is the ciphertext expansion of the selected AEAD scheme and L is the size of | is the ciphertext expansion of the selected AEAD scheme and L is the size of | |||
| the EncodedClientHelloInner the client would compute when offering ECH, padded | the `EncodedClientHelloInner` the client would compute when offering ECH, padded | |||
| according to {{padding}}. | according to {{padding}}. | |||
| If sending a second ClientHello in response to a HelloRetryRequest, the | If sending a second `ClientHello` in response to a HelloRetryRequest, the | |||
| client copies the entire "encrypted_client_hello" extension from the first | client copies the entire "encrypted_client_hello" extension from the first | |||
| ClientHello. The identical value will reveal to an observer that the value of | `ClientHello`. The identical value will reveal to an observer that the value of | |||
| "encrypted_client_hello" was fake, but this only occurs if there is a | "encrypted_client_hello" was fake, but this only occurs if there is a | |||
| HelloRetryRequest. | HelloRetryRequest. | |||
| If the server sends an "encrypted_client_hello" extension in either | If the server sends an "encrypted_client_hello" extension in either | |||
| HelloRetryRequest or EncryptedExtensions, the client MUST check the extension | HelloRetryRequest or EncryptedExtensions, the client MUST check the extension | |||
| syntactically and abort the connection with a "decode_error" alert if it is | syntactically and abort the connection with a "decode_error" alert if it is | |||
| invalid. It otherwise ignores the extension. It MUST NOT save the | invalid. It otherwise ignores the extension. It MUST NOT save the | |||
| "retry_configs" value in EncryptedExtensions. | "retry_configs" value in EncryptedExtensions. | |||
| Offering a GREASE extension is not considered offering an encrypted ClientHello | Offering a GREASE extension is not considered offering an encrypted `ClientHello` | |||
| for purposes of requirements in {{real-ech}}. In particular, the client | for purposes of requirements in {{real-ech}}. In particular, the client | |||
| MAY offer to resume sessions established without ECH. | MAY offer to resume sessions established without ECH. | |||
| <!-- [rfced] It seems that "client" was intended to be "clients" (plural) in | <!-- [rfced] It seems that "client" was intended to be "clients" (plural) in | |||
| the sentence below and updated as follows. Please let us know if that is not | the sentence below and updated as follows. Please let us know if that is not | |||
| accurate. | accurate. | |||
| Original: | Original: | |||
| Correctly-implemented client will ignore those extensions. | Correctly-implemented client will ignore those extensions. | |||
| skipping to change at line 1131 ¶ | skipping to change at line 1127 ¶ | |||
| they do not recognize the mandatory extension. Servers SHOULD ensure | they do not recognize the mandatory extension. Servers SHOULD ensure | |||
| that any client using these configurations encounters a warning or error | that any client using these configurations encounters a warning or error | |||
| message. This can be accomplished in several ways, including: | message. This can be accomplished in several ways, including: | |||
| * By giving the extraneous configurations distinctive config IDs or | * By giving the extraneous configurations distinctive config IDs or | |||
| public names, and rejecting the TLS connection or inserting an | public names, and rejecting the TLS connection or inserting an | |||
| application-level warning message when these are observed. | application-level warning message when these are observed. | |||
| * By giving the extraneous configurations an invalid public | * By giving the extraneous configurations an invalid public | |||
| key and a public name not associated with the server so that | key and a public name not associated with the server so that | |||
| the initial ClientHelloOuter will not be decryptable and | the initial `ClientHelloOuter` will not be decryptable and | |||
| the server cannot perform the recovery flow described | the server cannot perform the recovery flow described | |||
| in {{rejected-ech}}. | in {{rejected-ech}}. | |||
| # Server Behavior {#server-behavior} | # Server Behavior {#server-behavior} | |||
| As described in {{topologies}}, servers can play two roles, either as | As described in {{topologies}}, servers can play two roles, either as | |||
| the client-facing server or as the back-end server. | the client-facing server or as the back-end server. | |||
| Depending on the server role, the `ECHClientHello` will be different: | Depending on the server role, the `ECHClientHello` will be different: | |||
| * A client-facing server expects an `ECHClientHello.type` of `outer`, and | * A client-facing server expects an `ECHClientHello.type` of `outer`, and | |||
| proceeds as described in {{client-facing-server}} to extract a | proceeds as described in {{client-facing-server}} to extract a | |||
| ClientHelloInner, if available. | `ClientHelloInner`, if available. | |||
| * A backend server expects an `ECHClientHello.type` of `inner`, and | * A backend server expects an `ECHClientHello.type` of `inner`, and | |||
| proceeds as described in {{backend-server}}. | proceeds as described in {{backend-server}}. | |||
| In split mode, a client-facing server which receives a `ClientHello` | In split mode, a client-facing server which receives a `ClientHello` | |||
| with `ECHClientHello.type` of `inner` MUST abort with an | with `ECHClientHello.type` of `inner` MUST abort with an | |||
| "illegal_parameter" alert. Similarly, in split mode, a backend server | "illegal_parameter" alert. Similarly, in split mode, a backend server | |||
| which receives a `ClientHello` with `ECHClientHello.type` of `outer` | which receives a `ClientHello` with `ECHClientHello.type` of `outer` | |||
| MUST abort with an "illegal_parameter" alert. | MUST abort with an "illegal_parameter" alert. | |||
| skipping to change at line 1170 ¶ | skipping to change at line 1166 ¶ | |||
| If `ECHClientHello.type` is not a valid `ECHClientHelloType`, then | If `ECHClientHello.type` is not a valid `ECHClientHelloType`, then | |||
| the server MUST abort with an "illegal_parameter" alert. | the server MUST abort with an "illegal_parameter" alert. | |||
| If the "encrypted_client_hello" is not present, then the server completes the | If the "encrypted_client_hello" is not present, then the server completes the | |||
| handshake normally, as described in {{RFC8446}}. | handshake normally, as described in {{RFC8446}}. | |||
| ## Client-Facing Server {#client-facing-server} | ## Client-Facing Server {#client-facing-server} | |||
| Upon receiving an "encrypted_client_hello" extension in an initial | Upon receiving an "encrypted_client_hello" extension in an initial | |||
| ClientHello, the client-facing server determines if it will accept ECH prior | `ClientHello`, the client-facing server determines if it will accept ECH prior | |||
| to negotiating any other TLS parameters. Note that successfully decrypting the | to negotiating any other TLS parameters. Note that successfully decrypting the | |||
| extension will result in a new ClientHello to process, so even the client's TLS | extension will result in a new `ClientHello` to process, so even the client's TLS | |||
| version preferences may have changed. | version preferences may have changed. | |||
| First, the server collects a set of candidate ECHConfig values. This list is | First, the server collects a set of candidate `ECHConfig` values. This list is | |||
| determined by one of the two following methods: | determined by one of the two following methods: | |||
| 1. Compare ECHClientHello.config_id against identifiers of each known ECHConfig | 1. Compare `ECHClientHello.config_id` against identifiers of each known `ECHConfig` | |||
| and select the ones that match, if any, as candidates. | and select the ones that match, if any, as candidates. | |||
| 2. Collect all known ECHConfig values as candidates, with trial decryption | 2. Collect all known `ECHConfig` values as candidates, with trial decryption | |||
| below determining the final selection. | below determining the final selection. | |||
| Some uses of ECH, such as local discovery mode, may randomize the | Some uses of ECH, such as local discovery mode, may randomize the | |||
| ECHClientHello.config_id since it can be used as a tracking vector. In such | `ECHClientHello.config_id` since it can be used as a tracking vector. In such | |||
| cases, the second method SHOULD be used for matching the ECHClientHello to a | cases, the second method SHOULD be used for matching the `ECHClientHello` to a | |||
| known ECHConfig. See {{ignored-configs}}. Unless specified by the application | known `ECHConfig`. See {{ignored-configs}}. Unless specified by the application | |||
| profile or otherwise externally configured, implementations MUST use the first | profile or otherwise externally configured, implementations MUST use the first | |||
| method. | method. | |||
| The server then iterates over the candidate ECHConfig values, attempting to | The server then iterates over the candidate `ECHConfig` values, attempting to | |||
| decrypt the "encrypted_client_hello" extension as follows. | decrypt the "encrypted_client_hello" extension as follows. | |||
| The server verifies that the ECHConfig supports the cipher suite indicated by | The server verifies that the `ECHConfig` supports the cipher suite indicated by | |||
| the ECHClientHello.cipher_suite and that the version of ECH indicated by the | the `ECHClientHello.cipher_suite` and that the version of ECH indicated by the | |||
| client matches the ECHConfig.version. If not, the server continues to the next | client matches the `ECHConfig.version`. If not, the server continues to the next | |||
| candidate ECHConfig. | candidate `ECHConfig`. | |||
| Next, the server decrypts ECHClientHello.payload, using the private key skR | Next, the server decrypts `ECHClientHello.payload`, using the private key skR | |||
| corresponding to ECHConfig, as follows: | corresponding to `ECHConfig`, as follows: | |||
| ~~~ | ~~~ | |||
| context = SetupBaseR(ECHClientHello.enc, skR, | context = SetupBaseR(ECHClientHello.enc, skR, | |||
| "tls ech" || 0x00 || ECHConfig) | "tls ech" || 0x00 || ECHConfig) | |||
| EncodedClientHelloInner = context.Open(ClientHelloOuterAAD, | EncodedClientHelloInner = context.Open(ClientHelloOuterAAD, | |||
| ECHClientHello.payload) | ECHClientHello.payload) | |||
| ~~~ | ~~~ | |||
| ClientHelloOuterAAD is computed from ClientHelloOuter as described in | `ClientHelloOuterAAD` is computed from `ClientHelloOuter` as described in | |||
| {{authenticating-outer}}. The `info` parameter to SetupBaseR is the | {{authenticating-outer}}. The `info` parameter to SetupBaseR is the | |||
| concatenation "tls ech", a zero byte, and the serialized ECHConfig. If | concatenation "tls ech", a zero byte, and the serialized `ECHConfig`. If | |||
| decryption fails, the server continues to the next candidate ECHConfig. | decryption fails, the server continues to the next candidate `ECHConfig`. | |||
| Otherwise, the server reconstructs ClientHelloInner from | Otherwise, the server reconstructs `ClientHelloInner` from | |||
| EncodedClientHelloInner, as described in {{encoding-inner}}. It then stops | `EncodedClientHelloInner`, as described in {{encoding-inner}}. It then stops | |||
| iterating over the candidate ECHConfig values. | iterating over the candidate `ECHConfig` values. | |||
| Once the server has chosen the correct ECHConfig, it MAY verify that the value | Once the server has chosen the correct `ECHConfig`, it MAY verify that the value | |||
| in the ClientHelloOuter "server_name" extension matches the value of | in the `ClientHelloOuter` "server_name" extension matches the value of | |||
| ECHConfig.contents.public_name and abort with an "illegal_parameter" alert if | `ECHConfig.contents.public_name` and abort with an "illegal_parameter" alert if | |||
| these do not match. This optional check allows the server to limit ECH | these do not match. This optional check allows the server to limit ECH | |||
| connections to only use the public SNI values advertised in its ECHConfigs. | connections to only use the public SNI values advertised in its ECHConfigs. | |||
| The server MUST be careful not to unnecessarily reject connections if the same | The server MUST be careful not to unnecessarily reject connections if the same | |||
| ECHConfig id or keypair is used in multiple ECHConfigs with distinct public | `ECHConfig` id or keypair is used in multiple ECHConfigs with distinct public | |||
| names. | names. | |||
| Upon determining the ClientHelloInner, the client-facing server checks that the | Upon determining the `ClientHelloInner`, the client-facing server checks that the | |||
| message includes a well-formed "encrypted_client_hello" extension of type | message includes a well-formed "encrypted_client_hello" extension of type | |||
| `inner` and that it does not offer TLS 1.2 or below. If either of these checks | `inner` and that it does not offer TLS 1.2 or below. If either of these checks | |||
| fails, the client-facing server MUST abort with an "illegal_parameter" alert. | fails, the client-facing server MUST abort with an "illegal_parameter" alert. | |||
| If these checks succeed, the client-facing server then forwards the | If these checks succeed, the client-facing server then forwards the | |||
| ClientHelloInner to the appropriate backend server, which proceeds as in | `ClientHelloInner` to the appropriate backend server, which proceeds as in | |||
| {{backend-server}}. If the backend server responds with a HelloRetryRequest, the | {{backend-server}}. If the backend server responds with a HelloRetryRequest, the | |||
| client-facing server forwards it, decrypts the client's second ClientHelloOuter | client-facing server forwards it, decrypts the client's second `ClientHelloOuter` | |||
| using the procedure in {{client-facing-server-hrr}}, and forwards the resulting | using the procedure in {{client-facing-server-hrr}}, and forwards the resulting | |||
| second ClientHelloInner. The client-facing server forwards all other TLS | second `ClientHelloInner`. The client-facing server forwards all other TLS | |||
| messages between the client and backend server unmodified. | messages between the client and backend server unmodified. | |||
| Otherwise, if all candidate ECHConfig values fail to decrypt the extension, the | Otherwise, if all candidate `ECHConfig` values fail to decrypt the extension, the | |||
| client-facing server MUST ignore the extension and proceed with the connection | client-facing server MUST ignore the extension and proceed with the connection | |||
| using ClientHelloOuter with the following modifications: | using `ClientHelloOuter` with the following modifications: | |||
| * If sending a HelloRetryRequest, the server MAY include an | * If sending a HelloRetryRequest, the server MAY include an | |||
| "encrypted_client_hello" extension with a payload of 8 random bytes; see | "encrypted_client_hello" extension with a payload of 8 random bytes; see | |||
| {{dont-stick-out}} for details. | {{dont-stick-out}} for details. | |||
| * If the server is configured with any ECHConfigs, it MUST include the | * If the server is configured with any ECHConfigs, it MUST include the | |||
| "encrypted_client_hello" extension in its EncryptedExtensions with the | "encrypted_client_hello" extension in its EncryptedExtensions with the | |||
| "retry_configs" field set to one or more ECHConfig structures with up-to-date | "retry_configs" field set to one or more `ECHConfig` structures with up-to-date | |||
| keys. Servers MAY supply multiple ECHConfig values of different versions. | keys. Servers MAY supply multiple `ECHConfig` values of different versions. | |||
| This allows a server to support multiple versions at once. | This allows a server to support multiple versions at once. | |||
| Note that decryption failure could indicate a GREASE ECH extension (see | Note that decryption failure could indicate a GREASE ECH extension (see | |||
| {{grease-ech}}), so it is necessary for servers to proceed with the connection | {{grease-ech}}), so it is necessary for servers to proceed with the connection | |||
| and rely on the client to abort if ECH was required. In particular, the | and rely on the client to abort if ECH was required. In particular, the | |||
| unrecognized value alone does not indicate a misconfigured ECH advertisement | unrecognized value alone does not indicate a misconfigured ECH advertisement | |||
| ({{misconfiguration}}). Instead, servers can measure occurrences of the | ({{misconfiguration}}). Instead, servers can measure occurrences of the | |||
| "ech_required" alert to detect this case. | "ech_required" alert to detect this case. | |||
| ### Sending HelloRetryRequest {#client-facing-server-hrr} | ### Sending HelloRetryRequest {#client-facing-server-hrr} | |||
| After sending or forwarding a HelloRetryRequest, the client-facing server does | After sending or forwarding a HelloRetryRequest, the client-facing server does | |||
| not repeat the steps in {{client-facing-server}} with the second | not repeat the steps in {{client-facing-server}} with the second | |||
| ClientHelloOuter. Instead, it continues with the ECHConfig selection from the | `ClientHelloOuter`. Instead, it continues with the `ECHConfig` selection from the | |||
| first ClientHelloOuter as follows: | first `ClientHelloOuter` as follows: | |||
| If the client-facing server accepted ECH, it checks that the second ClientHelloOuter | If the client-facing server accepted ECH, it checks that the second `ClientHelloOuter ` | |||
| also contains the "encrypted_client_hello" extension. If not, it MUST abort the | also contains the "encrypted_client_hello" extension. If not, it MUST abort the | |||
| handshake with a "missing_extension" alert. Otherwise, it checks that | handshake with a "missing_extension" alert. Otherwise, it checks that | |||
| ECHClientHello.cipher_suite and ECHClientHello.config_id are unchanged, and that | `ECHClientHello.cipher_suite` and `ECHClientHello.config_id` are unchanged, and that | |||
| ECHClientHello.enc is empty. If not, it MUST abort the handshake with an | `ECHClientHello.enc` is empty. If not, it MUST abort the handshake with an | |||
| "illegal_parameter" alert. | "illegal_parameter" alert. | |||
| Finally, it decrypts the new ECHClientHello.payload as a second message with the | Finally, it decrypts the new `ECHClientHello.payload` as a second message with the | |||
| previous HPKE context: | previous HPKE context: | |||
| ~~~ | ~~~ | |||
| EncodedClientHelloInner = context.Open(ClientHelloOuterAAD, | EncodedClientHelloInner = context.Open(ClientHelloOuterAAD, | |||
| ECHClientHello.payload) | ECHClientHello.payload) | |||
| ~~~ | ~~~ | |||
| ClientHelloOuterAAD is computed as described in {{authenticating-outer}}, but | `ClientHelloOuterAAD` is computed as described in {{authenticating-outer}}, but | |||
| using the second ClientHelloOuter. If decryption fails, the client-facing | using the second `ClientHelloOuter`. If decryption fails, the client-facing | |||
| server MUST abort the handshake with a "decrypt_error" alert. Otherwise, it | server MUST abort the handshake with a "decrypt_error" alert. Otherwise, it | |||
| reconstructs the second ClientHelloInner from the new EncodedClientHelloInner | reconstructs the second `ClientHelloInner` from the new `EncodedClientHelloInner` | |||
| as described in {{encoding-inner}}, using the second ClientHelloOuter for | as described in {{encoding-inner}}, using the second `ClientHelloOuter` for | |||
| any referenced extensions. | any referenced extensions. | |||
| The client-facing server then forwards the resulting ClientHelloInner to the | The client-facing server then forwards the resulting `ClientHelloInner` to the | |||
| backend server. It forwards all subsequent TLS messages between the client and | backend server. It forwards all subsequent TLS messages between the client and | |||
| backend server unmodified. | backend server unmodified. | |||
| If the client-facing server rejected ECH, or if the first ClientHello did not | If the client-facing server rejected ECH, or if the first `ClientHello` did not | |||
| include an "encrypted_client_hello" extension, the client-facing server | include an "encrypted_client_hello" extension, the client-facing server | |||
| proceeds with the connection as usual. The server does not decrypt the | proceeds with the connection as usual. The server does not decrypt the | |||
| second ClientHello's ECHClientHello.payload value, if there is one. | second `ClientHello`'s `ECHClientHello.payload` value, if there is one. | |||
| Moreover, if the server is configured with any ECHConfigs, it MUST include the | Moreover, if the server is configured with any ECHConfigs, it MUST include the | |||
| "encrypted_client_hello" extension in its EncryptedExtensions with the | "encrypted_client_hello" extension in its EncryptedExtensions with the | |||
| "retry_configs" field set to one or more ECHConfig structures with up-to-date | "retry_configs" field set to one or more `ECHConfig` structures with up-to-date | |||
| keys, as described in {{client-facing-server}}. | keys, as described in {{client-facing-server}}. | |||
| Note that a client-facing server that forwards the first ClientHello cannot | Note that a client-facing server that forwards the first `ClientHello` cannot | |||
| include its own "cookie" extension if the backend server sends a | include its own "cookie" extension if the backend server sends a | |||
| HelloRetryRequest. This means that the client-facing server either needs to | HelloRetryRequest. This means that the client-facing server either needs to | |||
| maintain state for such a connection or it needs to coordinate with the backend | maintain state for such a connection or it needs to coordinate with the backend | |||
| server to include any information it requires to process the second ClientHello. | server to include any information it requires to process the second `ClientHello`. | |||
| <!-- [rfced] May we rephrase the following text for an improved sentence flow? | <!-- [rfced] May we rephrase the following text for an improved sentence flow? | |||
| Original: | Original: | |||
| The backend server embeds in ServerHello.random a string derived from | The backend server embeds in `ServerHello.random` a string derived from | |||
| the inner handshake. | the inner handshake. | |||
| Perhaps: | Perhaps: | |||
| A string derived from the inner handshake is embedded into | A string derived from the inner handshake is embedded into | |||
| ServerHello.random by the backend server. --> | `ServerHello.random` by the backend server. --> | |||
| ## Backend Server {#backend-server} | ## Backend Server {#backend-server} | |||
| Upon receipt of an "encrypted_client_hello" extension of type `inner` in a | Upon receipt of an "encrypted_client_hello" extension of type `inner` in a | |||
| ClientHello, if the backend server negotiates TLS 1.3 or higher, then it MUST | `ClientHello`, if the backend server negotiates TLS 1.3 or higher, then it MUST | |||
| confirm ECH acceptance to the client by computing its ServerHello as described | confirm ECH acceptance to the client by computing its ServerHello as described | |||
| here. | here. | |||
| The backend server embeds in ServerHello.random a string derived from the inner | The backend server embeds in `ServerHello.random` a string derived from the inner | |||
| handshake. It begins by computing its ServerHello as usual, except the last 8 | handshake. It begins by computing its ServerHello as usual, except the last 8 | |||
| bytes of ServerHello.random are set to zero. It then computes the transcript | bytes of `ServerHello.random` are set to zero. It then computes the transcript | |||
| hash for ClientHelloInner up to and including the modified ServerHello, as | hash for `ClientHelloInner` up to and including the modified ServerHello, as | |||
| described in {{RFC8446, Section 4.4.1}}. Let transcript_ech_conf denote the | described in {{RFC8446, Section 4.4.1}}. Let transcript_ech_conf denote the | |||
| output. Finally, the backend server overwrites the last 8 bytes of the | output. Finally, the backend server overwrites the last 8 bytes of the | |||
| ServerHello.random with the following string: | `ServerHello.random` with the following string: | |||
| ~~~ | ~~~ | |||
| accept_confirmation = HKDF-Expand-Label( | accept_confirmation = HKDF-Expand-Label( | |||
| HKDF-Extract(0, ClientHelloInner.random), | HKDF-Extract(0, ClientHelloInner.random), | |||
| "ech accept confirmation", | "ech accept confirmation", | |||
| transcript_ech_conf, | transcript_ech_conf, | |||
| 8) | 8) | |||
| ~~~ | ~~~ | |||
| where HKDF-Expand-Label is defined in {{RFC8446, Section 7.1}}, "0" indicates a | where HKDF-Expand-Label is defined in {{RFC8446, Section 7.1}}, "0" indicates a | |||
| string of Hash.length bytes set to zero, and Hash is the hash function used to | string of Hash.length bytes set to zero, and Hash is the hash function used to | |||
| compute the transcript hash. In DTLS, the modified version of HKDF-Expand-Label | compute the transcript hash. In DTLS, the modified version of HKDF-Expand-Label | |||
| defined in {{RFC9147, Section 5.9}} is used instead. | defined in {{RFC9147, Section 5.9}} is used instead. | |||
| The backend server MUST NOT perform this operation if it negotiated TLS 1.2 or | The backend server MUST NOT perform this operation if it negotiated TLS 1.2 or | |||
| below. Note that doing so would overwrite the downgrade signal for TLS 1.3 (see | below. Note that doing so would overwrite the downgrade signal for TLS 1.3 (see | |||
| {{RFC8446, Section 4.1.3}}). | {{RFC8446, Section 4.1.3}}). | |||
| ### Sending HelloRetryRequest {#backend-server-hrr} | ### Sending HelloRetryRequest {#backend-server-hrr} | |||
| When the backend server sends HelloRetryRequest in response to the ClientHello, | When the backend server sends HelloRetryRequest in response to the `ClientHello`, | |||
| it similarly confirms ECH acceptance by adding a confirmation signal to its | it similarly confirms ECH acceptance by adding a confirmation signal to its | |||
| HelloRetryRequest. But instead of embedding the signal in the | HelloRetryRequest. But instead of embedding the signal in the | |||
| HelloRetryRequest.random (the value of which is specified by {{RFC8446}}), it | HelloRetryRequest.random (the value of which is specified by {{RFC8446}}), it | |||
| sends the signal in an extension. | sends the signal in an extension. | |||
| The backend server begins by computing HelloRetryRequest as usual, except that | The backend server begins by computing HelloRetryRequest as usual, except that | |||
| it also contains an "encrypted_client_hello" extension with a payload of 8 zero | it also contains an "encrypted_client_hello" extension with a payload of 8 zero | |||
| bytes. It then computes the transcript hash for the first ClientHelloInner, | bytes. It then computes the transcript hash for the first `ClientHelloInner`, | |||
| denoted ClientHelloInner1, up to and including the modified HelloRetryRequest. | denoted ClientHelloInner1, up to and including the modified HelloRetryRequest. | |||
| Let transcript_hrr_ech_conf denote the output. Finally, the backend server | Let transcript_hrr_ech_conf denote the output. Finally, the backend server | |||
| overwrites the payload of the "encrypted_client_hello" extension with the | overwrites the payload of the "encrypted_client_hello" extension with the | |||
| following string: | following string: | |||
| ~~~ | ~~~ | |||
| hrr_accept_confirmation = HKDF-Expand-Label( | hrr_accept_confirmation = HKDF-Expand-Label( | |||
| HKDF-Extract(0, ClientHelloInner1.random), | HKDF-Extract(0, ClientHelloInner1.random), | |||
| "hrr ech accept confirmation", | "hrr ech accept confirmation", | |||
| transcript_hrr_ech_conf, | transcript_hrr_ech_conf, | |||
| 8) | 8) | |||
| ~~~ | ~~~ | |||
| In the subsequent ServerHello message, the backend server sends the | In the subsequent ServerHello message, the backend server sends the | |||
| accept_confirmation value as described in {{backend-server}}. | `accept_confirmation` value as described in {{backend-server}}. | |||
| # Deployment Considerations {#deployment} | # Deployment Considerations {#deployment} | |||
| The design of ECH as specified in this document necessarily requires changes | The design of ECH as specified in this document necessarily requires changes | |||
| to client, client-facing server, and backend server. Coordination between | to client, client-facing server, and backend server. Coordination between | |||
| client-facing and backend server requires care, as deployment mistakes | client-facing and backend server requires care, as deployment mistakes | |||
| can lead to compatibility issues. These are discussed in {{compat-issues}}. | can lead to compatibility issues. These are discussed in {{compat-issues}}. | |||
| Beyond coordination difficulties, ECH deployments may also induce challenges | Beyond coordination difficulties, ECH deployments may also induce challenges | |||
| for use cases of information that ECH protects. In particular, | for use cases of information that ECH protects. In particular, | |||
| skipping to change at line 1421 ¶ | skipping to change at line 1417 ¶ | |||
| mismatch, the server will reject ECH and respond with | mismatch, the server will reject ECH and respond with | |||
| "retry_configs". If the server does | "retry_configs". If the server does | |||
| not understand the "encrypted_client_hello" extension at all, it will ignore it | not understand the "encrypted_client_hello" extension at all, it will ignore it | |||
| as required by {{Section 4.1.2 of RFC8446}}. Provided the server can present a certif icate | as required by {{Section 4.1.2 of RFC8446}}. Provided the server can present a certif icate | |||
| valid for the public name, the client can safely retry with updated settings, | valid for the public name, the client can safely retry with updated settings, | |||
| as described in {{rejected-ech}}. | as described in {{rejected-ech}}. | |||
| Unless ECH is disabled as a result of successfully establishing a connection to | Unless ECH is disabled as a result of successfully establishing a connection to | |||
| the public name, the client MUST NOT fall back to using unencrypted | 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 | 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 | `ClientHello`, including the SNI. It MAY attempt to use another server from the | |||
| DNS results, if one is provided. | DNS results, if one is provided. | |||
| In order to ensure that the retry mechanism works successfully, servers | In order to ensure that the retry mechanism works successfully, servers | |||
| SHOULD ensure that every endpoint which might receive a TLS connection | SHOULD ensure that every endpoint which might receive a TLS connection | |||
| is provisioned with an appropriate certificate for the public name. | is provisioned with an appropriate certificate for the public name. | |||
| This is especially important during periods of server reconfiguration | This is especially important during periods of server reconfiguration | |||
| when different endpoints might have different configurations. | when different endpoints might have different configurations. | |||
| ### Middleboxes | ### Middleboxes | |||
| skipping to change at line 1468 ¶ | skipping to change at line 1464 ¶ | |||
| TLS-terminating proxies even in cases where the server supports ECH | TLS-terminating proxies even in cases where the server supports ECH | |||
| but the proxy does not, as detailed below. | but the proxy does not, as detailed below. | |||
| --> | --> | |||
| The requirements in {{RFC8446, Section 9.3}} which require proxies to | The requirements in {{RFC8446, Section 9.3}} which require proxies to | |||
| act as conforming TLS client and server provide interoperability | act as conforming TLS client and server provide interoperability | |||
| with TLS-terminating proxies even in cases where the server supports | with TLS-terminating proxies even in cases where the server supports | |||
| ECH but the proxy does not, as detailed below. | ECH but the proxy does not, as detailed below. | |||
| The proxy must ignore unknown parameters and | The proxy must ignore unknown parameters and | |||
| generate its own ClientHello containing only parameters it understands. Thus, | generate its own `ClientHello` containing only parameters it understands. Thus, | |||
| when presenting a certificate to the client or sending a ClientHello to the | when presenting a certificate to the client or sending a `ClientHello` to the | |||
| server, the proxy will act as if connecting to the ClientHelloOuter | server, the proxy will act as if connecting to the `ClientHelloOuter` | |||
| server_name, which SHOULD match the public name (see {{real-ech}}) without | server_name, which SHOULD match the public name (see {{real-ech}}) without | |||
| echoing the "encrypted_client_hello" extension. | echoing the "encrypted_client_hello" extension. | |||
| Depending on whether the client is configured to accept the proxy's certificate | 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 | as authoritative for the public name, this may trigger the retry logic described | |||
| in {{rejected-ech}} or result in a connection failure. A proxy which is not | in {{rejected-ech}} or result in a connection failure. A proxy which is not | |||
| authoritative for the public name cannot forge a signal to disable ECH. | authoritative for the public name cannot forge a signal to disable ECH. | |||
| ## Deployment Impact {#no-sni} | ## Deployment Impact {#no-sni} | |||
| skipping to change at line 1526 ¶ | skipping to change at line 1522 ¶ | |||
| read packets from the network, but they cannot perform any sort of active | read packets from the network, but they cannot perform any sort of active | |||
| behavior such as probing servers or querying DNS. A middlebox that filters based | behavior such as probing servers or querying DNS. A middlebox that filters based | |||
| on plaintext packet contents is one example of a passive attacker. In contrast, | on plaintext packet contents is one example of a passive attacker. In contrast, | |||
| active attackers can also write packets into the network for malicious purposes, | active attackers can also write packets into the network for malicious purposes, | |||
| such as interfering with existing connections, probing servers, and querying | such as interfering with existing connections, probing servers, and querying | |||
| DNS. In short, an active attacker corresponds to the conventional threat model | DNS. In short, an active attacker corresponds to the conventional threat model | |||
| {{?RFC3552}} for TLS 1.3 {{RFC8446}}. | {{?RFC3552}} for TLS 1.3 {{RFC8446}}. | |||
| Passive and active attackers can exist anywhere in the network, including | Passive and active attackers can exist anywhere in the network, including | |||
| between the client and client-facing server, as well as between the | between the client and client-facing server, as well as between the | |||
| client-facing and backend servers when running ECH in Split Mode. However, | client-facing and backend servers when running ECH in split mode. However, | |||
| for Split Mode in particular, ECH makes two additional assumptions: | for split mode in particular, ECH makes two additional assumptions: | |||
| 1. The channel between each client-facing and each backend server is | 1. The channel between each client-facing and each backend server is | |||
| authenticated such that the backend server only accepts messages from trusted | authenticated such that the backend server only accepts messages from trusted | |||
| client-facing servers. The exact mechanism for establishing this authenticated | client-facing servers. The exact mechanism for establishing this authenticated | |||
| channel is out of scope for this document. | channel is out of scope for this document. | |||
| 1. The attacker cannot correlate messages between a client and client-facing | 1. The attacker cannot correlate messages between a client and client-facing | |||
| server with messages between client-facing and backend server. Such correlation | server with messages between client-facing and backend server. Such correlation | |||
| could allow an attacker to link information unique to a backend server, such as | could allow an attacker to link information unique to a backend server, such as | |||
| their server name or IP address, with a client's encrypted ClientHelloInner. | their server name or IP address, with a client's encrypted `ClientHelloInner`. | |||
| Correlation could occur through timing analysis of messages across the | Correlation could occur through timing analysis of messages across the | |||
| client-facing server, or via examining the contents of messages sent between | client-facing server, or via examining the contents of messages sent between | |||
| client-facing and backend servers. The exact mechanism for preventing this sort | client-facing and backend servers. The exact mechanism for preventing this sort | |||
| of correlation is out of scope for this document. | of correlation is out of scope for this document. | |||
| Given this threat model, the primary goals of ECH are as follows. | Given this threat model, the primary goals of ECH are as follows. | |||
| 1. Security preservation. Use of ECH does not weaken the security properties of | 1. Security preservation. Use of ECH does not weaken the security properties of | |||
| TLS without ECH. | TLS without ECH. | |||
| 1. Handshake privacy. TLS connection establishment to a server name | 1. Handshake privacy. TLS connection establishment to a server name | |||
| within an anonymity set is indistinguishable from a connection to | within an anonymity set is indistinguishable from a connection to | |||
| any other server name within the anonymity set. (The anonymity set | any other server name within the anonymity set. (The anonymity set | |||
| is defined in {{intro}}.) | is defined in {{intro}}.) | |||
| 1. Downgrade resistance. An attacker cannot downgrade a connection that | 1. Downgrade resistance. An attacker cannot downgrade a connection that | |||
| attempts to use ECH to one that does not use ECH. | attempts to use ECH to one that does not use ECH. | |||
| These properties were formally proven in {{ECH-Analysis}}. | These properties were formally proven in {{ECH-Analysis}}. | |||
| With regards to handshake privacy, client-facing server configuration | With regards to handshake privacy, client-facing server configuration | |||
| determines the size of the anonymity set. For example, if a | determines the size of the anonymity set. For example, if a | |||
| client-facing server uses distinct ECHConfig values for each server | client-facing server uses distinct `ECHConfig` values for each server | |||
| name, then each anonymity set has size k = 1. Client-facing servers | name, then each anonymity set has size k = 1. Client-facing servers | |||
| SHOULD deploy ECH in such a way so as to maximize the size of the | SHOULD deploy ECH in such a way so as to maximize the size of the | |||
| anonymity set where possible. This means client-facing servers should | anonymity set where possible. This means client-facing servers should | |||
| use the same ECHConfig for as many server names as possible. An | use the same `ECHConfig` for as many server names as possible. An | |||
| attacker can distinguish two server names that have different | attacker can distinguish two server names that have different | |||
| ECHConfig values based on the ECHClientHello.config_id value. | `ECHConfig` values based on the `ECHClientHello`.`config_id` value. | |||
| This also means public information in a TLS handshake should be | This also means public information in a TLS handshake should be | |||
| consistent across server names. For example, if a client-facing server | consistent across server names. For example, if a client-facing server | |||
| services many backend origin server names, only one of which supports some | services many backend origin server names, only one of which supports some | |||
| cipher suite, it may be possible to identify that server name based on the | cipher suite, it may be possible to identify that server name based on the | |||
| contents of the unencrypted handshake message. Similarly, if a backend | contents of the unencrypted handshake message. Similarly, if a backend | |||
| origin reuses KeyShare values, then that provides a unique identifier | origin reuses KeyShare values, then that provides a unique identifier | |||
| for that server. | for that server. | |||
| Beyond these primary security and privacy goals, ECH also aims to hide, to some | Beyond these primary security and privacy goals, ECH also aims to hide, to some | |||
| skipping to change at line 1598 ¶ | skipping to change at line 1594 ¶ | |||
| the response. However, in the face of an attacker that controls DNS, | the response. However, in the face of an attacker that controls DNS, | |||
| no encryption scheme can work because the attacker can replace the IP | no encryption scheme can work because the attacker can replace the IP | |||
| address, thus blocking client connections, or substitute a unique IP | address, thus blocking client connections, or substitute a unique IP | |||
| address for each DNS name that was looked up. Thus, using DNS records | address for each DNS name that was looked up. Thus, using DNS records | |||
| without additional authentication does not make the situation significantly | without additional authentication does not make the situation significantly | |||
| worse. | worse. | |||
| Clearly, DNSSEC (if the client validates and hard fails) is a defense | Clearly, DNSSEC (if the client validates and hard fails) is a defense | |||
| against this form of attack, but encrypted DNS transport is also a | against this form of attack, but encrypted DNS transport is also a | |||
| defense against DNS attacks by attackers on the local network, which | defense against DNS attacks by attackers on the local network, which | |||
| is a common case where ClientHello and SNI encryption are | is a common case where `ClientHello` and SNI encryption are | |||
| desired. Moreover, as noted in the introduction, SNI encryption is | desired. Moreover, as noted in the introduction, SNI encryption is | |||
| less useful without encryption of DNS queries in transit. | less useful without encryption of DNS queries in transit. | |||
| ## Client Tracking | ## Client Tracking | |||
| A malicious client-facing server could distribute unique, per-client ECHConfig | A malicious client-facing server could distribute unique, per-client `ECHConfig` | |||
| structures as a way of tracking clients across subsequent connections. On-path | structures as a way of tracking clients across subsequent connections. On-path | |||
| adversaries which know about these unique keys could also track clients in this | adversaries which know about these unique keys could also track clients in this | |||
| way by observing TLS connection attempts. | way by observing TLS connection attempts. | |||
| The cost of this type of attack scales linearly with the desired number of | The cost of this type of attack scales linearly with the desired number of | |||
| target clients. Moreover, DNS caching behavior makes targeting individual users | target clients. Moreover, DNS caching behavior makes targeting individual users | |||
| for extended periods of time, e.g., using per-client ECHConfig structures | for extended periods of time, e.g., using per-client `ECHConfig` structures | |||
| delivered via HTTPS RRs with high TTLs, challenging. Clients can help mitigate | delivered via HTTPS RRs with high TTLs, challenging. Clients can help mitigate | |||
| this problem by flushing any DNS or ECHConfig state upon changing networks | this problem by flushing any DNS or `ECHConfig` state upon changing networks | |||
| (this may not be possible if clients use the operating system resolver | (this may not be possible if clients use the operating system resolver | |||
| rather than doing their own resolution). | rather than doing their own resolution). | |||
| ECHConfig rotation rate is also an issue for non-malicious servers, | `ECHConfig` rotation rate is also an issue for non-malicious servers, | |||
| which may want to rotate keys frequently to limit exposure if the key | which may want to rotate keys frequently to limit exposure if the key | |||
| is compromised. Rotating too frequently limits the client anonymity | is compromised. Rotating too frequently limits the client anonymity | |||
| set. In practice, servers which service many server names and thus | set. In practice, servers which service many server names and thus | |||
| have high loads are the best candidates to be client-facing servers | have high loads are the best candidates to be client-facing servers | |||
| and so anonymity sets will typically involve many connections even | and so anonymity sets will typically involve many connections even | |||
| with fairly fast rotation intervals. | with fairly fast rotation intervals. | |||
| ## Ignored Configuration Identifiers and Trial Decryption {#ignored-configs} | ## Ignored Configuration Identifiers and Trial Decryption {#ignored-configs} | |||
| Ignoring configuration identifiers may be useful in scenarios where clients and | Ignoring configuration identifiers may be useful in scenarios where clients and | |||
| client-facing servers do not want to reveal information about the client-facing | client-facing servers do not want to reveal information about the client-facing | |||
| server in the "encrypted_client_hello" extension. In such settings, clients send | server in the "encrypted_client_hello" extension. In such settings, clients send | |||
| a randomly generated config_id in the ECHClientHello. Servers in these settings | a randomly generated `config_id` in the `ECHClientHello`. Servers in these settings | |||
| must perform trial decryption since they cannot identify the client's chosen ECH | must perform trial decryption since they cannot identify the client's chosen ECH | |||
| key using the config_id value. As a result, ignoring configuration | key using the `config_id` value. As a result, ignoring configuration | |||
| identifiers may exacerbate DoS attacks. Specifically, an adversary may send | identifiers may exacerbate DoS attacks. Specifically, an adversary may send | |||
| malicious ClientHello messages, i.e., those which will not decrypt with any | malicious `ClientHello` messages, i.e., those which will not decrypt with any | |||
| known ECH key, in order to force wasteful decryption. Servers that support this | known ECH key, in order to force wasteful decryption. Servers that support this | |||
| feature should, for example, implement some form of rate limiting mechanism to | feature should, for example, implement some form of rate limiting mechanism to | |||
| limit the potential damage caused by such attacks. | limit the potential damage caused by such attacks. | |||
| Unless specified by the application using (D)TLS or externally configured, | Unless specified by the application using (D)TLS or externally configured, | |||
| implementations MUST NOT use this mode. | implementations MUST NOT use this mode. | |||
| ## Outer ClientHello {#outer-clienthello} | ## Outer ClientHello {#outer-clienthello} | |||
| Any information that the client includes in the ClientHelloOuter is visible to | Any information that the client includes in the `ClientHelloOuter` is visible to | |||
| passive observers. The client SHOULD NOT send values in the ClientHelloOuter | passive observers. The client SHOULD NOT send values in the `ClientHelloOuter` | |||
| which would reveal a sensitive ClientHelloInner property, such as the true | which would reveal a sensitive `ClientHelloInner` property, such as the true | |||
| server name. It MAY send values associated with the public name in the | server name. It MAY send values associated with the public name in the | |||
| ClientHelloOuter. | `ClientHelloOuter`. | |||
| In particular, some extensions require the client send a server-name-specific | In particular, some extensions require the client send a server-name-specific | |||
| value in the ClientHello. These values may reveal information about the | value in the `ClientHello`. These values may reveal information about the | |||
| true server name. For example, the "cached_info" ClientHello extension | true server name. For example, the "cached_info" `ClientHello` extension | |||
| {{?RFC7924}} can contain the hash of a previously observed server certificate. | {{?RFC7924}} can contain the hash of a previously observed server certificate. | |||
| The client SHOULD NOT send values associated with the true server name in the | The client SHOULD NOT send values associated with the true server name in the | |||
| ClientHelloOuter. It MAY send such values in the ClientHelloInner. | `ClientHelloOuter`. It MAY send such values in the `ClientHelloInner`. | |||
| A client may also use different preferences in different contexts. For example, | A client may also use different preferences in different contexts. For example, | |||
| it may send different ALPN lists to different servers or in different | it may send different ALPN lists to different servers or in different | |||
| application contexts. A client that treats this context as sensitive SHOULD NOT | application contexts. A client that treats this context as sensitive SHOULD NOT | |||
| send context-specific values in ClientHelloOuter. | send context-specific values in `ClientHelloOuter`. | |||
| Values which are independent of the true server name, or other information the | Values which are independent of the true server name, or other information the | |||
| client wishes to protect, MAY be included in ClientHelloOuter. If they match | client wishes to protect, MAY be included in `ClientHelloOuter`. If they match | |||
| the corresponding ClientHelloInner, they MAY be compressed as described in | the corresponding `ClientHelloInner`, they MAY be compressed as described in | |||
| {{encoding-inner}}. However, note that the payload length reveals information | {{encoding-inner}}. However, note that the payload length reveals information | |||
| about which extensions are compressed, so inner extensions which only sometimes | about which extensions are compressed, so inner extensions which only sometimes | |||
| match the corresponding outer extension SHOULD NOT be compressed. | match the corresponding outer extension SHOULD NOT be compressed. | |||
| Clients MAY include additional extensions in ClientHelloOuter to avoid | Clients MAY include additional extensions in `ClientHelloOuter` to avoid | |||
| signaling unusual behavior to passive observers, provided the choice of value | signaling unusual behavior to passive observers, provided the choice of value | |||
| and value itself are not sensitive. See {{dont-stick-out}}. | and value itself are not sensitive. See {{dont-stick-out}}. | |||
| ## Inner ClientHello {#inner-clienthello} | ## Inner ClientHello {#inner-clienthello} | |||
| Values which depend on the contents of ClientHelloInner, such as the | Values which depend on the contents of `ClientHelloInner`, such as the | |||
| true server name, can influence how client-facing servers process this message. | true server name, can influence how client-facing servers process this message. | |||
| In particular, timing side channels can reveal information about the contents | In particular, timing side channels can reveal information about the contents | |||
| of ClientHelloInner. Implementations should take such side channels into | of `ClientHelloInner`. Implementations should take such side channels into | |||
| consideration when reasoning about the privacy properties that ECH provides. | consideration when reasoning about the privacy properties that ECH provides. | |||
| ## Related Privacy Leaks | ## Related Privacy Leaks | |||
| ECH requires encrypted DNS to be an effective privacy protection mechanism. | ECH requires encrypted DNS to be an effective privacy protection mechanism. | |||
| However, verifying the server's identity from the Certificate message, | However, verifying the server's identity from the Certificate message, | |||
| particularly when using the X509 CertificateType, may result in additional | particularly when using the X509 CertificateType, may result in additional | |||
| network traffic that may reveal the server identity. Examples of this traffic | network traffic that may reveal the server identity. Examples of this traffic | |||
| may include requests for revocation information, such as Online Certificate Status Pr otocol (OCSP) or Certificate Revocation List (CRL) traffic, or requests for repositor y information, such as authorityInformationAccess. It may also include implementation -specific traffic for additional information sources as part of verification. | may include requests for revocation information, such as Online Certificate Status Pr otocol (OCSP) or Certificate Revocation List (CRL) traffic, or requests for repositor y information, such as authorityInformationAccess. It may also include implementation -specific traffic for additional information sources as part of verification. | |||
| skipping to change at line 1707 ¶ | skipping to change at line 1703 ¶ | |||
| Attacks that rely on non-ECH traffic to infer server identity in an ECH | Attacks that rely on non-ECH traffic to infer server identity in an ECH | |||
| connection are out of scope for this document. For example, a client that | connection are out of scope for this document. For example, a client that | |||
| connects to a particular host prior to ECH deployment may later resume a | connects to a particular host prior to ECH deployment may later resume a | |||
| connection to that same host after ECH deployment. An adversary that observes | connection to that same host after ECH deployment. An adversary that observes | |||
| this can deduce that the ECH-enabled connection was made to a host that the | this can deduce that the ECH-enabled connection was made to a host that the | |||
| client previously connected to and which is within the same anonymity set. | client previously connected to and which is within the same anonymity set. | |||
| ## Cookies | ## Cookies | |||
| {{Section 4.2.2 of RFC8446}} defines a cookie value that servers may send in | {{Section 4.2.2 of RFC8446}} defines a cookie value that servers may send in | |||
| HelloRetryRequest for clients to echo in the second ClientHello. While ECH | HelloRetryRequest for clients to echo in the second `ClientHello`. While ECH | |||
| encrypts the cookie in the second ClientHelloInner, the backend server's | encrypts the cookie in the second `ClientHelloInner`, the backend server's | |||
| HelloRetryRequest is unencrypted.This means differences in cookies between | HelloRetryRequest is unencrypted.This means differences in cookies between | |||
| backend servers, such as lengths or cleartext components, may leak information | backend servers, such as lengths or cleartext components, may leak information | |||
| about the server identity. | about the server identity. | |||
| Backend servers in an anonymity set SHOULD NOT reveal information in the cookie | Backend servers in an anonymity set SHOULD NOT reveal information in the cookie | |||
| which identifies the server. This may be done by handling HelloRetryRequest | which identifies the server. This may be done by handling HelloRetryRequest | |||
| statefully, thus not sending cookies, or by using the same cookie construction | statefully, thus not sending cookies, or by using the same cookie construction | |||
| for all backend servers. | for all backend servers. | |||
| Note that, if the cookie includes a key name, analogous to {{Section 4 of | Note that, if the cookie includes a key name, analogous to {{Section 4 of | |||
| ?RFC5077}}, this may leak information if different backend servers issue | ?RFC5077}}, this may leak information if different backend servers issue | |||
| cookies with different key names at the time of the connection. In particular, | cookies with different key names at the time of the connection. In particular, | |||
| if the deployment operates in Split Mode, the backend servers may not share | if the deployment operates in split mode, the backend servers may not share | |||
| cookie encryption keys. Backend servers may mitigate this either by handling | cookie encryption keys. Backend servers may mitigate this either by handling | |||
| key rotation with trial decryption or by coordinating to match key names. | key rotation with trial decryption or by coordinating to match key names. | |||
| ## Attacks Exploiting Acceptance Confirmation | ## Attacks Exploiting Acceptance Confirmation | |||
| To signal acceptance, the backend server overwrites 8 bytes of its | To signal acceptance, the backend server overwrites 8 bytes of its | |||
| ServerHello.random with a value derived from the ClientHelloInner.random. (See | `ServerHello.random` with a value derived from the `ClientHelloInner.random`. (See | |||
| {{backend-server}} for details.) This behavior increases the likelihood of the | {{backend-server}} for details.) This behavior increases the likelihood of the | |||
| ServerHello.random colliding with the ServerHello.random of a previous session, | `ServerHello.random` colliding with the `ServerHello.random` of a previous session, | |||
| potentially reducing the overall security of the protocol. However, the | potentially reducing the overall security of the protocol. However, the | |||
| remaining 24 bytes provide enough entropy to ensure this is not a practical | remaining 24 bytes provide enough entropy to ensure this is not a practical | |||
| avenue of attack. | avenue of attack. | |||
| On the other hand, the probability that two 8-byte strings are the same is | On the other hand, the probability that two 8-byte strings are the same is | |||
| non-negligible. This poses a modest operational risk. Suppose the client-facing | non-negligible. This poses a modest operational risk. Suppose the client-facing | |||
| server terminates the connection (i.e., ECH is rejected or bypassed): if the | server terminates the connection (i.e., ECH is rejected or bypassed): if the | |||
| last 8 bytes of its ServerHello.random coincide with the confirmation signal, | last 8 bytes of its `ServerHello.random` coincide with the confirmation signal, | |||
| then the client will incorrectly presume acceptance and proceed as if the | then the client will incorrectly presume acceptance and proceed as if the | |||
| backend server terminated the connection. However, the probability of a false | backend server terminated the connection. However, the probability of a false | |||
| positive occurring for a given connection is only 1 in 2^64. This value is | positive occurring for a given connection is only 1 in 2^64. This value is | |||
| smaller than the probability of network connection failures in practice. | smaller than the probability of network connection failures in practice. | |||
| Note that the same bytes of the ServerHello.random are used to implement | Note that the same bytes of the `ServerHello.random` are used to implement | |||
| downgrade protection for TLS 1.3 (see {{RFC8446, Section 4.1.3}}). These | downgrade protection for TLS 1.3 (see {{RFC8446, Section 4.1.3}}). These | |||
| mechanisms do not interfere because the backend server only signals ECH | mechanisms do not interfere because the backend server only signals ECH | |||
| acceptance in TLS 1.3 or higher. | acceptance in TLS 1.3 or higher. | |||
| ## Comparison Against Criteria | ## Comparison Against Criteria | |||
| {{?RFC8744}} lists several requirements for SNI encryption. | {{?RFC8744}} lists several requirements for SNI encryption. | |||
| In this section, we reiterate these requirements and assess the ECH design | In this section, we reiterate these requirements and assess the ECH design | |||
| against them. | against them. | |||
| ### Mitigate Cut-and-Paste Attacks | ### Mitigate Cut-and-Paste Attacks | |||
| Since servers process either ClientHelloInner or ClientHelloOuter, and because | Since servers process either `ClientHelloInner` or `ClientHelloOuter`, and because | |||
| ClientHelloInner.random is encrypted, it is not possible for an attacker to "cut | `ClientHelloInner`.random is encrypted, it is not possible for an attacker to "cut | |||
| and paste" the ECH value in a different Client Hello and learn information from | and paste" the ECH value in a different Client Hello and learn information from | |||
| ClientHelloInner. | `ClientHelloInner`. | |||
| ### Avoid Widely Shared Secrets | ### Avoid Widely Shared Secrets | |||
| This design depends upon DNS as a vehicle for semi-static public key | This design depends upon DNS as a vehicle for semi-static public key | |||
| distribution. Server operators may partition their private keys | distribution. Server operators may partition their private keys | |||
| however they see fit provided each server behind an IP address has the | however they see fit provided each server behind an IP address has the | |||
| corresponding private key to decrypt a key. Thus, when one ECH key is | corresponding private key to decrypt a key. Thus, when one ECH key is | |||
| provided, sharing is optimally bound by the number of hosts that share | provided, sharing is optimally bound by the number of hosts that share | |||
| an IP address. Server operators may further limit sharing of private | an IP address. Server operators may further limit sharing of private | |||
| keys by publishing different DNS records containing ECHConfig values | keys by publishing different DNS records containing `ECHConfig` values | |||
| with different public keys using a short TTL. | with different public keys using a short TTL. | |||
| ### SNI-Based Denial-of-Service Attacks | ### SNI-Based Denial-of-Service Attacks | |||
| This design requires servers to decrypt ClientHello messages with ECHClientHello | This design requires servers to decrypt `ClientHello` messages with `ECHClientHello` | |||
| extensions carrying valid digests. Thus, it is possible for an attacker to force | 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 | decryption operations on the server. This attack is bound by the number of valid | |||
| transport connections an attacker can open. | transport connections an attacker can open. | |||
| ### Do Not Stick Out {#dont-stick-out} | ### Do Not Stick Out {#dont-stick-out} | |||
| As a means of reducing the impact of network ossification, {{?RFC8744}} | As a means of reducing the impact of network ossification, {{?RFC8744}} | |||
| recommends SNI-protection mechanisms be designed in such a way that network | recommends SNI-protection mechanisms be designed in such a way that network | |||
| operators do not differentiate connections using the mechanism from connections | operators do not differentiate connections using the mechanism from connections | |||
| not using the mechanism. To that end, ECH is designed to resemble a standard | not using the mechanism. To that end, ECH is designed to resemble a standard | |||
| skipping to change at line 1808 ¶ | skipping to change at line 1804 ¶ | |||
| Ensuring that networks do not differentiate between real ECH and GREASE ECH may | Ensuring that networks do not differentiate between real ECH and GREASE ECH may | |||
| not be feasible for all implementations. While most middleboxes will not treat | not be feasible for all implementations. While most middleboxes will not treat | |||
| them differently, some operators may wish to block real ECH usage but allow | them differently, some operators may wish to block real ECH usage but allow | |||
| GREASE ECH. This specification aims to provide a baseline security level that | GREASE ECH. This specification aims to provide a baseline security level that | |||
| most deployments can achieve easily while providing implementations enough | most deployments can achieve easily while providing implementations enough | |||
| flexibility to achieve stronger security where possible. Minimally, real ECH is | flexibility to achieve stronger security where possible. Minimally, real ECH is | |||
| designed to be indifferentiable from GREASE ECH for passive adversaries with | designed to be indifferentiable from GREASE ECH for passive adversaries with | |||
| following capabilities: | following capabilities: | |||
| 1. The attacker does not know the ECHConfigList used by the server. | 1. The attacker does not know the `ECHConfigList` used by the server. | |||
| 1. The attacker keeps per-connection state only. In particular, it does not | 1. The attacker keeps per-connection state only. In particular, it does not | |||
| track endpoints across connections. | track endpoints across connections. | |||
| Moreover, real ECH and GREASE ECH are designed so that the following features | Moreover, real ECH and GREASE ECH are designed so that the following features | |||
| do not noticeably vary to the attacker, i.e., they are not distinguishers: | do not noticeably vary to the attacker, i.e., they are not distinguishers: | |||
| 1. the code points of extensions negotiated in the clear, and their order; | 1. the code points of extensions negotiated in the clear, and their order; | |||
| 1. the length of messages; and | 1. the length of messages; and | |||
| 1. the values of plaintext alert messages. | 1. the values of plaintext alert messages. | |||
| skipping to change at line 1835 ¶ | skipping to change at line 1831 ¶ | |||
| 1. client authentication, which may depend on ECH acceptance; and | 1. client authentication, which may depend on ECH acceptance; and | |||
| 1. HRR issuance, which may depend on ECH acceptance. | 1. HRR issuance, which may depend on ECH acceptance. | |||
| These can be addressed with more sophisticated implementations, but some | These can be addressed with more sophisticated implementations, but some | |||
| mitigations require coordination between the client and server, and even | mitigations require coordination between the client and server, and even | |||
| across different client and server implementations. These mitigations are | across different client and server implementations. These mitigations are | |||
| out-of-scope for this specification. | out-of-scope for this specification. | |||
| ### Maintain Forward Secrecy | ### Maintain Forward Secrecy | |||
| This design does not provide forward secrecy for the inner ClientHello | This design does not provide forward secrecy for the inner `ClientHello` | |||
| because the server's ECH key is static. However, the window of | because the server's ECH key is static. However, the window of | |||
| exposure is bound by the key lifetime. It is RECOMMENDED that servers | exposure is bound by the key lifetime. It is RECOMMENDED that servers | |||
| rotate keys regularly. | rotate keys regularly. | |||
| ### Enable Multi-party Security Contexts | ### Enable Multi-party Security Contexts | |||
| This design permits servers operating in Split Mode to forward connections | This design permits servers operating in split mode to forward connections | |||
| directly to backend origin servers. The client authenticates the identity of | directly to backend origin servers. The client authenticates the identity of | |||
| the backend origin server, thereby allowing the backend origin server | the backend origin server, thereby allowing the backend origin server | |||
| to hide behind the client-facing server without the client-facing | to hide behind the client-facing server without the client-facing | |||
| server decrypting and reencrypting the connection. | server decrypting and reencrypting the connection. | |||
| Conversely, if the DNS records used for configuration are | Conversely, if the DNS records used for configuration are | |||
| authenticated, e.g., via DNSSEC, | authenticated, e.g., via DNSSEC, | |||
| spoofing a client-facing server operating in Split Mode is not | spoofing a client-facing server operating in split mode is not | |||
| possible. See {{plaintext-dns}} for more details regarding plaintext | possible. See {{plaintext-dns}} for more details regarding plaintext | |||
| DNS. | DNS. | |||
| Authenticating the ECHConfig structure naturally authenticates the included | Authenticating the `ECHConfig` structure naturally authenticates the included | |||
| public name. This also authenticates any retry signals from the client-facing | public name. This also authenticates any retry signals from the client-facing | |||
| server because the client validates the server certificate against the public | server because the client validates the server certificate against the public | |||
| name before retrying. | name before retrying. | |||
| ### Support Multiple Protocols | ### Support Multiple Protocols | |||
| This design has no impact on application layer protocol negotiation. It may | This design has no impact on application layer protocol negotiation. It may | |||
| affect connection routing, server certificate selection, and client certificate | affect connection routing, server certificate selection, and client certificate | |||
| verification. Thus, it is compatible with multiple application and transport | verification. Thus, it is compatible with multiple application and transport | |||
| protocols. By encrypting the entire ClientHello, this design additionally | protocols. By encrypting the entire `ClientHello`, this design additionally | |||
| supports encrypting the ALPN extension. | supports encrypting the ALPN extension. | |||
| ## Padding Policy | ## Padding Policy | |||
| Variations in the length of the ClientHelloInner ciphertext could leak | Variations in the length of the `ClientHelloInner` ciphertext could leak | |||
| information about the corresponding plaintext. {{padding}} describes a | information about the corresponding plaintext. {{padding}} describes a | |||
| RECOMMENDED padding mechanism for clients aimed at reducing potential | RECOMMENDED padding mechanism for clients aimed at reducing potential | |||
| information leakage. | information leakage. | |||
| ## Active Attack Mitigations | ## Active Attack Mitigations | |||
| This section describes the rationale for ECH properties and mechanics as | This section describes the rationale for ECH properties and mechanics as | |||
| defenses against active attacks. In all the attacks below, the attacker is | defenses against active attacks. In all the attacks below, the attacker is | |||
| on-path between the target client and server. The goal of the attacker is to | on-path between the target client and server. The goal of the attacker is to | |||
| learn private information about the inner ClientHello, such as the true SNI | learn private information about the inner `ClientHello`, such as the true SNI | |||
| value. | value. | |||
| ### Client Reaction Attack Mitigation {#flow-client-reaction} | ### Client Reaction Attack Mitigation {#flow-client-reaction} | |||
| This attack uses the client's reaction to an incorrect certificate as an oracle. | This attack uses the client's reaction to an incorrect certificate as an oracle. | |||
| The attacker intercepts a legitimate ClientHello and replies with a ServerHello, | The attacker intercepts a legitimate `ClientHello` and replies with a ServerHello, | |||
| Certificate, CertificateVerify, and Finished messages, wherein the Certificate | Certificate, CertificateVerify, and Finished messages, wherein the Certificate | |||
| message contains a "test" certificate for the domain name it wishes to query. If | message contains a "test" certificate for the domain name it wishes to query. If | |||
| the client decrypted the Certificate and failed verification (or leaked | the client decrypted the Certificate and failed verification (or leaked | |||
| information about its verification process by a timing side channel), the | information about its verification process by a timing side channel), the | |||
| attacker learns that its test certificate name was incorrect. As an example, | attacker learns that its test certificate name was incorrect. As an example, | |||
| suppose the client's SNI value in its inner ClientHello is "example.com," and | suppose the client's SNI value in its inner `ClientHello` is "example.com," and | |||
| the attacker replied with a Certificate for "test.com". If the client produces a | the attacker replied with a Certificate for "test.com". If the client produces a | |||
| verification failure alert because of the mismatch faster than it would due to | verification failure alert because of the mismatch faster than it would due to | |||
| the Certificate signature validation, information about the name leaks. Note | the Certificate signature validation, information about the name leaks. Note | |||
| that the attacker can also withhold the CertificateVerify message. In that | that the attacker can also withhold the CertificateVerify message. In that | |||
| scenario, a client which first verifies the Certificate would then respond | scenario, a client which first verifies the Certificate would then respond | |||
| similarly and leak the same information. | similarly and leak the same information. | |||
| ~~~ | ~~~ | |||
| Client Attacker Server | Client Attacker Server | |||
| ClientHello | ClientHello | |||
| skipping to change at line 1917 ¶ | skipping to change at line 1913 ¶ | |||
| {EncryptedExtensions} | {EncryptedExtensions} | |||
| {CertificateRequest*} | {CertificateRequest*} | |||
| {Certificate*} | {Certificate*} | |||
| {CertificateVerify*} | {CertificateVerify*} | |||
| <------ | <------ | |||
| Alert | Alert | |||
| ------> | ------> | |||
| ~~~ | ~~~ | |||
| {: #flow-diagram-client-reaction title="Client Reaction Attack"} | {: #flow-diagram-client-reaction title="Client Reaction Attack"} | |||
| ClientHelloInner.random prevents this attack. In particular, since the attacker | `ClientHelloInner.random` prevents this attack. In particular, since the attacker | |||
| does not have access to this value, it cannot produce the right transcript and | does not have access to this value, it cannot produce the right transcript and | |||
| handshake keys needed for encrypting the Certificate message. Thus, the client | handshake keys needed for encrypting the Certificate message. Thus, the client | |||
| will fail to decrypt the Certificate and abort the connection. | will fail to decrypt the Certificate and abort the connection. | |||
| ### HelloRetryRequest Hijack Mitigation {#flow-hrr-hijack} | ### HelloRetryRequest Hijack Mitigation {#flow-hrr-hijack} | |||
| This attack aims to exploit server HRR state management to recover information | This attack aims to exploit server HRR state management to recover information | |||
| about a legitimate ClientHello using its own attacker-controlled ClientHello. | about a legitimate `ClientHello` using its own attacker-controlled `ClientHello`. | |||
| To begin, the attacker intercepts and forwards a legitimate ClientHello with an | To begin, the attacker intercepts and forwards a legitimate `ClientHello` with an | |||
| "encrypted_client_hello" (ech) extension to the server, which triggers a | "encrypted_client_hello" (ech) extension to the server, which triggers a | |||
| legitimate HelloRetryRequest in return. Rather than forward the retry to the | legitimate HelloRetryRequest in return. Rather than forward the retry to the | |||
| client, the attacker attempts to generate its own ClientHello in response based | client, the attacker attempts to generate its own `ClientHello` in response based | |||
| on the contents of the first ClientHello and HelloRetryRequest exchange with the | on the contents of the first `ClientHello` and HelloRetryRequest exchange with the | |||
| result that the server encrypts the Certificate to the attacker. If the server | result that the server encrypts the Certificate to the attacker. If the server | |||
| used the SNI from the first ClientHello and the key share from the second | used the SNI from the first `ClientHello` and the key share from the second | |||
| (attacker-controlled) ClientHello, the Certificate produced would leak the | (attacker-controlled) `ClientHello`, the Certificate produced would leak the | |||
| client's chosen SNI to the attacker. | client's chosen SNI to the attacker. | |||
| ~~~ | ~~~ | |||
| Client Attacker Server | Client Attacker Server | |||
| ClientHello | ClientHello | |||
| + key_share | + key_share | |||
| + ech ------> (forward) -------> | + ech ------> (forward) -------> | |||
| HelloRetryRequest | HelloRetryRequest | |||
| + key_share | + key_share | |||
| (intercept) <------- | (intercept) <------- | |||
| skipping to change at line 1960 ¶ | skipping to change at line 1956 ¶ | |||
| {EncryptedExtensions} | {EncryptedExtensions} | |||
| {CertificateRequest*} | {CertificateRequest*} | |||
| {Certificate*} | {Certificate*} | |||
| {CertificateVerify*} | {CertificateVerify*} | |||
| {Finished} | {Finished} | |||
| <------- | <------- | |||
| (process server flight) | (process server flight) | |||
| ~~~ | ~~~ | |||
| {: #flow-diagram-hrr-hijack title="HelloRetryRequest Hijack Attack"} | {: #flow-diagram-hrr-hijack title="HelloRetryRequest Hijack Attack"} | |||
| This attack is mitigated by using the same HPKE context for both ClientHello | This attack is mitigated by using the same HPKE context for both `ClientHello` | |||
| messages. The attacker does not possess the context's keys, so it cannot | messages. The attacker does not possess the context's keys, so it cannot | |||
| generate a valid encryption of the second inner ClientHello. | generate a valid encryption of the second inner `ClientHello`. | |||
| If the attacker could manipulate the second ClientHello, it might be possible | If the attacker could manipulate the second `ClientHello`, it might be possible | |||
| for the server to act as an oracle if it required parameters from the first | for the server to act as an oracle if it required parameters from the first | |||
| ClientHello to match that of the second ClientHello. For example, imagine the | `ClientHello` to match that of the second `ClientHello`. For example, imagine the | |||
| client's original SNI value in the inner ClientHello is "example.com", and the | client's original SNI value in the inner `ClientHello` is "example.com", and the | |||
| attacker's hijacked SNI value in its inner ClientHello is "test.com". A server | attacker's hijacked SNI value in its inner `ClientHello` is "test.com". A server | |||
| which checks these for equality and changes behavior based on the result can be | which checks these for equality and changes behavior based on the result can be | |||
| used as an oracle to learn the client's SNI. | used as an oracle to learn the client's SNI. | |||
| ### ClientHello Malleability Mitigation {#flow-clienthello-malleability} | ### ClientHello Malleability Mitigation {#flow-clienthello-malleability} | |||
| This attack aims to leak information about secret parts of the encrypted | This attack aims to leak information about secret parts of the encrypted | |||
| ClientHello by adding attacker-controlled parameters and observing the server's | `ClientHello` by adding attacker-controlled parameters and observing the server's | |||
| response. In particular, the compression mechanism described in | response. In particular, the compression mechanism described in | |||
| {{encoding-inner}} references parts of a potentially attacker-controlled | {{encoding-inner}} references parts of a potentially attacker-controlled | |||
| ClientHelloOuter to construct ClientHelloInner, or a buggy server may | `ClientHelloOuter` to construct `ClientHelloInner`, or a buggy server may | |||
| incorrectly apply parameters from ClientHelloOuter to the handshake. | incorrectly apply parameters from `ClientHelloOuter` to the handshake. | |||
| To begin, the attacker first interacts with a server to obtain a resumption | To begin, the attacker first interacts with a server to obtain a resumption | |||
| ticket for a given test domain, such as "example.com". Later, upon receipt of a | ticket for a given test domain, such as "example.com". Later, upon receipt of a | |||
| ClientHelloOuter, it modifies it such that the server will process the | `ClientHelloOuter`, it modifies it such that the server will process the | |||
| resumption ticket with ClientHelloInner. If the server only accepts resumption | resumption ticket with `ClientHelloInner`. If the server only accepts resumption | |||
| PSKs that match the server name, it will fail the PSK binder check with an | PSKs that match the server name, it will fail the PSK binder check with an | |||
| alert when ClientHelloInner is for "example.com" but silently ignore the PSK | alert when `ClientHelloInner` is for "example.com" but silently ignore the PSK | |||
| and continue when ClientHelloInner is for any other name. This introduces an | and continue when `ClientHelloInner` is for any other name. This introduces an | |||
| oracle for testing encrypted SNI values. | oracle for testing encrypted SNI values. | |||
| ~~~ | ~~~ | |||
| Client Attacker Server | Client Attacker Server | |||
| handshake and ticket | handshake and ticket | |||
| for "example.com" | for "example.com" | |||
| <--------> | <--------> | |||
| ClientHello | ClientHello | |||
| skipping to change at line 2023 ¶ | skipping to change at line 2019 ¶ | |||
| ... | ... | |||
| Finished | Finished | |||
| <-------- | <-------- | |||
| ~~~ | ~~~ | |||
| {: #tls-clienthello-malleability title="Message Flow for Malleable ClientHello"} | {: #tls-clienthello-malleability title="Message Flow for Malleable ClientHello"} | |||
| This attack may be generalized to any parameter which the server varies by | This attack may be generalized to any parameter which the server varies by | |||
| server name, such as ALPN preferences. | server name, such as ALPN preferences. | |||
| ECH mitigates this attack by only negotiating TLS parameters from | ECH mitigates this attack by only negotiating TLS parameters from | |||
| ClientHelloInner and authenticating all inputs to the ClientHelloInner | `ClientHelloInner` and authenticating all inputs to the `ClientHelloInner` | |||
| (EncodedClientHelloInner and ClientHelloOuter) with the HPKE AEAD. See | (`EncodedClientHelloInner` and `ClientHelloOuter`) with the HPKE AEAD. See | |||
| {{authenticating-outer}}. The decompression process in {{encoding-inner}} | {{authenticating-outer}}. The decompression process in {{encoding-inner}} | |||
| forbids "encrypted_client_hello" in OuterExtensions. This ensures the | forbids "encrypted_client_hello" in OuterExtensions. This ensures the | |||
| unauthenticated portion of ClientHelloOuter is not incorporated into | unauthenticated portion of `ClientHelloOuter` is not incorporated into | |||
| ClientHelloInner. An earlier iteration of this specification only | `ClientHelloInner`. An earlier iteration of this specification only | |||
| encrypted and authenticated the "server_name" extension, which left the overall | encrypted and authenticated the "server_name" extension, which left the overall | |||
| ClientHello vulnerable to an analogue of this attack. | `ClientHello` vulnerable to an analogue of this attack. | |||
| ### ClientHelloInner Packet Amplification Mitigation {#decompression-amp} | ### ClientHelloInner Packet Amplification Mitigation {#decompression-amp} | |||
| Client-facing servers must decompress EncodedClientHelloInners. A malicious | Client-facing servers must decompress EncodedClientHelloInners. A malicious | |||
| attacker may craft a packet which takes excessive resources to decompress | attacker may craft a packet which takes excessive resources to decompress | |||
| or may be much larger than the incoming packet: | or may be much larger than the incoming packet: | |||
| * If looking up a ClientHelloOuter extension takes time linear in the number of | * If looking up a `ClientHelloOuter` extension takes time linear in the number of | |||
| extensions, the overall decoding process would take O(M\*N) time, where | extensions, the overall decoding process would take O(M\*N) time, where | |||
| M is the number of extensions in ClientHelloOuter and N is the | M is the number of extensions in `ClientHelloOuter` and N is the | |||
| size of OuterExtensions. | size of OuterExtensions. | |||
| * If the same ClientHelloOuter extension can be copied multiple times, | * If the same `ClientHelloOuter` extension can be copied multiple times, | |||
| an attacker could cause the client-facing server to construct a large | an attacker could cause the client-facing server to construct a large | |||
| ClientHelloInner by including a large extension in ClientHelloOuter | `ClientHelloInner` by including a large extension in `ClientHelloOuter` | |||
| of length L and an OuterExtensions list referencing N copies of that | of length L and an OuterExtensions list referencing N copies of that | |||
| extension. The client-facing server would then use O(N\*L) memory in | extension. The client-facing server would then use O(N\*L) memory in | |||
| response to O(N+L) bandwidth from the client. In split-mode, an | response to O(N+L) bandwidth from the client. In split mode, an | |||
| O(N\*L)-sized packet would then be transmitted to the | O(N\*L)-sized packet would then be transmitted to the | |||
| backend server. | backend server. | |||
| ECH mitigates this attack by requiring that OuterExtensions be referenced in | ECH mitigates this attack by requiring that OuterExtensions be referenced in | |||
| order, that duplicate references be rejected, and by recommending that | order, that duplicate references be rejected, and by recommending that | |||
| client-facing servers use a linear scan to perform decompression. These | client-facing servers use a linear scan to perform decompression. These | |||
| requirements are detailed in {{encoding-inner}}. | requirements are detailed in {{encoding-inner}}. | |||
| # IANA Considerations | # IANA Considerations | |||
| skipping to change at line 2073 ¶ | skipping to change at line 2069 ¶ | |||
| 1. encrypted_client_hello (0xfe0d), with "TLS 1.3" column values set to | 1. encrypted_client_hello (0xfe0d), with "TLS 1.3" column values set to | |||
| "CH, HRR, EE", "DTLS-Only" column set to "N", and "Recommended" column set | "CH, HRR, EE", "DTLS-Only" column set to "N", and "Recommended" column set | |||
| to "Y". | to "Y". | |||
| 1. ech_outer_extensions (0xfd00), with the "TLS 1.3" column values set to "CH", | 1. ech_outer_extensions (0xfd00), with the "TLS 1.3" column values set to "CH", | |||
| "DTLS-Only" column set to "N", "Recommended" column set to "Y", and the | "DTLS-Only" column set to "N", "Recommended" column set to "Y", and the | |||
| "Comment" column set to "Only appears in inner CH." | "Comment" column set to "Only appears in inner CH." | |||
| ## Update of the TLS Alert Registry {#alerts} | ## Update of the TLS Alert Registry {#alerts} | |||
| IANA has created an entry, ech_required (121) in the existing "TLS Alerts" registry ( | IANA has created an entry, ech_required (121) in the existing "TLS | |||
| defined in {{!RFC8446}}), with the "DTLS-OK" column set to | Alerts" registry (defined in {{!RFC8446}}), with the "DTLS-OK" column | |||
| "Y". | set to "Y". | |||
| ## ECH Configuration Extension Registry {#config-extensions-iana} | ## ECH Configuration Extension Registry {#config-extensions-iana} | |||
| IANA has created a new "TLS ECHConfig Extension" registry in a new | IANA has created a new "TLS ECHConfig Extension" registry in a new | |||
| "TLS Encrypted Client Hello (ECH) Configuration Extensions" registry group. New | "TLS Encrypted Client Hello (ECH) Configuration Extensions" registry group. New | |||
| registrations will list the following attributes: | registrations will list the following attributes: | |||
| Value: | Value: | |||
| : The two-byte identifier for the ECHConfigExtension, i.e., the | : The two-byte identifier for the ECHConfigExtension, i.e., the | |||
| ECHConfigExtensionType | ECHConfigExtensionType | |||
| skipping to change at line 2136 ¶ | skipping to change at line 2133 ¶ | |||
| Extension Name: | Extension Name: | |||
| : RESERVED | : RESERVED | |||
| Recommended: | Recommended: | |||
| : Y | : Y | |||
| Reference: | Reference: | |||
| : RFC 9849 | : RFC 9849 | |||
| Notes: | Notes: | |||
| : Grease entries | : GREASE entries | |||
| {: spacing="compact"} | {: spacing="compact"} | |||
| <!-- [rfced] We note that the following terms use fixed-width font | ||||
| inconsistently. Please review these terms and let us know how we should update | ||||
| or if there are any specific patterns that should be followed (e.g., | ||||
| fixed-width font used for field names, variants, etc.). | ||||
| accept_confirmation | ||||
| cipher_suite | ||||
| ClientHello | ||||
| ClientHelloInner | ||||
| ClientHelloOuter | ||||
| ClientHelloOuterAAD | ||||
| config_id | ||||
| ECHClientHello | ||||
| ECHConfig | ||||
| ECHConfig.contents.public_name | ||||
| ECHConfigContents | ||||
| ECHConfigList | ||||
| EncodedClientHelloInner | ||||
| inner | ||||
| maximum_name_length | ||||
| outer | ||||
| payload | ||||
| public_key | ||||
| ServerHello.random | ||||
| zeros | ||||
| <!-- [rfced] We note that these terms are used inconsistently. Please let us | ||||
| know which form you prefer. | ||||
| split-mode vs. Split Mode | ||||
| GREASE vs. Grease (IANA Section) | ||||
| <!-- [rfced] FYI - We have added expansions for abbreviations upon first use | ||||
| per Section 3.6 of RFC 7322 ("RFC Style Guide"). Please review each | ||||
| expansion in the document carefully to ensure correctness. | ||||
| <!-- [rfced] Please review the "Inclusive Language" portion of the online | ||||
| Style Guide <https://www.rfc-editor.org/styleguide/part2/#inclusive_language> | ||||
| and let us know if any changes are needed. Updates of this nature typically | ||||
| result in more precise language, which is helpful for readers. Note that our | ||||
| script did not flag any words in particular, but this should still be reviewed | ||||
| as a best practice. --> | ||||
| --- back | --- back | |||
| # Linear-Time Outer Extension Processing {#linear-outer-extensions} | # Linear-Time Outer Extension Processing {#linear-outer-extensions} | |||
| The following procedure processes the "ech_outer_extensions" extension (see | The following procedure processes the "ech_outer_extensions" extension (see | |||
| {{encoding-inner}}) in linear time, ensuring that each referenced extension | {{encoding-inner}}) in linear time, ensuring that each referenced extension | |||
| in the ClientHelloOuter is included at most once: | in the `ClientHelloOuter` is included at most once: | |||
| 1. Let I be initialized to zero and N be set to the number of extensions | 1. Let I be initialized to zero and N be set to the number of extensions | |||
| in ClientHelloOuter. | in `ClientHelloOuter`. | |||
| 1. For each extension type, E, in OuterExtensions: | 1. For each extension type, E, in OuterExtensions: | |||
| * If E is "encrypted_client_hello", abort the connection with an | * If E is "encrypted_client_hello", abort the connection with an | |||
| "illegal_parameter" alert and terminate this procedure. | "illegal_parameter" alert and terminate this procedure. | |||
| * While I is less than N and the I-th extension of | * While I is less than N and the I-th extension of | |||
| ClientHelloOuter does not have type E, increment I. | `ClientHelloOuter` does not have type E, increment I. | |||
| * If I is equal to N, abort the connection with an "illegal_parameter" | * If I is equal to N, abort the connection with an "illegal_parameter" | |||
| alert and terminate this procedure. | alert and terminate this procedure. | |||
| * Otherwise, the I-th extension of ClientHelloOuter has type E. Copy | * Otherwise, the I-th extension of `ClientHelloOuter` has type E. Copy | |||
| it to the EncodedClientHelloInner and increment I. | it to the `EncodedClientHelloInner` and increment I. | |||
| # Acknowledgements | # Acknowledgements | |||
| {:numbered="false"} | {:numbered="false"} | |||
| This document draws extensively from ideas in {{?I-D.kazuho-protected-sni}}, but | 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 | is a much more limited mechanism because it depends on the DNS for the | |||
| protection of the ECH key. {{{Richard Barnes}}}, {{{Christian Huitema}}}, {{{Patrick McManus}}}, | protection of the ECH key. {{{Richard Barnes}}}, {{{Christian Huitema}}}, {{{Patrick McManus}}}, | |||
| {{{Matthew Prince}}}, {{{Nick Sullivan}}}, {{{Martin Thomson}}}, and {{{David Benjami n}}} also provided | {{{Matthew Prince}}}, {{{Nick Sullivan}}}, {{{Martin Thomson}}}, and {{{David Benjami n}}} also provided | |||
| important ideas and contributions. | important ideas and contributions. | |||
| End of changes. 228 change blocks. | ||||
| 375 lines changed or deleted | 327 lines changed or added | |||
This html diff was produced by rfcdiff 1.48. | ||||