With encrypted client hello in use, which host name travels in the ClientHelloOuter and which in the ClientHelloInner?
answer
- Two hellos, one message
- Public name outside, real name inside
- HPKE seals the inner hello
- config_id picks which key to use
- retry_configs when decryption fails
basics
~10 sThe outer hello names the ECHConfig's public_name in its server_name(0) extension; the host the client actually wants sits in the ClientHelloInner, sealed with HPKE inside the encrypted_client_hello(0xfe0d) extension the outer carries.
solid answer
~50 sA client using encrypted client hello sends one message that contains two hellos. The **`ClientHelloOuter`** is an ordinary, complete `ClientHello` whose `server_name(0)` names the `public_name` from the `ECHConfig` it is using — the client-facing server. Inside its `encrypted_client_hello(0xfe0d)` extension it carries `config_id` and `kem_id` identifying which published key config was used, the HPKE encapsulated key, and an encrypted payload: the **`ClientHelloInner`**, whose `server_name(0)` holds the host the client really wants. Only a server holding the private key for that `ECHConfig` can open it. If it can, it either handles the inner hello or forwards it onward; if it cannot, it completes the handshake with the outer hello under `public_name` and may return `retry_configs` so the client can try again with a fresh config. A client that required ECH and did not get it aborts with the `ech_required` alert.
code
pseudocode · 12 linesClientHelloOuter:
server_name(0).host_name = public_name from the ECHConfig
key_share(51), supported_versions(43), cipher suites // a usable hello
encrypted_client_hello(0xfe0d):
config_id = which published config was used
kem_id = HPKE key encapsulation mechanism
enc = HPKE encapsulated key
payload = seal(ClientHelloInner)
ClientHelloInner: // only the config key opens this
server_name(0).host_name = the host the client actually wants
key_share(51), supported_versions(43), cipher suitesgo deeper
Recall that the first hello of a handshake cannot be encrypted, and that this mechanism works by putting a second, sealed hello inside it.
Explain which name sits where: public_name in the outer server_name(0), the real host in the HPKE-sealed inner hello, with config_id and kem_id naming the key that opens it.
Show you know the failure path: an undecryptable inner means the handshake completes under public_name, retry_configs comes back from the server, and a client that required the mechanism sends ech_required.
Weigh the split shape: one client-facing name can front many backend names without holding their certificates, at the price of a published key config that must be rotated and republished.
## Two hellos in one message The first `ClientHello` is the one part of a TLS handshake that cannot itself be encrypted — there are no keys yet. That is why the requested host name, carried in `server_name(0)`, historically travelled in the clear. Encrypted client hello closes that by putting **a second, complete hello inside the first**. - **`ClientHelloOuter`** is a real, well-formed `ClientHello`: its own `key_share(51)`, its own suite list, and a `server_name(0)` naming the `public_name` taken from the `ECHConfig` the client is using. - **`ClientHelloInner`** is the hello the client would have sent without the mechanism, `server_name(0)` and all, sealed with HPKE and carried as the payload of the outer hello's `encrypted_client_hello(0xfe0d)` extension. So the name a reader of the outer hello sees is the client-facing server's published name; the name that actually selects a backend is inside. ## What each hello carries | | `ClientHelloOuter` | `ClientHelloInner` | |---|---|---| | `server_name(0)` | the `ECHConfig`'s `public_name` | the host the client actually wants | | Protection | none — it is the message on the wire | sealed with HPKE under the config's public key | | Purpose | a usable handshake if the inner cannot be opened | the real handshake, once decrypted | | Extension | carries `encrypted_client_hello(0xfe0d)` | is the payload of that extension | The outer must be *usable*, not a decoy shell: if nothing can open the inner, the handshake still has to go somewhere, and it goes to `public_name`. ## Where the configuration comes from A client cannot invent any of this. The server publishes an **`ECHConfigList`**, a list of **`ECHConfig`** entries; each entry's **`ECHConfigContents`** carries the public key and the parameters a client needs, including: 1. **`config_id`** — a short identifier the client echoes so the server knows which of its keys to use, instead of trial-decrypting with every key it holds. 2. **`kem_id`** — which HPKE key encapsulation mechanism the published public key belongs to. 3. **`public_name`** — the name the client puts in the outer hello, and the name the client-facing server can be served a certificate for. 4. **`ECHConfigExtension`** entries — the config's own extension points, which are not TLS hello extensions and not certificate extensions, despite sharing the noun. ## When decryption fails Configs go stale, keys rotate, and a client may be using one the server has retired. The specification does not make that a dead end: - The client-facing server **completes the handshake with the `ClientHelloOuter`**, under `public_name`, because the outer is a complete hello. - It may hand back **`retry_configs`** — current `ECHConfig` entries — so the client can retry the connection with a config that will decrypt. - A client that merely *prefers* the mechanism can carry on with the outer handshake; a client that **requires** it sends the **`ech_required`** alert instead, because the guarantee it wanted did not hold. The important direction here: `retry_configs` is something the **server** offers, and `ech_required` is something the **client** sends. Swapping them is the usual mistake. ## The split shape The mechanism is deliberately written so that the server that holds the ECH key need not be the server that answers: - On **failure to decrypt**, the client-facing server completes the handshake itself with the outer hello. - On **successful decryption**, it can forward the `ClientHelloInner` onward to the server that name selects, and take no further part in the handshake for that name. That split is what lets one front-facing name cover many backend names without the front-facing server holding certificates for all of them. ## Status and version Encrypted client hello applies to **TLS 1.3 only** — it depends on the 1.3 handshake, where everything after `ServerHello` is already encrypted, so the first hello is the last thing left in the clear. At the time of writing it is specified in an IETF draft rather than a published RFC, and its extension codepoint `encrypted_client_hello(0xfe0d)` reflects that. Treat the shape — outer, inner, `config_id`, `retry_configs`, `ech_required` — as stable and the details as still moving; there is no equivalent in TLS 1.2, where the whole certificate flight is in the clear anyway.
- What can a client do when the client-facing server completed the handshake with the ClientHelloOuter?It knows the inner hello was not accepted, because the handshake completed under `public_name`. It can take the `retry_configs` the server offers and reconnect with a config that will decrypt. A client that required the mechanism does not carry on at all — it sends the `ech_required` alert.
- What are config_id and kem_id for?`config_id` is a short identifier for the published `ECHConfig` the client used, so the server picks the right private key instead of trial-decrypting with every key it holds. `kem_id` names the HPKE key encapsulation mechanism that the config's public key belongs to, so both sides agree on how the payload was sealed.
- Why must the ClientHelloOuter be a complete, well-formed ClientHello rather than a placeholder?Because it is the fallback. If the inner hello cannot be decrypted, the client-facing server completes an ordinary handshake with the outer under `public_name`, which requires the outer to carry its own `key_share(51)`, versions and suite list like any other hello.
An envelope inside an envelope: the outer is addressed to the mail room, which opens it and finds an inner envelope addressed to the person who should actually read it.
saying these in an interview costs you the question
- Encrypted client hello encrypts the whole first flight
- The ECHConfig public key is a secret shared privately with clients
- The inner hello is just the outer with the name removed
- If decryption fails the connection simply aborts with no recovery
- The client-facing server learns nothing, since the inner hello stays encrypted end to end