rfc9849v3.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-12 date: 2026-02
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: Knight-Georgetown Institute org: Independent
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: Apple
email: caw@heapingbits.net email: caw@heapingbits.net
normative: normative:
RFC2119: RFC2119:
RFC7918: RFC7918:
RFC9180: RFC9180:
display: HPKE display: HPKE
informative: informative:
skipping to change at line 198 skipping to change at line 198
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 289 skipping to change at line 289
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 327 skipping to change at line 327
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 397 skipping to change at line 397
`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
skipping to change at line 455 skipping to change at line 455
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 507 skipping to change at line 507
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 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.
skipping to change at line 653 skipping to change at line 653
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.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
skipping to change at line 677 skipping to change at line 677
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
skipping to change at line 751 skipping to change at line 751
- `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 816 skipping to change at line 816
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 M 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, M - 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 M + 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 M-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.
skipping to change at line 1197 skipping to change at line 1197
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 1272 skipping to change at line 1272
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
skipping to change at line 1328 skipping to change at line 1328
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}}).
skipping to change at line 1361 skipping to change at line 1361
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 1895 skipping to change at line 1895
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. 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
skipping to change at line 1932 skipping to change at line 1932
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) <-------
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 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
skipping to change at line 1986 skipping to change at line 1986
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 2012 skipping to change at line 2012
+ 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. 45 change blocks. 
46 lines changed or deleted 46 lines changed or added

This html diff was produced by rfcdiff 1.48.