| rfc9849v5.md | rfc9849.md | |||
|---|---|---|---|---|
| skipping to change at line 135 ¶ | skipping to change at line 135 ¶ | |||
| 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>. | |||
| 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. | OK. | |||
| --> | --> | |||
| <!-- [rfced] Please insert any keywords (beyond those that appear in | ||||
| 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 | |||
| skipping to change at line 199 ¶ | skipping to change at line 196 ¶ | |||
| notation comes from {{RFC8446, Section 3}}. | notation comes from {{RFC8446, Section 3}}. | |||
| # Overview | # Overview | |||
| This protocol is designed to operate in one of two topologies illustrated below, | This protocol is designed to operate in one of two topologies illustrated below, | |||
| which we call "Shared Mode" and "Split Mode". These modes are described in the | which we call "Shared Mode" and "Split Mode". These modes are described in the | |||
| following section. | following section. | |||
| ## Topologies | ## Topologies | |||
| ~~~ | ~~~~ | |||
| +---------------------+ | +---------------------+ | |||
| | | | | | | |||
| | 2001:DB8::1111 | | | 2001:DB8::1111 | | |||
| | | | | | | |||
| 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 | |||
| skipping to change at line 290 ¶ | skipping to change at line 287 ¶ | |||
| 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 | |||
| anonymity set are indistinguishable from one another. Moreover, it should | anonymity set are indistinguishable from one another. Moreover, it should | |||
| achieve this goal without affecting any existing security properties of TLS 1.3. | achieve this goal without affecting any existing security properties of TLS 1.3. | |||
| See {{goals}} for more details about the ECH security and privacy goals. | See {{goals}} for more details about the ECH security and privacy goals. | |||
| # Encrypted ClientHello Configuration {#ech-configuration} | # Encrypted ClientHello Configuration {#ech-configuration} | |||
| ECH uses Hybrid Public Key Encryption (HPKE) for public key encryption {{RFC9180 }}. | ECH uses Hybrid Public Key Encryption (HPKE) for public key encryption {{RFC9180 }}. | |||
| The ECH configuration is defined by the following `ECHConfig` structure. | The ECH configuration is defined by the following `ECHConfig` structure. | |||
| ~~~ | ~~~~ | |||
| opaque HpkePublicKey<1..2^16-1>; | opaque HpkePublicKey<1..2^16-1>; | |||
| uint16 HpkeKemId; // Defined in RFC 9180 | uint16 HpkeKemId; // Defined in RFC 9180 | |||
| uint16 HpkeKdfId; // Defined in RFC 9180 | uint16 HpkeKdfId; // Defined in RFC 9180 | |||
| uint16 HpkeAeadId; // Defined in RFC 9180 | uint16 HpkeAeadId; // Defined in RFC 9180 | |||
| uint16 ECHConfigExtensionType; // Defined in Section 11.3 | uint16 ECHConfigExtensionType; // Defined in Section 11.3 | |||
| struct { | struct { | |||
| HpkeKdfId kdf_id; | HpkeKdfId kdf_id; | |||
| HpkeAeadId aead_id; | HpkeAeadId aead_id; | |||
| } HpkeSymmetricCipherSuite; | } HpkeSymmetricCipherSuite; | |||
| skipping to change at line 328 ¶ | skipping to change at line 325 ¶ | |||
| ECHConfigExtension extensions<0..2^16-1>; | ECHConfigExtension extensions<0..2^16-1>; | |||
| } ECHConfigContents; | } ECHConfigContents; | |||
| struct { | struct { | |||
| uint16 version; | uint16 version; | |||
| uint16 length; | uint16 length; | |||
| select (ECHConfig.version) { | select (ECHConfig.version) { | |||
| case 0xfe0d: ECHConfigContents contents; | case 0xfe0d: ECHConfigContents contents; | |||
| } | } | |||
| } ECHConfig; | } ECHConfig; | |||
| ~~~ | ~~~~ | |||
| The structure contains the following fields: | The structure contains the following fields: | |||
| 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: | |||
| skipping to change at line 398 ¶ | skipping to change at line 395 ¶ | |||
| `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 wi th Associated Data (AEAD) identifier pairs clients can use for encrypting | : The list of HPKE Key Derivation Function (KDF) and Authenticated Encryption wi th Associated 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 | |||
| skipping to change at line 456 ¶ | skipping to change at line 453 ¶ | |||
| 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: | |||
| ~~~ | ~~~~ | |||
| enum { outer(0), inner(1) } ECHClientHelloType; | enum { outer(0), inner(1) } ECHClientHelloType; | |||
| struct { | struct { | |||
| ECHClientHelloType type; | ECHClientHelloType type; | |||
| select (ECHClientHello.type) { | select (ECHClientHello.type) { | |||
| case outer: | case outer: | |||
| HpkeSymmetricCipherSuite cipher_suite; | HpkeSymmetricCipherSuite cipher_suite; | |||
| uint8 config_id; | uint8 config_id; | |||
| opaque enc<0..2^16-1>; | opaque enc<0..2^16-1>; | |||
| 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`. | |||
| skipping to change at line 508 ¶ | skipping to change at line 505 ¶ | |||
| 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]; | |||
| } ECHHelloRetryRequest; | } ECHHelloRetryRequest; | |||
| ~~ | ~~~ | |||
| 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` structure defined in | In DTLS, it uses the `ClientHello` structure defined in | |||
| {{Section 5.3 of RFC9147}}. This does not include the Handshake structure's | {{Section 5.3 of RFC9147}}. This does not include the Handshake structure's | |||
| four-byte header in TLS, nor the twelve-byte header in DTLS. The `zeros` field M UST | four-byte header in TLS, nor the twelve-byte header in DTLS. The `zeros` field M UST | |||
| be all zeros of length `length_of_padding` (see {{padding}}). | be all zeros 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 a list of the removed ExtensionType values. Each value references | OuterExtensions contains a list of 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 MUS T | contiguously in `ClientHelloInner`, and the "ech_outer_extensions" extension MUS T | |||
| 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 `ClientHelloO uter` | example, OuterExtensions may contain extensions A, B, and C, while `ClientHelloO uter` | |||
| contains extensions A, D, B, C, E, and F. | contains extensions A, D, B, C, E, and F. | |||
| skipping to change at line 681 ¶ | skipping to change at line 678 ¶ | |||
| 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.key_config.public_key) | pkR = DeserializePublicKey(ECHConfig.contents.key_config.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 th e | 1. If it compressed any extensions in `EncodedClientHelloInner`, it MUST copy th e | |||
| 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 | |||
| skipping to change at line 755 ¶ | skipping to change at line 752 ¶ | |||
| - `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. | |||
| skipping to change at line 871 ¶ | skipping to change at line 868 ¶ | |||
| has a length other than 8, the client MUST abort the handshake | has a length other than 8, the client MUST abort 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 | ||||
| bytes? If yes, may we update as follows? | ||||
| Current: | ||||
| Otherwise, if it has a length other than 8, the client aborts the | ||||
| handshake with a "decode_error" alert. | ||||
| Perhaps: | ||||
| Otherwise, if it has a length other than 8 bytes, the client aborts | ||||
| 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`. Th at | The client behaves as if it had sent `ClientHelloInner` as the `ClientHello`. Th at | |||
| 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`. | |||
| skipping to change at line 1106 ¶ | skipping to change at line 1092 ¶ | |||
| 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 | ||||
| the sentence below and updated as follows. Please let us know if that is not | ||||
| accurate. | ||||
| Original: | ||||
| Correctly-implemented client will ignore those extensions. | ||||
| Current: | ||||
| Correctly implemented clients will ignore those extensions. | ||||
| ### Server Greasing | ### Server Greasing | |||
| {{config-extensions-iana}} describes a set of Reserved extensions | {{config-extensions-iana}} describes a set of Reserved extensions | |||
| which will never be registered. These can be used by servers to | which will never be registered. These can be used by servers to | |||
| "grease" the contents of the ECH configuration, as inspired by | "grease" the contents of the ECH configuration, as inspired by | |||
| {{?RFC8701}}. This helps ensure clients process ECH extensions | {{?RFC8701}}. This helps ensure clients process ECH extensions | |||
| correctly. When constructing ECH configurations, servers SHOULD | correctly. When constructing ECH configurations, servers SHOULD | |||
| randomly select from reserved values with the high-order bit | randomly select from reserved values with the high-order bit | |||
| clear. Correctly implemented clients will ignore those extensions. | clear. Correctly implemented clients will ignore those extensions. | |||
| skipping to change at line 1211 ¶ | skipping to change at line 1186 ¶ | |||
| 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 | |||
| skipping to change at line 1286 ¶ | skipping to change at line 1261 ¶ | |||
| If the client-facing server accepted ECH, it checks that the second `ClientHello Outer` | If the client-facing server accepted ECH, it checks that the second `ClientHello Outer` | |||
| 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 t he | Finally, it decrypts the new `ECHClientHello.payload` as a second message with t he | |||
| 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 | |||
| skipping to change at line 1317 ¶ | skipping to change at line 1292 ¶ | |||
| "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? | ||||
| Original: | ||||
| The backend server embeds in `ServerHello.random` a string derived from | ||||
| the inner handshake. | ||||
| Perhaps: | ||||
| A string derived from the inner handshake is embedded into | ||||
| `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 inne r | The backend server embeds in `ServerHello.random` a string derived from the inne r | |||
| 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}}). | |||
| skipping to change at line 1375 ¶ | skipping to change at line 1340 ¶ | |||
| 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}}. | |||
| skipping to change at line 1442 ¶ | skipping to change at line 1407 ¶ | |||
| 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 | |||
| <!--[rfced] How may we update this sentence to make it clear whether | ||||
| all the requirements or only some of the requirements require | ||||
| proxies to act as conforming TLS client and server? | ||||
| For background, in general, the RPC recommends using nonrestrictive "which" | ||||
| and restrictive "that". (More details are on | ||||
| https://www.rfc-editor.org/styleguide/tips/) However, edits to that | ||||
| usage have not been made in this document. In this specific sentence, | ||||
| we are asking about the usage because it can affect the understanding | ||||
| of the statement. | ||||
| Original: | ||||
| The requirements in [RFC8446], Section 9.3 which require proxies to | ||||
| act as conforming TLS client and server provide interoperability with | ||||
| TLS-terminating proxies even in cases where the server supports ECH | ||||
| but the proxy does not, as detailed below. | ||||
| Option A (all requirements require it): | ||||
| The requirements in [RFC8446], Section 9.3, which require proxies to | ||||
| act as conforming TLS client and server, provide interoperability with | ||||
| TLS-terminating proxies even in cases where the server supports ECH | ||||
| but the proxy does not, as detailed below. | ||||
| Option B (some requirements require it): | ||||
| The requirements in [RFC8446], Section 9.3 that require proxies to | ||||
| act as conforming TLS client and server provide interoperability with | ||||
| TLS-terminating proxies even in cases where the server supports ECH | ||||
| 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 | |||
| skipping to change at line 1909 ¶ | skipping to change at line 1844 ¶ | |||
| 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 | |||
| + key_share | + key_share | |||
| + ech ------> (intercept) -----> X (drop) | + ech ------> (intercept) -----> X (drop) | |||
| ServerHello | ServerHello | |||
| + key_share | + key_share | |||
| {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: because the attacker | `ClientHelloInner.random` prevents this attack: because 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 | |||
| skipping to change at line 1946 ¶ | skipping to change at line 1881 ¶ | |||
| To begin, the attacker intercepts and forwards a legitimate `ClientHello` with a n | To begin, the attacker intercepts and forwards a legitimate `ClientHello` with a n | |||
| "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 base d | client, the attacker attempts to generate its own `ClientHello` in response base d | |||
| on the contents of the first `ClientHello` and HelloRetryRequest exchange with t he | on the contents of the first `ClientHello` and HelloRetryRequest exchange with t he | |||
| 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) <------- | |||
| ClientHello | ClientHello | |||
| + key_share' | + key_share' | |||
| + ech' -------> | + ech' -------> | |||
| ServerHello | ServerHello | |||
| + key_share | + key_share | |||
| {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 th e | `ClientHello` to match that of the second `ClientHello`. For example, imagine th e | |||
| 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 | |||
| skipping to change at line 2000 ¶ | skipping to change at line 1935 ¶ | |||
| 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 | |||
| + key_share | + key_share | |||
| + ech | + ech | |||
| + ech_outer_extensions(pre_shared_key) | + ech_outer_extensions(pre_shared_key) | |||
| skipping to change at line 2026 ¶ | skipping to change at line 1961 ¶ | |||
| + ech | + ech | |||
| + ech_outer_extensions(pre_shared_key) | + ech_outer_extensions(pre_shared_key) | |||
| + pre_shared_key' | + pre_shared_key' | |||
| --------> | --------> | |||
| Alert | Alert | |||
| -or- | -or- | |||
| ServerHello | ServerHello | |||
| ... | ... | |||
| 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 | |||
| End of changes. 43 change blocks. | ||||
| 101 lines changed or deleted | 38 lines changed or added | |||
This html diff was produced by rfcdiff 1.48. | ||||