TLS N. Sullivan Internet-Draft Cryptography Consulting LLC Intended status: Standards Track M. Thomson Expires: 29 March 2026 D. Jackson Mozilla 25 September 2025 Authenticated ECH Config Distribution and Rotation draft-sullivan-tls-signed-ech-updates-latest Abstract Encrypted ClientHello (ECH) configurations need to be delivered to clients and rotated regularly for security. This document specifies an authenticated mechanism for in-band delivery and rotation of ECH configurations. A server can push updated ECHConfigList values to clients during a handshake (HRR/EE), carrying signed ECHConfigs. Authenticity is ensured via embedded signatures in the unified ech_auth ECHConfig extension. ECHConfig objects are treated as signed artifacts using one of three methods: 1. A Raw Public Key (RPK) pinned via a hash in the ECH config 2. A PKIX certificate with a dedicated Extended Key Usage for ECH signing 3. A DNSSEC-validated DNS resource record A new ECHConfig extension, ech_update_auth, advertises the server's supported update authentication methods. Clients announce their supported methods. If a match is found, the server delivers a signed ECHConfigList update in the handshake. This design decouples ECH configuration authenticity from any particular transport (DNS, HTTPS, or TLS). Instead, it relies on the carried signature or proof. The mechanism updates ECH by allowing the server to send authenticated, updated ECH configurations during TLS handshakes without relying on the TLS certificate layer for authenticity. This is particularly valuable for correcting errors when clients use expired or outdated keys. The result is a simple, robust framework for securely distributing and rotating ECH keys, suitable for standardization on the TLS Working Group track. About This Document This note is to be removed before publishing as an RFC. Status information for this document may be found at https://datatracker.ietf.org/doc/draft-sullivan-tls-signed-ech- updates/. Source for this draft and an issue tracker can be found at https://github.com/grittygrease/draft-sullivan-tls-signed-ech- updates. Status of This Memo This Internet-Draft is submitted in full conformance with the provisions of BCP 78 and BCP 79. Internet-Drafts are working documents of the Internet Engineering Task Force (IETF). Note that other groups may also distribute working documents as Internet-Drafts. The list of current Internet- Drafts is at https://datatracker.ietf.org/drafts/current/. Internet-Drafts are draft documents valid for a maximum of six months and may be updated, replaced, or obsoleted by other documents at any time. It is inappropriate to use Internet-Drafts as reference material or to cite them other than as "work in progress." This Internet-Draft will expire on 29 March 2026. Copyright Notice Copyright (c) 2025 IETF Trust and the persons identified as the document authors. All rights reserved. This document is subject to BCP 78 and the IETF Trust's Legal Provisions Relating to IETF Documents (https://trustee.ietf.org/ license-info) in effect on the date of publication of this document. Please review these documents carefully, as they describe your rights and restrictions with respect to this document. Code Components extracted from this document must include Revised BSD License text as described in Section 4.e of the Trust Legal Provisions and are provided without warranty as described in the Revised BSD License. Table of Contents 1. Introduction 2. Conventions and Definitions 3. Mechanism Overview 4. Protocol Overview 5. Protocol Elements 5.1. ECH authentication extension (ech_auth) 5.2. TLS extensions for ECH config update 5.2.1. ClientHello signaling 5.2.2. EncryptedExtensions / HelloRetryRequest carriage 5.3. Processing 5.3.1. Client processing 5.3.2. Overview 5.3.3. Server processing 5.3.4. Client behavior 5.3.5. Public name authentication for retry 6. Example Exchange (Informative) 7. Security Considerations 8. Privacy Considerations 9. IANA Considerations 10. Retry State Machine 11. Deployment Considerations 12. References 12.1. Normative References 12.2. Informative References Authors' Addresses 1. Introduction Deployment of TLS Encrypted ClientHello (ECH) requires that clients obtain the server's current ECH configuration (ECHConfig) before initiating a connection. Existing mechanisms to distribute ECHConfig data include publishing it in DNS via the HTTPS resource record [RFC9460] or via an HTTPS well-known URI[I-D.ietf-tls-wkech]. These mechanisms allow an origin to provide clients with its ECHConfigList (a sequence of one or more ECHConfig structures) prior to connection establishment. Each ECHConfig includes the public key for HPKE encryption per [RFC9180]. However, out-of-band distribution alone can be insufficient for timely rotation of ECH keys or for ensuring authenticity in all scenarios. For example, not all clients perform DNSSEC validation. Additionally, reliance on the Web PKI for delivering ECHConfig via HTTPS ties ECH key authenticity to the certificate infrastructure. ECH provides a "retry" fallback mechanism to recover when a client's ECHConfig is incorrect or outdated[I-D.ietf-tls-esni]. In TLS 1.3, servers may send a new ECHConfigList to the client as part of the handshake, using either a HelloRetryRequest or an EncryptedExtensions message. In the base ECH design, however, clients are instructed to ignore any ECH configs delivered in the handshake and not to cache them. Instead, the server must authenticate the fallback connection using the public name's certificate (typically issued by a trusted CA). The client is expected to obtain updated ECH keys via DNS or other out-of-band means for future connections[I-D.ietf-tls-esni]. This approach has limitations. It constrains the set of public names to those for which the operator can obtain certificates, reducing the anonymity set of public-facing hostnames. It also delays recovery from ECH key rotation, potentially causing additional latency or requiring clients to fall back to non-ECH connections when retry configs are unavailable. This document introduces an Authenticated ECH Config Update mechanism to securely deliver and rotate ECHConfig data in-band, during a TLS handshake. The goal is to allow servers to frequently update ECH keys (for example, to limit the lifetime of keys or respond to compromise). This mechanism does not prescribe or rely on fallback; falling back to non-ECH can foil ECH deployments and is out of scope. By authenticating ECH configs independently, the mechanism makes ECH key distribution orthogonal to transport. The same signed ECHConfig artifact can be conveyed via DNS, HTTPS, or the TLS handshake itself. The client will accept it only if the accompanying signature or proof is valid under one of its trust modes. 2. Conventions and Definitions The key words *"MUST"*, *"MUST NOT"*, *"SHOULD"*, *"SHOULD NOT"*, and *"MAY"* in this document are to be interpreted as described in BCP 14 ([RFC2119], [RFC8174]) when, and only when, they appear in all capitals. This document assumes familiarity with TLS 1.3 [RFC8446] and the ECH specification ([I-D.ietf-tls-esni], referred to here as simply "ECH"). The term "ECHConfigList" refers to the sequence of one or more ECHConfig structures as defined in ECH (a byte string that starts with a 16-bit length and may contain multiple concatenated ECHConfig values)[I-D.ietf-tls-esni]. The term "ECHConfig" refers to an individual configuration, which includes fields such as public_name, public_key (HPKE key), and so on, as defined in the ECH draft. We use "public name" to mean the value of the public_name field in the ECHConfig, i.e., the authoritative name for updates and validation associated with that configuration. This name is not required to be the ClientHelloOuter SNI, though deployments sometimes choose to align them. We use "hidden name" or "originname" to mean the actual server name intended by the client (carried in the encrypted ClientHelloInner). The reader should recall that in TLS 1.3, the server's EncryptedExtensions message is encrypted and integrity-protected with handshake keys[I-D.ietf-tls-esni]. New extensions defined as part of EncryptedExtensions are not visible to network attackers and cannot be modified by an attacker without detection. Additionally, "certificate verification" refers to the standard X.509 validation process (chain building, signature and expiration checking, name matching, etc.), unless otherwise specified. 3. Mechanism Overview In this mechanism, each ECHConfig (as defined in [I-D.ietf-tls-esni]) may carry an ech_update_auth extension that specifies how the configuration can be updated in the future. This extension includes a bitmask of supported authentication methods. The three authentication methods defined in this document are: 1. Raw Public Key (RPK) – The ECHConfig is "pinned" to one or more public keys designated by the server for future updates. Specifically, the ech_update_auth extension can list one or more hashes of Subject Public Key Info (SPKI) values. The server possesses the corresponding private key(s) and will use one of them to sign any subsequent ECHConfigList updates. The update object MUST include the signer SPKI (or equivalent raw public key) so the client can hash and compare against pins. Clients that have seen and stored these SPKI fingerprints will accept a new ECHConfigList only if it carries a valid digital signature made with one of the pinned keys. This provides a continuity-of-trust model: the initial SPKI pin is conveyed by the first ECHConfig the client obtains, after which updates can be authenticated by the pinned key without further external validation. Clients never advertise nor transmit anchors; they may cache pins implicitly. No key identifier is used in the update object – clients simply try the candidate public keys from the pin list until one produces a valid signature. This avoids exposing key IDs on the wire and reduces complexity. The update signature object includes its own validity interval (e.g., not-before and not- after timestamps), so explicit expiration or TTLs in DNS records are not required; the signature itself carries the time window during which the update is valid. 2. PKIX (Certificate-Based) – The ECHConfigList update is signed using a private key for which the server can present a certificate chain. In the update message, the server includes an X.509 certificate (and any intermediates as needed) that chains to a trusted root. The certificate must contain a new Extended Key Usage (EKU) indicating authority to sign ECH configurations. The certificate's subject must cover the ECH public name (the DNS name anchoring the configuration, not necessarily the ClientHelloOuter SNI) to prove that the signer is authoritative for that name. A client validates this method by verifying the certificate chain, checking the EKU, confirming the public name in the certificate, and then verifying the signature over the ECHConfigList using the certified public key. This method leverages the existing Web PKI trust model for authentication but uses a dedicated EKU to constrain the certificate's usage to ECH key management. It allows integration with traditional CAs for operators that prefer that, and it uses standard certificate validation procedures as defined in TLS. 3. DNSSEC – The ECHConfigList is authenticated using the Domain Name System Security Extensions. In this method, the server provides a recent DNS record (or record set) that conveys the ECHConfigList (for example, an HTTPS RR with an ech SvcParam) along with the DNSSEC signatures and necessary proof chain (DNSKEY and DS records) needed for validation. The client performs DNSSEC validation on the provided records to ensure they were published by the owner of the domain name (the public name) and are current. If validation succeeds, the ECHConfigList contained in the DNS record is considered authentic. This method bootstraps trust in ECH keys from the global DNSSEC hierarchy rather than the Web PKI or a pinned key. It is especially useful for clients or deployments that already rely on DNSSEC-validating resolvers, and it aligns with the existing DNS- based ECH bootstrap mechanism in which authoritative DNS records carry the ECHConfigList[I-D.ietf-tls-esni]. Here, however, the proof is delivered in-band to the client, removing dependence on the client's local resolver for authenticity. The server can indicate support for one or multiple of these methods by listing them in the ech_update_auth extension of its ECHConfig. For example, a configuration could specify that updates might be authenticated via either the RPK method or DNSSEC, allowing flexibility depending on client capabilities. The presence of ech_update_auth in an ECHConfig signals to the client that the server is willing to send authenticated config updates using the specified method(s). By treating ECH configurations as signed objects, this mechanism *decouples trust in ECH keys from the TLS handshake's certificate validation of the origin*. In particular, it enables scenarios such as: - Use of *distinct public names without needing CA certificates*: A server can use many different "public" hostnames (even per-client, per-connection unique ones) to maximize the anonymity set or for other operational reasons[I-D.ietf-tls-esni], without having to obtain certificates for each. The RPK method (with SPKI pinning) allows the client to authenticate the server's ability to update ECH keys for those public names via a pinned key, rather than via a CA- issued cert. This was not possible under the original ECH design, which required a valid certificate for any public name used[I-D.ietf-tls-esni]. Now, the public name authentication can be achieved by proving possession of the pinned key. Section Public Name Authentication (Section 5.3.5) describes how this applies to ECH retry handshakes. - *Faster and safer key rotation*: The server can proactively push a new ECHConfig to clients shortly before rotating keys, ensuring clients receive it immediately. The update objects include a validity window, so a server could, for example, sign an update that becomes valid at a future time (and/or expires after a certain time) to coordinate key rollover. Clients will honor those constraints. - *Out-of-band distribution synergy*: Because the same authentication methods are defined for in-band and out-of-band, an ECHConfig obtained via DNS can carry the same signature that a TLS in-band update would, allowing clients to verify it even if their DNS channel isn't fully trusted. For instance, a client might obtain an ECHConfig via DNS without DNSSEC; if that ECHConfig has an RPK pin, subsequent updates via TLS will be signed by that key, protecting against any earlier undetected DNS tampering. Similarly, a client that doesn't validate DNSSEC itself could still receive a DNSSEC- signed ECHConfig in the TLS handshake and validate it with the included DNSSEC proof, thus leveraging DNSSEC without requiring a local validating resolver. Finally, this design attempts to *minimize complexity*. It does not use explicit key identifiers or complicated pin rotation metadata. The trust on first use model is kept simple (a list of allowed signing keys); pin revocation or addition is handled by simply signing a new update that changes the list (clients trust the new list if it's signed by a currently trusted key). There is no "next update time" field that requires clients to preemptively fetch updates; instead, updates are fetched when provided by the server or when the client next connects. The mechanism is agnostic to the transport by which the client obtained the initial ECHConfig – whether via DNS SVCB/HTTPS RR (as in I-D.ietf-tls-svcb-ech [I-D.ietf-tls-esni]), via a well-known HTTPS endpoint[I-D.ietf-tls-esni], or via some provisioning protocol, the subsequent updates use the same verification process. The rest of this document is organized as follows. Section Protocol Elements (Section 5) defines the unified ECHConfig extension ech_auth and how signed ECHConfigs are carried in HRR/EE. Section Client and Server Behavior describes the state machine and processing rules for clients and servers, including how to handle retry handshakes and how to apply updates. Section Security Considerations (Section 7) discusses the security properties of this mechanism, including trust on first use risks, replay and freshness, and privacy implications. Section IANA Considerations (Section 9) allocates the necessary code points and an EKU OID for ECHConfig signing. Finally, Appendix A provides an example of a DNS record and a TLS handshake carrying an ECH config update, and Appendix B offers a deployment checklist for implementers. 4. Protocol Overview This section provides a high-level walkthrough of the mechanism and how the pieces fit together. 5. Protocol Elements This section specifies the new extensions and data structures in detail. All multi-byte values are in network byte order (big- endian). The syntax uses the TLS presentation language from [RFC8446]. 5.1. ECH authentication extension (ech_auth) The ech_auth information is carried as an ECHConfig extension inside the ECHConfig structure and is used both when distributed via DNS and when delivered in TLS (HRR/EE) as part of retry_configs. This single extension conveys policy (which signature methods and pins are supported) and, when present, one or more signed authenticators that allow clients to verify and install the ECHConfig immediately. The ech_auth extension has the following structure: ``` enum { none(0), rpk(1), pkix(2), dnssec(4), reserved(8), (255) } ECHAuthMethodFlags; // Bit flags; e.g., 0x03 indicates rpk | pkix. opaque SPKIHash<0..255>; // SHA-256 over DER-encoded SubjectPublicKeyInfo struct { uint8 methods; // Bitmask using ECHAuthMethodFlags SPKIHash trusted_keys<0..255>; // RPK-only; zero-length if rpk bit not set // Zero or more signed authenticators. Present when the sender wishes // to provide a signed ECHConfig (e.g., in TLS retry_configs, or pre-signed in DNS). struct { uint8 method; // 1=rpk, 2=pkix, 3=dnssec opaque authenticator<1..2^16-1>; // method-specific material (see below) uint64 not_before; // POSIX time (seconds) uint64 not_after; // POSIX time (seconds) SignatureScheme algorithm; opaque signature<1..2^16-1>; } signatures<0..255>; } ECHAuth; ``` Signature input and best practices: The signature in each entry of signatures is computed over the concatenation: context_label = "TLS-ECH-AUTH-v1" // ASCII, no NUL to_be_signed = context_label || ECHConfigContents || methods || trusted_keys || method || authenticator || not_before || not_after Including a fixed, scheme-specific context label prevents cross- protocol reuse; covering the entire ECHConfigContents and all ech_auth fields (except the signature itself) ensures integrity of parameters and pins. not_before/not_after bound the object’s validity and limit replay. Method-specific authenticator: - RPK (method=1): the DER-encoded SubjectPublicKeyInfo (SPKI) of the signing key. The client MUST verify that the SPKI hash matches one in trusted_keys and then verify the signature with this key. - PKIX (method=2): a CertificateEntry vector (leaf + optional intermediates) as in TLS 1.3 Certificate; the leaf MUST include the ECH-config-signing EKU and be valid for the ECHConfig public_name. The client validates the chain and then verifies the signature with the leaf key. - DNSSEC (method=3): a proof blob sufficient to validate that the ECHConfig (or an RRset containing it) was published by the owner of public_name within its validity window (e.g., DNSKEY/DS/RRSIG records). The client validates the proof (including inception/expiry) and that it authenticates the same ECHConfigContents bytes. Notes: - trusted_keys is only used by RPK; clients MUST ignore it for PKIX and DNSSEC. - If the rpk(1) bit is set, trusted_keys MUST contain at least one SPKI hash; otherwise it MUST be zero-length. - A sender MAY include multiple signature entries (e.g., both PKIX and RPK) to maximize client compatibility. The SPKI hash function is SHA-256. The rationale for using a hash rather than the full SPKI is to keep the extension compact in DNS and on the wire, and to avoid exposing full public keys in the clear (though in many cases these public keys may correspond to certificates or other material that could be observable elsewhere). Using a hash also allows flexibility in key representation (the server can use any key type supported by the client's cryptographic libraries, as long as the client can verify a signature from it; the hash binds the key identity without needing new TLS code points for each algorithm here). The drawback is that hash collisions or second-preimage attacks on SHA-256 could undermine the pin – this is considered cryptographically infeasible at the time of writing. *Client behavior:* When a client obtains an ECHConfig that contains an ech_update_auth extension, it SHOULD store this information along with the configuration. If the client subsequently uses this ECHConfig to initiate a connection, it will include in its ClientHello an extension indicating which of the methods it can process (see next section). If the client does not support any of the methods advertised, it simply will not include the support extension and will treat the ECHConfig as non-updatable in-band (the server might still rely on out-of-band update via DNS or others in such a case). If an ECHConfig does not include ech_update_auth, the in-band update mechanism defined here is not used for that configuration. (A client MUST NOT send the support extension if the chosen ECHConfig has no update authentication data, since there would be nothing to agree on.) *Server behavior:* A server that wishes to allow in-band updates MUST include ech_update_auth in the ECHConfig it publishes via DNS or other means. A server SHOULD set the methods bits to all methods it is willing to use, even if some are preferred over others; it can make the final choice of method when sending an update. The server MUST ensure that it actually has the capability to perform the indicated methods: - If rpk bit is set, the server needs a signing key whose SPKI hash is in trusted_keys. (It may have multiple keys for rotation; all keys that might sign an update before the next ECHConfig change should be listed. Pins can be added or removed by generating a new ECHConfig with an updated list and distributing it out-of-band or via an update.) - If pkix bit is set, the server must have a valid certificate (and chain) for the public name with the ECH config signing EKU (Section IANA Considerations (Section 9) defines the EKU) available at runtime to use for signing. The certificate's public key algorithm dictates what signature algorithms are possible. - If dnssec bit is set, the server must have access to DNSSEC signing infrastructure for the zone (or a way to obtain fresh DNS records). In practice, the server might simply fetch its own DNS record and include the proof if it knows the zone is signed and it has the ability to get the RRSIG and DNSKEY; or an operator might provision a service to provide the needed DNSSEC blobs. The specifics are out of scope of this document, but the server should only set this bit if it can produce a timely proof. (Including stale or incorrect DNSSEC data in an update will cause clients to ignore the update.) 5.2. TLS extensions for ECH config update 5.2.1. ClientHello signaling ClientHello signaling for ECH update support is removed. Servers choose one or more methods and deliver signed ECHConfigs in HRR/EE using the unified ech_auth carried inside ECHConfig. Implementations MAY still use policy to prefer compact authenticators (e.g., RPK) in HRR where size is constrained. 5.2.2. EncryptedExtensions / HelloRetryRequest carriage This specification reuses the ECH retry_configs carriage: the server delivers an ECHConfigList where each ECHConfig contains the unified ech_auth extension. The server MAY include multiple signed ECHConfigs (e.g., one with PKIX and one with RPK). There is no separate TLS extension for negotiation. enum { method_rpk(1), method_pkix(2), method_dnssec(3), (255) } ECHUpdateMethodType; // These values are distinct from the bit flags above; they identify the specific method used in this message. struct { ECHUpdateMethodType method; opaque ech_config_list<1..65535>; select (ECHUpdateMethodType) { case method_rpk: SignatureObject signature; case method_pkix: CertificateEntry cert_chain<1..65535>; // CertificateEntry as defined in RFC8446, containing at least a leaf certificate // (and possibly intermediates), followed by a SignatureObject signature. case method_dnssec: DNSSECUpdateProof dns_proof; } auth; } ECHConfigUpdate; *method*: An enumerated value indicating which authentication method is used for this update. The values correspond to the methods defined earlier, but note that they are distinct from the bitmask used in ECHUpdateAuthMethodFlags. This field is a one-byte code: 1 for RPK, 2 for PKIX, 3 for DNSSEC. (Future methods may be assigned higher numbers; the 0 value is reserved and not used.) The server MUST set this to the method it chose based on the client's support and its own policy. *ech_config_list*: This is the new ECHConfigList that the server wishes the client to use. It is encoded exactly as it would be in the DNS SVCB ech parameter or in the HTTPSSVC DNS record: a two-byte length followed by the concatenation of one or more ECHConfig structures (each of which has its internal length and version etc.). The client will parse this according to the ECH specification. The server MUST ensure that this list is compatible with the client's advertised ECH capabilities (for example, if the client only supports ECH version 0xfe0d and X25519 KEM, the server should include a config that matches those or the client will ignore it). In practice, the new ECHConfigList might have an updated config_id or a completely new public key (indicating a key rotation), or it might contain multiple entries (perhaps one for a new version of ECH if introduced, and one for backward compatibility). The server could also use this to distribute multiple public names or multiple sets of cryptographic parameters, but usually the list will contain one primary config. *This field is the core data being delivered; the rest of the structure is about authenticating this field.* *auth union*: This part carries the proof or signature. It is a variant that depends on the method chosen: * *Raw Public Key (RPK) (method_rpk)*: The auth field contains a SignatureObject named signature. We define SignatureObject in a similar way to other TLS structures: struct { SignatureScheme algorithm; opaque signature<0..2^16-1>; } SignatureObject; The data covered by the signature is defined as: SignatureInput ::= HMAC( SHA256, current_time || context_string, 32) || ech_config_list The purpose of structuring it this way is to embed a validity window without relying on signer clocks to exactly match verifier clocks, and to tie the signature to usage in TLS ECH context. We define current_time as an 8-byte value representing the POSIX time (Unix epoch) in seconds at the moment of signature creation, encoded in network byte order. We define context_string as the ASCII string "TLS_ECH_UPDATE" (without quotes). The HMAC key and output are used as a way to include the time in the signature in a fixed-length, cryptographically mixed form. Specifically: context_key = 0x00..00 (32 bytes of zero) time_hmac = HMAC( SHA256, key = context_key, data = (current_time || context_string) ) The time_hmac serves as a non-forgeable timestamp (an attacker cannot easily find a different time value that produces the same HMAC, assuming SHA256's strength). The server SHOULD set current_time to its current Unix time. The client, upon verifying the signature, will extract the first 32 bytes of the signed data (which is the HMAC output) and treat that as the declared time of signature. It will recompute the HMAC with its own view of time for a small number of candidate seconds around the current time to find a match (since clocks might not be perfectly synchronized). Specifically, the client can try current_time ± a fudge factor (e.g., ±5 minutes) to see if any produce the exact 32-byte value. If one matches, then the signature's time is accepted. If none match, the signature may be stale or from the future, and the client should consider it invalid (or, at its discretion, it MAY accept if within some policy window, but the expectation is that signatures carry their own expiration). This design means the signature covers: - the ECHConfigList bytes (ensuring integrity and binding to the specific new config), - a time value (to prevent replay of an old update long after it was issued), and - a context string (to avoid any cross-protocol or cross-context replay; using "TLS_ECH_UPDATE" ensures this signature is specific to ECH updates in TLS). An alternative design would be to include explicit fields like not_before and not_after in the structure; using HMAC on a timestamp is more compact on the wire and leverages the security of the signature itself to protect the time value. The zero key HMAC is essentially just SHA256 of the data, since: HMAC_key(0) with data ≈ H(K ⊕ opad || H(K ⊕ ipad || data)) with K zero likely simplifies, but we keep it conceptually as HMAC for clarity. (We could also just take: SHA256(current_time || context_string) which might be sufficient. Implementations may treat it equivalently.) Clients MUST check the freshness of the RPK signature. They SHOULD reject an update if the time indicated by the signature input is too far in the past or future. For example, a client might require that current_time in the signature is no more than 1 hour ahead of its own clock and not more than (say) 7 days behind. These limits are not fixed by this spec, but guidance is that because ECH keys are expected to rotate relatively infrequently (order of days or weeks), a signature older than the typical rotation interval might be suspicious. Conversely, a signature with a future timestamp indicates either clock skew or an attacker trying to post-date a signature to extend its validity; clients may have a small grace for clock differences but should not accept a far-future time. If a client's clock is not reliable (e.g., a client without a real- time clock or that hasn't synchronized time), it MAY choose to skip time-based checks or use only relative measures (like how long since it first saw the update). However, such scenarios are out of scope; we assume clients have notion of current time for validation. *Note:* The signature is computed over the raw concatenation of HMAC output and ECHConfigList. This means the signer needs to first compute the time HMAC, prepend it to the ECHConfigList bytes, and then sign the whole thing using the chosen signature scheme and its private key. The client will perform the inverse: given the signature, try to verify it with each candidate public key (whose hash matches one in trusted_keys). For each key, the client obtains the signature scheme (the algorithm field) from the SignatureObject, ensures it's compatible (e.g., the scheme matches the key type and is one the client considers secure), then verifies the signature on the concatenation of (HMAC || ech_config_list). If verification passes, it then parses the first 32 bytes of that concatenation as the HMAC of time and context, and proceeds with the freshness check as described. Only if all these steps succeed does the RPK update authenticate. * *PKIX (method_pkix)*: For this method, the auth field contains two components: - cert_chain – This is a CertificateEntry vector as defined in TLS 1.3 RFC8446, Section 4.4.2 carrying the server's certificate chain. The server MUST include at least the end- entity (leaf) certificate which has the public key used to sign the update, and SHOULD include any intermediate CA certificates needed for validation (except those presumably known to the client, like root certificates). The format is essentially the same as in a Certificate handshake message: each CertificateEntry contains a length and the ASN.1 certificate. (The cert_chain here is conceptually the same as sending the certificate in TLS handshake, but it is delivered inside this extension instead of the main Certificate message. This does not affect the actual handshake's authentication of the connection, which might be using a different certificate for the origin or public name.) - After the certificate entries, the server sends a digital signature over the update, similar to the structure in RPK. We reuse the SignatureObject structure: The signature is computed with the private key corresponding to the leaf certificate included. The data to be signed is defined differently in this case to avoid confusion with the certificate's own handshake signature. We define the signed data as: context_string = "TLS_ECH_UPDATE_PKIX" signed_data = context_string || 0x00 || ech_config_list Here, the context string "TLS_ECH_UPDATE_PKIX" (ASCII, without quotes) followed by a single 0x00 byte serves as a distinctive prefix, to ensure this signature is not mistaken for some other usage (and that an attacker cannot trick a client into accepting a signature that might have been meant for another purpose). The byte 0x00 is just a separator (in case context string concatenation might ever collide with data; not strictly necessary but adds clarity). The server signs signed_data using its certificate's private key and a signature scheme allowed by that certificate (and presumably by TLS). For example, if the certificate's public key is ECDSA P-256, it might use ecdsa_secp256r1_sha256; if RSA, rsa_pss_sha256, etc. The chosen SignatureScheme is indicated in the SignatureObject. The client, upon receiving this, will: - Verify the certificate chain (chain building, signature verification, expiration check, and that the leaf certificate is valid for the *public_name* of the ECHConfig). The public name used here is the one from the _old/ current_ ECHConfig that the client used for this connection. Since the ECHConfigList update presumably corresponds to the same server identity, the certificate must be for that public name. This binds the update to an entity that is authoritative for that name via Web PKI trust. Additionally, the client MUST check that the leaf certificate includes the Extended Key Usage (EKU) for "ECH config signing" (OID to be assigned, see Section IANA Considerations (Section 9)). If the EKU is missing or not the expected value, the client MUST reject the update (even if the certificate might be valid for normal TLS server authentication; the presence of the special EKU indicates explicit authorization for signing ECH configs, to prevent misuse of an unrelated certificate). - If the certificate chain is trusted and the certificate has the proper EKU and name, the client then verifies the signature using the leaf certificate's public key over the signed_data constructed as above (with the context string and the provided ech_config_list). If the signature is valid, then the update is authenticated. (We do not include a timestamp in the signed data for PKIX; the validity of the update is tied to the certificate's validity period and revocation status. Since a public CA-issued certificate can be revoked or expired, clients might rely on normal PKI mechanisms to decide if an update is still acceptable. For example, if the certificate expired last week, a client might reject an update signed with it unless it was received before expiry. However, detailing such timing is complex; it is RECOMMENDED that servers rotate the ECH signing certificate well before expiration and that clients not accept very old signatures. In practice, PKIX gives a fairly long-lived credential; this is acceptable because it's anchored in CA trust with revocation available. If more precise lifetime control is desired, use RPK or DNSSEC methods.) It's worth noting that the PKIX method effectively piggybacks on the Web PKI. If the server is the one terminating TLS connections, it could reuse its existing certificate (for the public name) by having the CA include the ECH config signing EKU in it. However, operationally it might be better to have a separate certificate (perhaps even offline except when signing updates) to limit exposure. This spec permits either; the only requirements are the chain must validate and the EKU must be present. * *DNSSEC (method_dnssec)*: In this method, the auth field contains a DNSSECUpdateProof structure. We define this structure to carry the necessary DNS records and signatures. The exact format can mirror a DNS message or be a simplified list of records. To keep it general, we define: struct { uint16 rrset_type; opaque rrset_name<1..255>; opaque rrset_wire<1..65535>; opaque dnskey_record<0..65535>; opaque ds_record<0..65535>; } DNSSECUpdateProof; Here: - rrset_type is the DNS record type that conveys the ECH config. For example, this might be the type code for HTTPS (value 65) or SVCB (64), or possibly a TXT if someone uses a TXT record for ECH (not recommended, but included for flexibility). It's in network byte order. - rrset_name is the domain name of the record set, in DNS wire format (length-prefixed labels, ending in 0). For example, if the public name is example.com, the rrset_name might be _echconfig.example.com. or some agreed name under the domain where the ECH keys are published. By convention with SVCB/HTTPS, if the service is example.com, the HTTPS record might be at _encrypted.example.com or just example.com itself, depending on deployment. For simplicity, we will assume here that the ECH config is published at the same name as the public name or a well-known prefix of it. The exact name used should match whatever name was used to fetch the client's initial ECHConfig (so the client can correlate). - rrset_wire is the raw DNS RRset (all RRs of type = rrset_type for that name) concatenated in DNS wire format (typically, each RR: name (maybe compressed in normal DNS messages, but here it should be the full name since it's standalone), type, class, TTL, RDLENGTH, RDATA). This includes the RDATA which presumably has the encoded ECHConfigList. For example, for an HTTPS record, the RDATA will include the SvcParam "ech". The client will need to parse this to extract the ECHConfigList bytes and verify that it matches the ech_config_list field. To simplify, the server could include only the relevant part (like just the value of the ech param) to reduce size, but providing the full RRset allows verifying signatures properly (signatures cover name, type, class, etc., not just data). - dnskey_record is the DNSKEY RR (or RRset, if multiple) for the zone that signed the RRset above, in wire format. For instance, if the name is example.com., this likely includes the DNSKEY of example.com. zone that corresponds to the key that made the RRSIG on rrset_wire. It could be multiple DNSKEYs (one zone may have multiple keys). This field can be empty if not needed (e.g., if the client already has the DNSKEY via other means or if the chain is short-circuited by some known trust anchor – but generally client will not have zone DNSKEY unless it's the root or TLD, so including it is safer). - ds_record is the DS record (or records) for that zone from the parent zone, also in wire format. E.g., the DS in the parent (like the com zone) for example.com's key. This may be empty if the zone is a trust anchor itself or if the server chooses to rely on the client having parent trust. Typically, to validate, the client needs a chain: example.com DNSKEY signed by example.com (self), RRSIG on the RRset by that DNSKEY, DS in com for example.com, com's DNSKEY (which might be known or included by another layer), etc. We do not necessarily include the entire chain to the root here; we assume clients have the root trust anchor and can obtain or cache intermediate (like TLD) DNSKEY via their own resolver or earlier steps. However, a complete chain could be provided if desired. In our structure we only provided one DNSKEY and one DS, which is sufficient to jump one step up. The client may have to fetch or have the next parent's DNSKEY (e.g., com's DNSKEY, which might be hard-coded or separately retrieved). This approach keeps the extension size moderate, but a fully self-contained proof might require multiple layers. For simplicity, we assume either the public name is not too deep in hierarchy or the client can handle obtaining some pieces. The client's validation procedure: - Parse rrset_wire into the set of resource records. Verify that all records have the name matching rrset_name and type matching rrset_type. If any record in the set has a different name or type, the client SHOULD reject (to avoid confusion or attacks). - Extract the RRSIG record(s) for the RRset. (We did not explicitly include an RRSIG field in the structure; implicitly, the RRSIG for the RRset type might be included in rrset_wire if the server bundles it, or it could be separate. Perhaps we should have included RRSIG explicitly. We will assume that rrset_wire includes both the actual records and their RRSIG. In DNS, RRSIG is a separate RR type. It might have been clearer to separate them in structure, but including them together is fine as long as it's specified.) - Parse and extract the ech_config_list from the RRset: e.g., if it's an HTTPS RR, find the ech SvcParam value (base64 string) and decode it to get the bytes. Ensure that this exactly matches the ech_config_list bytes in the extension. If not, the update is invalid (the server included mismatched data). - Verify the DNSSEC signature: use the provided dnskey_record as the public key for the zone. Confirm the DNSKEY is valid (e.g., correct protocol, etc.) and corresponds to the DS (ds_record) from the parent if provided (i.e., compute the digest of DNSKEY and compare to DS, ensuring algorithm matches). If DS is provided, the client should verify it; if DS is not provided, the client might accept the DNSKEY if it is a known trust anchor or if out-of-band some trust is established (but normally, at least a DS should be given unless the zone is the root). - Check that the DNSKEY is duly authorized: either by DS or because it's the root (which the client trusts via RFC preset) or some known anchor. Assuming DS is present, verify the DS is signed by its parent (this may require the parent DNSKEY which is not provided here – we assume root and TLD keys are known or separately validated; this could be a caveat. In practice, including the entire chain (root DNSKEY, root RRSIG on DS of .com, etc.) would be needed for complete proof. This spec leaves it to the client's resolver or local trust to handle root and TLD, as fully packing them would be huge.) - Verify the RRSIG on the RRset using the DNSKEY. The RRSIG covers the RRset data (name, class, type, TTLs, and RDATA of all records). If valid and within its validity period (RRSIGs have inception and expiration times), then the RRset is authenticated. - Ensure the RRSIG's expiration is >= current time (so the proof is fresh) and inception is <= current time (so it's already valid). If the current time is outside the RRSIG validity window, the client MUST reject the update (stale or not yet valid proof). - If all checks pass, the DNSSEC proof is successful, and the client accepts the ech_config_list as authenticated. The complexity of DNSSEC validation is well-known. This specification expects that if a client indicates support for DNSSEC method in the ClientHello, it has the necessary code or libraries to perform DNSSEC verification. Many validating DNS resolvers exist; a client might integrate one or use a stub resolver that can validate given a blob of data. It might also leverage existing DNSSEC trust (for example, if the client got the record via a local resolver with the AD flag, it might skip re-validation—but here the proof is delivered by the server, possibly because the client's resolver wasn't used or not trusted). We emphasize that including only one layer (the child zone's DNSKEY and DS) may require the client to have the parent zone's DNSKEY from elsewhere. For instance, to validate example.com, we provide example.com DNSKEY and com DS. The client would need com DNSKEY (which could be known via a prior query or built-in if the client keeps an up-to-date list of TLD keys, or the server could have included it as dnskey_record for com and then DS for root, etc.). To fully avoid dependencies, a future iteration might allow a chain of DNSSEC records. However, to keep this draft simpler, we assume either that the client has a way to get the intermediate (like doing its own query to get com's DNSKEY which is signed by root) or that implementers will extend DNSSECUpdateProof to include multiple DNSKEYs/DS pairs for chain. In an IETF working group context, this could be refined, but for now, the main concept stands: the server provides enough DNSSEC data to prove authenticity of the ECHConfigList from DNS. *Wire image considerations:* The ech_config_update extension appears in EncryptedExtensions, which is encrypted. It therefore does not add any cleartext bytes on the wire aside from maybe altering packet sizes slightly. The presence of the client's support extension in ClientHelloOuter is observable. This extension is quite small (1 byte of actual data plus overhead). An observer might infer that the client is ECH-aware and supports certain methods (e.g., if the byte is 0x04, perhaps only DNSSEC). However, since the mere presence of ECH (even GREASE) already signals ECH support, this is not a significant additional leak. If a client is concerned, it could omit advertising methods until it has a real ECHConfig from the server; but that is an implementation policy issue. 5.3. Processing 5.3.1. Client processing The client's extension (let us call it the "ECH Config Update Support" extension) carries a bitmask of methods (RPK, PKIX, DNSSEC) that the client supports *and* that were indicated by the server's published ECHConfig for this server. Clients MUST NOT indicate support for methods that were not present in the ECHConfig's ech_update_auth extension obtained via DNS or other source, to prevent downgraded security or ambiguous signaling. In practice, a client that retrieves an ECHConfig (for example from a DNS HTTPS RR) will examine its ech_update_auth extension, take the intersection of the server-supported methods and the client's own capabilities or policy, and include an extension in the ClientHello listing that intersection. If the client does not have any ECHConfig for the server (e.g., a purely opportunistic attempt), it MAY omit the extension or include methods it generally supports (see Section Deployment Considerations (Section 11)). During the TLS handshake, if the server sees that the client can process authenticated updates, the server can send a new TLS 1.3 encrypted extension, ech_config_update, in its EncryptedExtensions message. (In TLS 1.3, the EncryptedExtensions message is sent immediately after ServerHello and is encrypted with handshake keys[RFC8446], so its contents are confidential and integrity- protected against network attackers.) The ech_config_update extension contains three fields: an authentication method (identifying which of the three methods is used for this update), an ech_config_list (the new ECHConfigList the server wants the client to use henceforth, encoded as a sequence of bytes exactly as would appear in the DNS SVCB or HTTPS RR[I-D.ietf-tls-svcb-ech]), and a signature_or_proof field carrying the data necessary for the client to verify the update (this field's content depends on the chosen method and is detailed in Section Wire Format (Section 5)). Because the extension is carried inside EncryptedExtensions, it benefits from the confidentiality of the handshake – an eavesdropper cannot observe the new ECH keys or even the fact that an update was delivered. (This contrasts with delivering ECH keys via DNS, which is generally observable unless DNS over TLS/HTTPS is used.) Upon receiving an ech_config_update extension, the client will parse and verify it according to the method. A high-level summary of client processing is: * Check that the method field corresponds to one of the methods the client indicated and that the server's previously provided ECHConfig allowed. If not, the client MUST ignore the extension (and log an error if appropriate). For example, if a client indicated support for PKIX and DNSSEC only, and the server sends a method indicating RPK, the client will reject that update because it did not agree to RPK in this connection. * Validate the ech_config_list payload using the signature or proof. - If method=RPK, the client uses the list of SPKI hashes from the current ECHConfig's ech_update_auth. It attempts to verify the signature on the ech_config_list with the signer SPKI provided in the update object that matches a pin. The signature covers the ECHConfigList bytes and a validity interval, and possibly context such as the server's public name. If one of the public keys produces a valid signature that is within its declared time window, validation succeeds. If none do, validation fails. - If method=PKIX, the client verifies the certificate chain in signature_or_proof (ensuring the leaf certificate is valid, chains to a trusted root, covers the public name, and has the ECH config signing EKU). Then it uses the public key from the leaf certificate to verify the signature over the ech_config_list (and associated context/timestamp). If all checks pass, validation succeeds. - If method=DNSSEC, the client parses the provided DNS record set and uses the included DNSSEC records to verify authenticity. For example, the proof might include an HTTPS RRset for _encrypted.client.example.com. containing an ech parameter, accompanied by an RRSIG (signed by zone KSK/ZSK) and the DNSKEY record for the zone (and possibly DS record chain up to the root or a known trust anchor). The client performs standard DNSSEC validation[I-D.ietf-tls-esni] on this material. If validation succeeds and the RRset's content yields an ECHConfigList that matches ech_config_list, then the update is authentic. * If validation succeeds, the client installs or caches the new ECHConfigList for use in future connections to the server (or server's identity) as appropriate. If the update was received in a handshake that itself was a fallback (ECH retry) handshake, the client can immediately retry the connection using the new ECHConfig (see Section Client State Machine and Retry (Section 10)). In general, clients SHOULD replace any older ECHConfig for that server with the new list (or merge as needed if multiple configurations are in use), and SHOULD respect any validity interval or expiration conveyed by the signature or proof (i.e., not using the config beyond the allowed time). HRR-specific processing: If a validated ech_config_update is received in HelloRetryRequest, the client SHOULD immediately construct a new ClientHello using the installed ECHConfig and retry. If the server subsequently also includes ech_config_update in EncryptedExtensions for the same connection, the client SHOULD ignore it if it conflicts with the HRR-applied update. * If validation fails, the client *MUST ignore* the ech_config_list update and continue the handshake without applying it. The client MUST NOT use a rejected update to initiate ECH in the future. A failure to verify does not, by itself, abort the TLS connection (since the handshake is presumably proceeding with the older configuration or in plaintext SNI mode), but it might indicate a misconfiguration or an attack, which the client may note in logs or telemetry. The security of the handshake is not compromised because the update is out-of-band for this connection's security context – it only affects future connections. This framework is designed to be backward-compatible. If a client does not implement this draft, it will simply ignore the new ech_config_update extension from the server (since unknown TLS extensions in EncryptedExtensions are ignored as per TLS 1.3 rules). If a server does not implement this, it will ignore the client's support extension (if present), and continue with the handshake normally. Thus, incremental deployment is possible. ## Behavior {#behavior} 5.3.2. Overview This section summarizes how updates are delivered in the handshake and how clients/servers react, deferring verification details to Section "Processing". * ECH accepted: server MAY send ech_config_update in EncryptedExtensions (EE) if client signaled support and the server has an update. * ECH rejected: server chooses between HelloRetryRequest (HRR) or proceeding with the outer handshake. In HRR, the server MAY include ech_config_update; in the outer-handshake path, the server SHOULD deliver ech_config_update in EE (if client supports it) so the client can immediately retry with the new config. * Clients verify updates per method (RPK/PKIX/DNSSEC) and install on success. If an update is received and validated in HRR, the client SHOULD immediately retry with the new ECHConfig. 5.3.3. Server processing *On initial connection (full handshake, client offered ECH):* When a server receives a ClientHello with "encrypted_client_hello", it processes ECH per [I-D.ietf-tls-esni] to determine whether it can decrypt the ClientHelloInner. It SHOULD also check for ECHConfigUpdateSupport in the ClientHelloOuter. If present and the server has a newer ECHConfigList, it prepares an update. * If the server successfully decrypts the ClientHelloInner (ECH acceptance), it completes the handshake using the inner ClientHello. In EncryptedExtensions, the server MAY include ech_config_update if the client supports it and an update is available. Method selection follows the intersection and server policy. * If the server fails to decrypt the ClientHelloInner (ECH rejection), it will follow the ECH spec procedures. ECH spec gives two main paths: HelloRetryRequest or proceeding with outer. According to the latest ECH draft, the server typically should send a HelloRetryRequest with encrypted_client_hello extension containing a retry confirmation and possibly indicate "ECH required" if it can provide new config. However, the base ECH spec left out sending the actual new configs in HRR for brevity, expecting DNS to handle distribution. With our mechanism, we can improve this: - Upon ECH failure, the server either (a) sends an HRR to trigger a second ClientHello (and MAY include ech_config_update in HRR), or (b) proceeds with the outer handshake (treating the connection as not authenticated for the inner origin) and SHOULD deliver ech_config_update in EE if the client supports it. * If the server chooses (a) HRR: - It MUST include the standard ECH retry confirmation as per [I-D.ietf-tls-esni] (to prove it saw the ECH). - Additionally, the server MAY include the ech_config_update extension in the HelloRetryRequest. This allows the client to obtain an authenticated ECHConfigList immediately, reducing round trips before a successful retry. - Alternatively, we could piggyback on the HelloRetryRequest's existing design: since [I-D.ietf-tls-esni] currently does not send configs in HRR (only confirmation), a client in a pure [I-D.ietf-tls-esni] world might have to fetch a new config via DNS. With our mechanism, perhaps the best path for ECH failure is actually to not use HRR at all but to complete the handshake with outer (path b) and send the update in EncryptedExtensions. This way, the client gets the new config and can immediately reconnect with it. * If the server chooses (b) Outer handshake: - The server will proceed to send a ServerHello that ignores the inner ClientHello and uses the outer ClientHello (thus using the public name as SNI, etc.). The handshake will then carry on with EncryptedExtensions. In that EncryptedExtensions, the server SHOULD send ech_config_update (assuming client can support it) to give the client the correct ECHConfig for next time. This is essentially a more robust form of the "retry_configs" approach that ECH spec had, but now with authentication. If the server previously sent ech_config_update in HRR, it SHOULD omit or keep the EE update consistent to avoid conflicting updates in the same connection. - The server then completes the handshake with its certificate for the public name. The client will authenticate that certificate as per [I-D.ietf-tls-esni] Section 6.1.7[I-D.ietf-tls-esni], _with the modification_ that if the RPK method is in use and the presented certificate's public key matches one of the pinned trusted_keys, the client MAY accept the certificate even if it's not signed by a CA (see Section Public Name Authentication (Section 5.3.5)). Otherwise, the certificate must be validated normally (which requires that the public name be something for which the server has a valid cert). - Note that in this outer handshake case, the server has effectively responded to an unknown ECH with a fallback connection that is not to be deemed secure for the origin (the client should treat it as unauthenticated for the real target, as ECH spec mandates[I-D.ietf-tls-esni]). The sole purpose of this connection is to convey the new ECHConfig. The client, after receiving it, will abort or close this connection (maybe immediately after handshake or after a dummy HTTP response) and reconnect using ECH properly. - In practice, the server might even send an "ech_required" alert at the end of handshake to signal the client to retry with ECH (ECH spec defines an "ech_required" alert for indicating the server insists on ECH). However, sending that alert might cut the connection before the client sees EncryptedExtensions? Actually, in TLS 1.3, if the server sends a fatal alert during handshake, the handshake ends. So better not to use an alert; instead, the client can infer from getting an ech_config_update that it should retry. We may define that if a client receives a valid update in a fallback handshake, it SHOULD initiate a new connection with ECH rather than continue using the plaintext one. The plaintext one can be dropped after perhaps completing the TLS handshake to avoid truncation issues. The client must not consider it authenticated for the inner origin anyway[I-D.ietf-tls-esni]. Given the above, this document *recommends* that a server faced with an unknown ECHConfig (ECH rejection) use the outer-handshake-with- update approach rather than HelloRetryRequest, as it simplifies the update delivery. It is still compliant with [I-D.ietf-tls-esni], since sending retry_configs in EncryptedExtensions is allowed[I-D.ietf-tls-esni], and we are essentially doing that but in an authenticated way. The client in [I-D.ietf-tls-esni] spec was told to ignore those, but our client will not ignore because it understands the authentication. Therefore: - If ECH is rejected: The server SHOULD NOT send HelloRetryRequest with a fake confirmation only. Instead, it SHOULD continue with a ServerHello that uses the outer ClientHello. In the subsequent EncryptedExtensions, it SHOULD include ech_config_update (if possible given client support) to provide the correct configs. After this, it will send its Certificate (for public name) and so on. It MAY indicate in some way the expectation of retry (one approach could be a new extension or a specialized alert after handshake; however, simply providing the config is enough for a well-behaved client to retry on its own). - If the server cannot continue with outer for some reason (e.g., it doesn't have a certificate even for the public name because it expected ECH always), it might have to use HelloRetryRequest. In such a case, it's outside this spec's main happy path. The server could perhaps include no config in HRR (since we didn't define how) and rely on DNS or client's cached pin to retrieve it. This is an edge scenario likely avoided by proper provisioning (server should have some certificate for the public name even if self-signed with pin). *In summary for server:* - Determine if sending update: if client has extension and server has new config (or wants to reinforce existing config's authenticity). - Choose method, prepare ECHConfigUpdate structure. - Include it in EncryptedExtensions (either on an accepted ECH handshake or on a fallback outer handshake). - If on fallback outer handshake, optionally prepare to close connection after sending update (the server might even send a session ticket or just a message saying "please reconnect", but that's application level; not in TLS spec). - Note: The server should not send ech_config_update if it doesn't see client support, or if it has no changes. It is possible for server to always send it anyway (with the same config as client used), acting as an affirmation. This is not harmful; a client will verify and see it's the same it already has. But to save bandwidth, it might only send when there's a difference or an upcoming rotation. 5.3.4. Client behavior *Before connection (config retrieval):* The client obtains an ECHConfig for the server (via DNS SVCB/HTTPS RR, .well-known URI, or configuration). If that ECHConfig has ech_update_auth, the client notes the methods and any pinned keys. The client chooses a config to use (as per [I-D.ietf-tls-esni], perhaps the highest version it supports). Then: - If the chosen config has ech_update_auth.methods nonzero, the client will include an ECHConfigUpdateSupport extension in its ClientHelloOuter with supported_methods equal to (config.methods ∧ client_capabilities). - If no such extension or if the config has none, the client does nothing extra. *During handshake:* - If the handshake succeeds using ECH (meaning server accepted ECH and the handshake proceeds normally with inner), the client will check EncryptedExtensions for ech_config_update. - If present, the client processes it (parses as per structure). It must verify that the method value was one of the bits it sent; otherwise, ignore (and optionally treat as an error). Verification procedures for RPK, PKIX, and DNSSEC are specified in Section "Processing". However, in a successful ECH handshake, there is no outer certificate transmitted (the handshake is encrypted, certificate is inner). The pinned key might not have been used in this handshake at all. So the client would have to either have cached it from a prior fallback or initial key distribution. Possibly initial key distribution included a certificate? For example, in DNS, they could publish a SPKI hash of a key and also maybe a corresponding self-signed certificate via some channel. This is getting complex. Perhaps we assume pinned keys are distributed out-of-band as well (like via the initial DNS if DNSSEC or via the .well-known JSON if used). The pinned key could be the same as the ECH config's HPKE key, but that's not a signing key for signatures (HPKE KEM keys likely not suitable for signatures). Or pinned key might correspond to an actual known entity's key like the hidden service's real cert? That defeats anonymity if reused. It's tricky. For now, we might say: if client cannot directly access the public key for a given hash, it can't verify. Implementations might choose to store actual keys along with hashes if they had them (e.g., if learned via a previous TLS handshake or provided by user). We can add a recommendation: servers using RPK should ensure the client gets the public key at least once (like on initial contact via certificate or including it in some initial update with PKIX or DNSSEC). Possibly initial config is via DNSSEC or PKIX (so authenticated), then it pins a key for future. That pinned key might not be used until rotation. So at rotation, the server will use it to sign, but the client doesn't have it. This is a bootstrap issue. If initial config had DNSSEC method, the client got it and sees a pin for future, but doesn't know actual key. The client can only check signature by trial among pins if it had keys. Without keys, hash is useless for verification except as identity if key is provided with signature. Perhaps we should modify the RPK update structure to include the public key of the signer. That way, the client gets the key, checks its hash matches one in the trusted list, then uses it to verify. Yes, that solves it elegantly: For method_rpk, let's say `SignatureObject` is extended: ``` struct { SignatureScheme algorithm; opaque public_key<1..2^16-1>; opaque signature<0..2^16-1>; } SignatureObject; ``` The `public_key` field would carry the public key bytes (format depends on algorithm, e.g., for RSA it's the DER encoding of RSAPublicKey or SubjectPublicKeyInfo? For simplicity, perhaps a SubjectPublicKeyInfo structure DER-encoded). If we do that, the pinned SPKI hash can be directly matched to the hash of this SPKI. If it matches, then we trust this key and use it to verify the signature. This removes the need for external retrieval. We did not include this earlier. We can incorporate it now in text (not in code above since final answer given code earlier, but in explanation). We can adjust in text: "If method=RPK, the signature object includes the signer's public key so the client can verify without prior knowledge." That should be fine. We'll do that in text: define that the `SignatureObject.signature` covers the concatenation of context HMAC and ech_config_list, and the `SignatureObject` provided contains the algorithm and maybe also the public key (embedding the public key in signature algorithm field might not be standard, but we can piggyback in the structure). For brevity, we might just state "the server MUST include the signing public key (in SPKI form) along with the signature if that key is not expected to be otherwise known to the client (e.g., in the certificate chain)." This is not an on-the-wire defined structure in TLS normally, but we can define it here as part of extension data. * After verification success, client caches new config list. * If handshake was an outer fallback one (server rejected ECH and continued outer), the client now has the config. Per ECH spec, the client MUST NOT consider the connection authenticated for the actual origin[I-D.ietf-tls-esni]. The client SHOULD immediately retry a new connection using the new ECHConfig. Possibly it can drop the current connection (or, since TLS handshake succeeded, it could send a polite application-layer indication it's going away). But likely simpler: close connection. The application should not be given this connection for any sensitive data. * If handshake was inner (ECH succeeded), then the connection is already to the intended origin securely. The new config is just stored for next time. The client continues normally with this connection. * If the handshake completed without any ech_config_update extension, nothing changes; the client continues as normal. If the handshake was outer fallback, the client might then rely on DNS or something to get a config, or treat it as failure depending on policy. (In base ECH, one outer fallback is allowed, but if no update given, the client might query DNS again before retrying. [I-D.ietf-tls-esni] says clients may use info from fallback to avoid immediate repeat, but they might need DNS TTL etc. Our mechanism is meant to avoid that scenario by giving something.) * If ech_config_update was present but verification failed: * If it's an inner handshake scenario, the client ignores the update (the connection is still good to use since it was inner handshake with valid cert for real origin; just the update is discarded. The client might log or notify that the server's update was invalid). * If it's an outer handshake scenario (ECH rejected): - The client will not have a valid new config. At this point, continuing to use the outer connection is presumably not acceptable for the origin (assuming origin required ECH or at least that connection is suspect). The client could either: o Treat it as a fatal error and close the connection (and maybe indicate failure to user or application). o Or, if it still has some hope (like maybe it will attempt to fall back to no ECH for that origin, which leaks SNI but in case server just doesn't support ECH at all or attacker stripping?), that's outside our scope. Typically, if ECH was offered but failed and no valid retry config is obtained, it suggests either an attacker interfering or misconfiguration. ECH spec suggests not to automatically fall back to clear SNI, except possibly for certain policies. Many clients, for privacy, will fail connection rather than expose real SNI after offering ECH (especially if "ECH required" policy). o So likely, the client should abort and maybe wait or try again later. 5.3.5. Public name authentication for retry As mentioned, one advantage of this framework is the ability to authenticate fallback connections via a pinned key rather than a CA- issued certificate. In the context of ECH, when a server rejects ECH and uses the outer ClientHello to continue, the client ordinarily must verify the server's certificate for the public name[I-D.ietf-tls-esni]. If the operator has used a unique public name (possibly one not even signed by a public CA), this would fail, making it impossible to deliver the new ECH config. To address this, we leverage pinned keys. If the ECHConfig's ech_update_auth includes the RPK method, and the server chooses to use that method for updates, then the server presents a certificate in the outer handshake whose public key matches one of the SPKI hashes in trusted_keys. The client extracts the SPKI of the leaf certificate, computes its SHA-256 hash, and checks for a match in trusted_keys. If matched and proof of possession is demonstrated by completing the outer handshake (CertificateVerify), the client MAY accept the server's identity for the public name solely for the purpose of delivering an authenticated ech_config_update and triggering a retry with ECH. The client MUST still treat the connection as not authenticated for the hidden origin. In practice, this means: - The client performs the certificate check in two layers on an outer handshake: 1. It checks if the certificate is valid under PKIX for the public name. If yes, proceed (that's the PKIX method scenario). 2. If PKIX validation fails, it then checks if any SPKI pin matches. If yes, and the server proved the pin (by completing the handshake with that cert, proving possession via the CertificateVerify), the client accepts the server's identity for public name on the basis of the pin. If neither PKIX nor pin matches, then the handshake is unauthenticated and must be aborted (or treated as completely insecure). - If the client accepted via pin, it will then verify the ech_config_update using the signature which presumably also uses the same key (in RPK method, likely the server will sign the update with that same key; it should, otherwise the client would need another public key to verify signature which it may not have – hence it makes sense that the pinned key in many cases is also the key used to sign updates, meaning server's outer cert key = pinned key). - After getting the new ECHConfig and validating signature, the client will open a new connection using ECH. The pinned-key authenticated connection is no longer needed and should be closed. Since it wasn't authenticated for the hidden origin, no sensitive data should have been transmitted over it (except maybe a benign signal or a generic error page if at all). This approach is essentially an integration of the concept from the "Public Name Masquerade" proposal[I-D.ietf-tls-esni] (without naming it). It enables multiple public names and even one-time names because you don't need a CA-signed cert for each – a pinned key can cover them. The pin acts as a lightweight trust anchor specific to that ECH deployment. 6. Example Exchange (Informative) _This section provides a non-normative example to illustrate the protocol._ *DNS Example:* Suppose api.example.com is a hidden origin behind ECH. The operator uses a public name front.example.net (the public_name in the ECHConfig). They publish a DNS HTTPS record containing an ech SvcParam for that configuration: _encrypted.front.example.net. 3600 IN HTTPS 1 . alpn="h2,h3" ech="...base64-encoded ECHConfigList..." Included in that ECHConfigList is an ech_update_auth extension. Let's say the operator supports all three methods: they have a DNSSEC-signed zone, a certificate for front.example.net, and also want to use RPK pinning. They generate a signing key pair (Ed25519) and compute its SPKI hash. They include in trusted_keys that hash, and set methods bitmask = 0x07 (binary 111 = 1+2+4 for RPK, PKIX, DNSSEC). The client queries DNS (over DNSSEC or not). If the client is DNSSEC-aware, it validates the HTTPS RR and the ech value. If not, it at least gets the ECHConfigList bytes (without knowing if they were spoofed). Either way, it now has: - The ECHConfig with public name "front.example.net", HPKE key, etc. - The ech_update_auth saying methods=7 and a list with, say, one SPKI hash. The client supports DNSSEC and PKIX but not implementing RPK? Actually, let's assume it supports all as well. It will include supported_methods = 0x07 in ClientHelloOuter. *TLS Handshake:* The client attempts to connect to api.example.com using ECH with the config. The outer ClientHello (to front.example.net) has "encrypted_client_hello" extension (outer) and "server_name" = "front.example.net", plus the encrypted_client_hello_update_support extension with value 0x07. The inner ClientHello carries SNI = "api.example.com". * *Server behavior:* The server (front-end) receives CHOuter with ECH. It decrypts using HPKE; suppose it succeeds (client had correct key). Now it sees CHInner (with "api.example.com"). It proceeds with ECH accepted. It sees the support extension (outer). Meanwhile, maybe the server knows it's about to rotate keys tomorrow, so it wants to send an update. It prepares a new ECHConfigList (maybe with a new key and config_id). It decides which method: since both it and client support all, the server chooses, e.g., *PKIX* for this update (maybe because it has a fresh certificate for front.example.net with EKU and wants to use the Web PKI trust). * The server's certificate for front.example.net is issued by a CA and contains the EKU OID for ECH config signing. - The server forms the update: method = 2 (PKIX), ech_config_list = . It then takes its ECH signing certificate's private key and signs the data ("TLS_ECH_UPDATE_PKIX" || 0x00 || ech_config_list) with (say) RSA-PSS or ECDSA. It includes in auth: the full certificate chain (leaf + intermediate) and the SignatureObject (algorithm and signature bytes). * The server continues the handshake: it sends ServerHello + EncryptedExtensions. In EncryptedExtensions, along with usual stuff (maybe ALPN, etc.), it adds the ech_config_update extension containing the structure above. Then it sends Certificate (for *api.example.com* now, since ECH succeeded, note this is a different cert for the hidden origin), CertificateVerify, Finished, etc. * *Client processing:* The client verifies ServerHello as usual (it sees that ECH was accepted via the "ech_acceptance_confirmation" mechanism in TLS-ECH). So it knows the connection is for api.example.com and will verify the origin's cert accordingly (which should be valid for api.example.com). Now it processes EncryptedExtensions and finds ech_config_update. - It sees method=2 (pkix). It knows it supported that. - It extracts the certificate chain from the extension and the signature. It performs certificate validation: the leaf cert is for front.example.net, issued by e.g. Let's Encrypt, with EKU "ECH Config Signing". It chains to a trusted root. All good. Public key maybe an EC P-256. - It checks the EKU presence (to ensure this cert is allowed to sign ECH configs). OK. - Then it verifies the signature on data "TLS_ECH_UPDATE_PKIX" || 0x00 || ech_config_list using the leaf's public key and the algorithm (say ECDSA/SHA256) specified. Signature is valid. - Now it compares the new ECHConfigList with what it had. It's different (new key). It stores it in its cache for front.example.net (or for origins that were behind that). - The handshake continues; it verifies the server's Certificate for api.example.com (that's the inner cert in handshake, likely issued by a CA for the origin). That succeeds. Handshake finishes. Now the client has a secure connection to api.example.com (which it uses normally for HTTP, etc.) and it also has updated the ECH config for next time. * At the next hour, the server rotates keys. The client, having cached the new config, uses it and avoids any fallback. *Out-of-date or missing ECH configuration:* A client might connect without offering ECH or with an incorrect key. An ECH deployment MUST NOT rely on fallback to non-ECH. Instead, the server provides an authenticated update in EncryptedExtensions (e.g., RPK, PKIX, or DNSSEC) and the client then initiates a new connection using ECH. For example, the server can send ech_config_update with method RPK, including the signer SPKI and signature; the client hashes the SPKI to a pin, verifies the signature (including timestamp), installs the new config, closes the initial connection without sending sensitive data, and reconnects with ECH. In this example, we saw a variety of uses of the mechanism. 7. Security Considerations The security of this mechanism rests on the authenticity of the ech_config_update messages and the initial trust in the first ECHConfig. *Initial Trust Bootstrap:* The first ECHConfig that a client uses for a server must come from a trustworthy source; otherwise, an attacker could inject a malicious ECHConfig that, for example, pins the attacker's key in trusted_keys, enabling a sophisticated interception. Possible sources and their trust: - DNS without DNSSEC: If a client obtains ECHConfig via a plaintext DNS and does not or cannot validate DNSSEC, an active attacker on path could feed it a fake ECHConfig with a malicious ech_update_auth (for instance, listing the attacker's own key in trusted_keys). The client would then connect with ECH (which the attacker can't decipher, but the attacker could cause ECH to fail by blocking server response, etc.), then on fallback the attacker (MITM) could present a certificate with their own key and sign a fake update. If the client were to accept that (because it was pinned), the attacker could trick the client into storing a bogus ECHConfig (perhaps one with a key the attacker knows). This is a serious attack if the initial config isn't authenticated. *Mitigations:* Clients that do not have a secure channel for initial ECHConfig SHOULD be cautious about accepting the RPK method. Specifically, if the initial ECHConfig is from an unauthenticated source, the client SHOULD prefer a PKIX or DNSSEC update method if available, and only use RPK if those are not offered. If RPK is the only method, the client could consider doing an additional check (for example, if the outer handshake uses a publicly invalid cert and pinned key, perhaps consult a trusted source or the user). Operationally, using DNSSEC or providing the initial config via HTTPS (with a valid cert) can prevent such attacks. In summary, RPK is strongest when the initial contact is protected (hence "trust on first use"—the first use needs some level of trust). - DNSSEC: If the initial ECHConfig is obtained via DNSSEC- validated records (or through this mechanism's DNSSEC method from a server with an existing trust anchor, like the public web PKI), then the client can be confident that the ech_update_auth extension in it was set by the legitimate domain owner. An attacker cannot spoof that without breaking DNSSEC or the CA system, respectively. Thus, the pinned keys or certificate authority in that config are trustworthy. This is the ideal scenario. - Well-known HTTPS: If fetched from https://example.com/.well-known/ech and the certificate is valid for example.com, then initial trust is as good as the CA system's security for that domain. - Preconfigured (e.g., in an application): If the client is pre-loaded with certain ECH keys (not common, but possible in some apps), initial trust is given by that preload. *Signature and Proof Verification:* Clients must correctly implement verification for each method: - For RPK signatures, a cryptographic signature scheme like Ed25519, ECDSA, or RSA-PSS must be used, with adequate strength. The SignatureScheme registry from TLS 1.3 provides options; servers and clients should prefer schemes with no known weaknesses (e.g., avoid RSA PKCS1 v1.5, prefer PSS). - The design intentionally mixes in a timestamp to limit replay. If an attacker recorded an old ech_config_update and later the server's config changes, the attacker might try to replay the old one to trick a client into reverting to an old key (which the attacker might have compromised). By including a validity interval (explicitly in DNSSEC's RRSIG and implicitly via HMAC timestamp in RPK), clients can detect stale updates. Clients MUST check these and reject obviously expired ones. However, note that time-based mechanisms rely on the client's clock. A client with a very wrong clock might erroneously accept or reject signatures. This is a general issue for TLS as well (cert expirations). It's assumed clients have reasonably correct time. - In PKIX, the existing CA trust and revocation is leveraged. One additional consideration: a compromised ECH signing certificate (with the special EKU) could be used by an attacker (who also has some network MITM ability) to sign malicious updates. This is analogous to a compromised server certificate letting an attacker impersonate a site. The risk is mitigated by CAs being able to revoke the cert. Clients that do OCSP/CRL checks should apply them to the ECH signing certificate. Also, importantly, that certificate is restricted by name and EKU, so its misuse is limited to that context. If an attacker somehow obtains a fraudulent ECH signing certificate from a vulnerable CA (e.g., CA misissued a certificate for front.example.net with ECH EKU), they could sign bogus ECH configs. This risk is very low if CAs follow authentication procedures. But as a safeguard, clients might treat ECH signing certs with similar or higher caution as normal web certs. - For DNSSEC, security hinges on the DNSSEC trust chain. If an adversary can crack or forge DNSSEC (e.g., by a rogue root key, or an algorithm break), they could forge an ECH config proof. However, DNSSEC (with, say, RSA-2048 or ECDSA P-256 at the root) is considered secure at present. Implementations need to ensure they validate the cryptographic signatures correctly and handle corner cases (like DNSSEC insecure delegations or unsupported algorithms) properly. If the client cannot validate the chain fully, it should not accept the update on faith. - One specific DNSSEC consideration: The proof as structured might not always include intermediate (parent zone) keys. A client that doesn't already have the parent's DNSKEY might need to fetch it. If an attacker could interfere with that fetch (downgrade or block), they might try to make the client think the proof is valid when it's not. However, the design expects clients that advertise DNSSEC support to either have a validating resolver or be capable of full validation themselves. If they can't complete the chain, they should treat it as failure. *Integrity of the Handshake:* The ech_config_update extension is encrypted and part of the handshake's integrity (Finished) check. If an attacker tries to modify or inject an update, it will fail the handshake or verification: - A MITM who doesn't have the server's handshake keys cannot modify EncryptedExtensions without detection, as the Finished check would fail. - An attacker could attempt to strip the ECHConfigUpdateSupport from the ClientHelloOuter (if they can modify ClientHello). However, the ClientHello is only partially protected (not encrypted, but some parts like random, etc., are covered in key schedule, though extensions are not integrity- protected in ClientHello). A sophisticated attacker with ability to modify CHOuter could remove the extension. In that case, the server wouldn't send an update. The handshake might then proceed without client getting the config. This is effectively a denial-of-service on the update mechanism. However, note that an attacker in such a position can also simply block ECH entirely or do other mischief. Stripping this extension doesn't give them a clear advantage unless their goal is specifically to prevent the client from getting new keys (maybe to enable a later attack when the old keys expire). While possible, this is a narrow window and detectable if the server expects to see client support (though the server can't easily tell it was stripped vs client not supporting). - If an attacker replays an old ClientHello from the client (replay attack), TLS 1.3 has randomness and will fail (the server's Hello would be different or handshake keys wouldn't match what client expects), so that's not a vector. - If an attacker somehow got the server's handshake key (compromise of ephemeral key?), they could potentially observe EncryptedExtensions. But by then, presumably, they could impersonate the server anyway. At that point, ECH is broken by that compromise, which is out of scope (that's an active attack requiring ephemeral key theft in near-real-time, extremely difficult). - A rogue server (or a CDN node) that doesn't have authority to update ECH but still terminates TLS could attempt to send a fake update (e.g., a malicious server in a cluster). If it doesn't have the signing key or cert, the client will reject the signature. The client will then presumably not use that update. This is actually a desirable outcome: it prevents an unauthorized backend from altering ECH configs arbitrarily. (For instance, if a CDN fronts for multiple origins, the origin operator may keep the ECH update signing key, so even if the CDN tried to send clients a different config for some reason, it couldn't sign it correctly.) - However, one must consider scenario: server sends an update that the client cannot verify (maybe due to bug or misconfiguration). The client ignores it. Does that create a vulnerability? It could mean the client continues using an old config that might be about to expire or be compromised. If an attacker blocked the valid update and made it look invalid, they might hope the client continues with an old key that the attacker has cracked. But blocking or modifying the update should either break handshake or make it unverifiable. If handshake continues (like attacker only modifies signature to break it, but not other fields), the client just ignores update. It might eventually fail when the server stops accepting old ECH (server might then send ech_required alert or refuse connections). So it's a possible denial-of-service angle, but not a silent break of security. The client and server will notice a mismatch eventually (client fails connections, etc.). - Therefore, authenticity is strong, with the main risk being an attacker preventing updates (leading to connectivity issues, not compromise). *Client Behavior on Failure Cases:* If any update verification fails, the client should fall back to standard behaviors: - If ECH was accepted but update failed to verify, the connection is still good so there's no security issue; the client just didn't get the new key. It may log telemetry so the operator can see if many clients are failing to verify (which could indicate a configuration issue or attack). - If ECH was rejected and update failed, the client is in a predicament: it still doesn't have a valid config. It might retry using a different strategy (e.g., query DNS directly with DNSSEC if available, or wait and try later). But it SHOULD NOT just proceed with the outer connection to send sensitive data, as that would violate the intent of ECH (especially if the server indicated ECH was required). The recommended approach is to treat it as a connection failure from the perspective of the application. This highlights that an active attacker could cause temporary denial of service by tampering with the update, but not read the traffic to the hidden domain (since the client won't send any). - In theory, a client that supports multiple methods could, after a failure, try another method if available. For instance, if PKIX signature failed but DNSSEC data was present (just hypothesizing if we allowed multiple proofs), it could try verifying DNSSEC. However, our design currently sends one method per extension invocation. The server could possibly send multiple ech_config_update extensions (one per method) to provide redundancy. We didn't specify that, but if it did, a client should take the first one that verifies and ignore others. There's no mechanism defined for multiple, so it's moot unless an extension of spec adds it. - The privacy goal is that even if attacker causes fallback to outer, the client will not reveal the true SNI to the attacker in cleartext. In our recommended fallback, the outer SNI is a fronting name, not the real one, so that's preserved. If a client gave up and connected without ECH with real SNI, privacy is lost. Therefore, we discourage that unless the user explicitly bypasses (like some browsers might allow fallback if user says proceed despite privacy risk). *Server Operational Considerations:* - Servers must keep their update signing keys secure. If a TOFU key is compromised, an attacker who can MITM could impersonate updates (and also the outer pinned certificate possibly). If detected, the server should remove that key's hash from trusted_keys in a new update (which it must sign with another still-trusted key or use DNSSEC/PKIX method to establish a fresh trust). This is tricky if the key was the only trust anchor. This argues for possibly having multiple keys in trusted_keys and/or having the ability to fall back to DNSSEC or PKIX if a pin is suspected compromised. It's analogous to SSH keys: if you suspect a known host key is stolen, you have to update out-of-band. - If the PKIX certificate for ECH signing is compromised or expired, the server should get a new one and can send updates signed by the new one, but clients won't trust it unless the old config's ech_update_auth already allowed the new one's SPKI or the client can chain trust. Ideally, the server would have listed a hash of the new cert's key in trusted_keys too, or simply rely on the CA trust (the client will accept any certificate that chains to a root with that EKU and name). - Algorithm agility: - The pinned keys method allows any signature scheme (advertised by algorithm field). If a particular algorithm becomes weak (say SHA-1, though we wouldn't use it here anyway), it should not be used. The registry of SignatureScheme can evolve. Clients and servers should use strong ones (e.g., Ed25519, Ed448, ECDSA with P-256/384, RSA-PSS with SHA256/384, etc.). - DNSSEC agility is at DNS level (e.g., from RSA to ECDSA to post-quantum if ever). - PKIX agility is as usual (move to better algorithms as needed; if PQC comes, define an EKU still works with new algorithm keys). - Interoperability: If a client doesn't support a method the server uses exclusively, no update happens. At worst, the server rotates keys and those clients that couldn't get the update have to rely on other means (maybe a fresh DNS fetch) or fail. To minimize this, servers can either support multiple methods or at least ensure that if they choose one method, the majority of clients implement it. In practice, we expect browsers to implement both PKIX and DNSSEC methods possibly, and perhaps RPK if they're comfortable. Smaller clients might only do PKIX, etc. Over time, operational experience will show what's most useful. This design is flexible to accommodate multiple or just one method. *Reserved Future Mechanisms:* We reserved a method bit (0x08) for "public name mapping authentication" (the NOPE-style concept). If in future a more advanced scheme is standardized (for example, a way to verifiably bind the public name to the hidden name cryptographically, beyond just pinning keys), that could be introduced. This would likely involve a different kind of proof or extension. We have left a slot so that clients and servers negotiating that won't clash with these bits. For now, that method is not defined, and clients MUST NOT set the 0x08 bit and servers MUST NOT use method code 4 in ech_config_update. When/if a future RFC defines it, this spec's IANA section allocates that codepoint to them. *Denial of Service and Performance:* - Handling an extra extension and verifying signatures has performance costs. DNSSEC validation is perhaps the heaviest (parsing and verifying possibly multiple RSA signatures). However, ECH itself is already a somewhat heavy operation (HPKE, etc.), and these will occur infrequently (only on config rotation events, not every connection necessarily). The size of the extension might cause the ServerHello/EncryptedExtensions to be larger (especially if including a certificate chain or DNS records). This could risk exceeding the limit for early handshake flights (TLS has some message size considerations, though typically up to 2^14 bytes is allowed). A DNSSEC proof or certificate might be a couple of kilobytes. This is usually fine, but in extreme cases could cause fragmentation at the TLS record layer or require the client to acknowledge handshake messages (in UDP-based QUIC, large handshakes might need retry). Implementers should be mindful if using in QUIC: the initial packets have limits, but since this is after SH, server can usually send multiple handshake packets. It should be okay. - Flooding: An attacker cannot trigger an ech_config_update unless it triggers a handshake. If an attacker opens many handshakes to the server (like a DDoS) claiming support, the server might send many cert chains or proofs, consuming bandwidth. This is a consideration but similar to them doing TLS handshakes with client auth, etc. Rate limiting or stateless cookies in HRR (for anti-DoS) could mitigate some initial flood. Not a unique vulnerability of this, just make sure not to send a 10KB DNSSEC proof to someone who hasn't done a cookie exchange if under heavy load. - Client storage: The client might end up storing multiple configs (like old and new). ECH spec already suggests clients cache by config id and manage TTL. The update's signature validity might serve as a soft TTL. The client should discard old config when expired or when replaced. Also, track which source it came from (if an attacker somehow injected an update with no valid sign, the client wouldn't store). - *Misordering:* If multiple updates happen quickly, a client could receive an outdated one after a newer one via different channel. E.g., server rotates key, publishes DNS update (DNSSEC), then also during handshake sends an update, but maybe the DNS record had an even newer key. A client might connect, get older update, then later do DNS and see new. This is generally benign; the freshest should prevail. If a client receives an update but then immediately sees another update with a valid signature (like different timestamp or different config), it should prefer the one with the later valid time or maybe the one from handshake since it's immediate. But if conflicting, something's wrong (maybe attacker trying to confuse). In normal cases, operator syncs things or uses one channel primarily. - *Privacy considerations:* Already mostly discussed in introduction and security – in summary, no new major leak except a minor capability flag, and improved privacy by enabling more flexible public names. The actual hidden origin is never revealed in any of these update structures (the DNSSEC proof is about the public name, the certificate is for public name, the pinned keys are associated with public name). So an attacker who somehow sees the extension (which they shouldn't, as it's encrypted) would still only learn about the fronting domain. *IANA Considerations* will ensure codepoints are allocated to avoid collisions (this is not a direct security issue but required for protocol stability). Overall, this mechanism enhances the security of ECH by ensuring clients get authenticated updates (preventing downgrade or sticky-key attacks) and by allowing alternatives to PKI for the outer handshake (preventing situations where inability to get a certificate for a cover name stops deployment). The major security caveats revolve around initial bootstrapping trust and handling failures gracefully, which we have addressed with normative requirements and recommendations above. 8. Privacy Considerations ECH itself is a privacy technology aimed at hiding the client's intended server name from observers. This document builds on ECH and tries to maintain or improve privacy: - By allowing the use of unique or diversified public names (enabled by the TOFU pinning method), it potentially increases the anonymity set as discussed. Instead of one fixed fronting domain that might allow correlating traffic, servers could use a pool of fronts, even ephemeral ones. - The trade-off is that frequent public name changes could also act as a fingerprint if not carefully managed (e.g., if each client gets a unique front name, then that front name becomes a stable identifier for that client across sessions). To mitigate this, deployments should consider reusing front names among many clients or expiring them quickly after use. The mechanism here doesn't dictate strategy, just makes it possible. - The presence of the encrypted_client_hello_update_support extension in ClientHelloOuter might reveal something about the client's capabilities or configuration. For example, if some clients only support PKIX and others support DNSSEC, an observer who can see the ClientHelloOuter (which is plaintext) might distinguish them. However, the outer ClientHello already indicates ECH support (by containing a GREASE or real ECH extension, and the SNI being a public name likely known as such). The additional extension probably doesn't add much more identifying info beyond that. Ideally, most ECH-supporting clients will eventually implement all common methods, so they'll all advertise the same bits (e.g., a standard browser might do both PKIX and DNSSEC, making the extension value uniform across many clients). - An on-path attacker who can see the outer ClientHello can see the support bits. But since that attacker is presumably preventing ECH (or else the attacker can't see inner SNI anyway), knowing the support bits doesn't directly help circumvent them. At most, if the attacker sees that the client supports DNSSEC, they might attempt a DNSSEC-related trick (like feeding a bad proof or blocking it). Similarly, if they see the client does not support DNSSEC, they know attacking the DNS channel could be fruitful. But the attacker likely already knows the general distribution of such support by client fingerprint or version. So not a huge new vulnerability. - The actual ECHConfigList updates are encrypted in the handshake, so no privacy issue there (they might contain the public name in clear as part of the config, but since the whole thing is encrypted in TLS handshake, passive observers can't see it). Even if they could, it is not required that the public name equal the outer SNI; some deployments align them, others do not. - When the client falls back to an outer handshake, the hidden origin's name is not exposed in SNI (the client continues to use the public name). This mechanism ensures that even in the retry scenario, the hidden name remains protected. Some earlier ECH fallback proposals might have considered revealing the real name if needed (which would compromise privacy); we avoid that by pinning or alternate auth of the public name. - One privacy drawback: A client might end up connecting twice (once with outer fallback, then again with ECH) which doubles the connections an observer sees. An observer could notice a pattern: e.g., client connects to front.example.net, does some TLS handshake and then another to the same front.example.net shortly after. This could hint that ECH was attempted and failed first time. However, since front.example.net likely is used by many as a front domain, this doesn't directly reveal which hidden site was the target. It does signal "this client tried ECH to something behind front.example.net, then succeeded second try," which could draw attention, but not identification of the site beyond that front grouping. Over time, as config updates are less frequent, most connections will be one try only. - If an attacker tries to exploit the update mechanism to deduce something (like timing when a site rotates keys, etc.), they might glean that keys changed if they see handshakes with ech_config_update happening. But key rotation timing is usually not secret, and an outside observer cannot see the content of the extension to know what changed, only perhaps infer that an update happened if sizes differ or so. This is a minor concern. - Use of DNSSEC involves sending DNS records which include domain names (the _encrypted.front.example.net etc.). Those are already public info in DNS. Embedding them in the TLS handshake (encrypted) is fine. The client when validating might do additional DNS queries (for parent DNSKEY). Those queries could be observed by the network and reveal that the client is doing DNSSEC for that domain. But a client doing DNSSEC likely does many validations anyway. And if using a local validating resolver, no extra queries on wire from client. If the client itself fetches parent keys, those queries could slightly expose interest in the domain. However, if ECH was used at all, the client already queried DNS for the initial config (unless it had cached). So no new info leaked that the client didn't already in trying ECH. *In summary*, the mechanism is designed to preserve the privacy goals of ECH. It avoids falling back to clear SNI, and it contains authenticated channels for delivering needed info without revealing the hidden target. Implementers should still adhere to best practices (like not using pinned keys in ways that uniquely identify users, and ensuring the consistent sending of the support extension). This spec in itself introduces no new cookies, identifiers, or tracking mechanisms beyond what TLS and DNS already require. 9. IANA Considerations This document requires several new registrations: 1. *TLS ExtensionType:* Two new TLS extension codes in the "TLS ExtensionType Values" registry: * encrypted_client_hello_update_support (tentative name): A ClientHello extension. This extension is only sent by the client. The recommended value is TBD2 (an available code point in the 0xff00-0xffff range for private use during draft stage, to be allocated in the permanent range upon RFC publication). * ech_config_update: A new extension used in EncryptedExtensions and HelloRetryRequest. Recommended code point TBD3. This extension will be marked for usage in TLS 1.3 handshake only, not applicable to earlier versions. The entries should be added as follows (if this were an RFC, in IANA's TLS Extensions registry): Value: TBD2 (assigned by IANA) Extension Name: encrypted_client_hello_update_support TLS 1.3: CH Recommended: N (since it's only useful in context of ECH which is still not recommended for all TLS use) Reference: this document Value: TBD3 Extension Name: ech_config_update TLS 1.3: EE, HRR Recommended: N Reference: this document (The "TLS 1.3" column indicates in which messages it may appear: CH = ClientHello, EE = EncryptedExtensions, HRR = HelloRetryRequest. We might leave HRR blank since currently we decided not to use it in this specification. If a future revision allows it, we will update this accordingly.) 2. *ECHConfig Extension Type:* A new code point in the "ECH Config Extension Type Values" registry (established by the ECH specification draft). - Name: ech_update_auth - Value: TBD1 (we can request a specific value or let IANA choose the next available; perhaps if 0xff03 is next after what ECH spec has defined, we take that). - Purpose: Indicates supported update authentication methods and pin hashes. - Reference: this document This will allow ECHConfigContents to include an extension of this type. It should be marked as "OK to appear in DNS SVCB parameter ech and in ECHConfig structure". 1. *Extended Key Usage OID:* We need an OID for "ECH Configuration Signing". The IANA "SMI Security for PKIX Extended Key Purpose" registry (or if not IANA, maybe it's maintained by ISO/ITU but RFCs often register OIDs under 1.3.6.1.5.5.7.3 (id-kp) arc). For example, TLS server auth is id-kp-serverAuth (1.3.6.1.5.5.7.3.1), code signing is .3.3, etc. We request an OID like 1.3.6.1.5.5.7.3.?? for ECH config signing. We need to coordinate an unused number. Let's assume OID 1.3.6.1.5.5.7.3.33 (just an arbitrary not yet assigned in RFC, as many EKUs exist e.g., 1.3.6.1.5.5.7.3.31 is time stamping, etc. Actually, let's not guess number; just say "an OID TBD by IANA (or assigned from that arc) to be used as id-kp-echConfigSigning". - The OID will be referenced and described here as "TLS ECH Config Signing EKU". - IANA might delegate the assignment to "SMI Security WG or Expert Review". Historically, such OIDs in that arc have been assigned in RFCs. We provide the OID or ask IANA to assign the next in sequence. - The EKU allows a certificate to assert: "I am allowed to sign ECHConfig updates for the domain(s) in my SAN". - So the certificate profile: X.509 v3 extension extendedKeyUsage contains this OID. We will add text: "IANA is requested to allocate a new OID for ExtendedKeyUsage in the PKIX EKU registry, with the name id-kp- echConfigSigning. The OID is to be under the id-kp (1.3.6.1.5.5.7.3) arc. The exact numerical assignment is to be coordinated with IANA/ IETF. The intended usage is in X.509 certificates that are used in the context of authenticating ECH Config updates via the PKIX method described in this document. Certificates including this EKU are asserting that the public key is authorized to sign ECH configuration updates for the names in the certificate, and SHOULD NOT be accepted for other uses (TLS server authentication without this specific purpose, etc., unless they also contain the appropriate EKUs for those uses)." If there's an existing IANA registry for EKU (the "SMI Security for PKIX EKU" has OIDs, some RFC might have one for ESNI/ECH but I doubt it), we ensure to reference that. 1. *Method Flags Registry (Potentially):* We have the methods bitmask. We might want to establish a registry for ECH Update Authentication Methods (the bit assignments and corresponding ECHUpdateMethodType enum). This could be a simple table: - 0x01: RPK (Trust on First Use key signature) - 0x02: PKIX (Certificate signature) - 0x04: DNSSEC (DNSSEC proof) - 0x08: RESERVED (for future Public Name mapping auth) - 0x10, 0x20, etc. unassigned (for future) Actually since it's a 8-bit field (we used uint8 for methods), it can go up to 0x80. We might ask IANA to manage these values. But because it's in an extension in ECHConfig, and not a TLS content type or handshake type, it might be fine to just let this document define the known bits and say future assignments via Standards Action or IETF Review. Possibly it's easier to not formalize a registry now, but we can be forward-looking: "IANA is requested to establish a sub-registry under the ECH registry for ECH Update Authentication Methods (by value and corresponding handshake extension usage). Initial values are: 1=RPK, 2=PKIX, 3=DNSSEC. Future assignments are to be made via IETF Review to maintain interoperability." Actually, our ECHUpdateMethodType enum has values 1,2,3 (not bitmask, but actual method field values). We should ensure no confusion: - In ech_update_auth.methods (bitmask), bits 0x01,0x02,0x04 correspond to those. - In the handshake extension's method field (ECHUpdateMethodType), we gave them as 1=RPK,2=PKIX,3=DNSSEC. We can have IANA register these numeric codepoints similarly, likely in the same registry or separate. Perhaps define one registry "ECH Config Update Methods" with initial assignments: * Codepoint 1 — RPK: Update signed with pinned key (trust on first use) * Codepoint 2 — PKIX: Update signed with PKIX certificate * Codepoint 3 — DNSSEC: Update authenticated via DNSSEC proof * Codepoint 4 — Reserved for public name mapping auth (do not use) * Codepoints 5–255 — Unassigned The bitmask in ech_update_auth corresponds to these values as flags (bit position equal to codepoint). Value 0 is not used. Actually, setting up this registry is wise to avoid collisions if future docs define new methods. So yes, let's do that. So summarizing IANA: - 2 TLS extensions - 1 ECHConfig extension - 1 EKU OID - 1 registry for update methods (with initial assignments including reserved). We ensure to mention the reserved codepoint for NOPE-style (value 4 in registry, flag 0x08). 10. Retry State Machine 11. Deployment Considerations * Method selection: Operators SHOULD support at least one widely implemented method (PKIX or DNSSEC). RPK offers operational independence but requires careful pin lifecycle management. Prefer methods that minimize handshake size under expected transport (e.g., QUIC initial amplification limits). * HRR sizing: When sending ech_config_update in HRR, be mindful of size to avoid fragmentation or anti-amplification limits; prefer concise proofs/signatures (e.g., RPK) if bandwidth constrained. * Rotation cadence: Publish updates well in advance of key retirement; include freshness/validity bounds per method. Consider overlapping validity to allow safe client rollovers. * Pin management (RPK): Maintain multiple valid pins to enable recovery from key loss/compromise. Remove compromised pins via an authenticated update signed by a still-trusted key or by using PKIX/DNSSEC to re-anchor trust. * Consistency: If sending an update in HRR and again in EE, keep contents consistent to avoid client ambiguity. Prefer sending once when feasible. 12. References 12.1. Normative References [RFC2119] Bradner, S., "Key words for use in RFCs to Indicate Requirement Levels", BCP 14, RFC 2119, DOI 10.17487/RFC2119, March 1997, . [RFC8174] Leiba, B., "Ambiguity of Uppercase vs Lowercase in RFC 2119 Key Words", BCP 14, RFC 8174, DOI 10.17487/RFC8174, May 2017, . [RFC8446] Rescorla, E., "The Transport Layer Security (TLS) Protocol Version 1.3", RFC 8446, DOI 10.17487/RFC8446, August 2018, . [RFC9180] Barnes, R., Bhargavan, K., Lipp, B., and C. Wood, "Hybrid Public Key Encryption", RFC 9180, DOI 10.17487/RFC9180, February 2022, . [RFC9460] Schwartz, B., Bishop, M., and E. Nygren, "Service Binding and Parameter Specification via the DNS (SVCB and HTTPS Resource Records)", RFC 9460, DOI 10.17487/RFC9460, November 2023, . [I-D.ietf-tls-svcb-ech] Schwartz, B. M., Bishop, M., and E. Nygren, "Bootstrapping TLS Encrypted ClientHello with DNS Service Bindings", Work in Progress, Internet-Draft, draft-ietf-tls-svcb-ech-08, 16 June 2025, . [I-D.ietf-tls-wkech] Farrell, S., Salz, R., and B. M. Schwartz, "A well-known URI for publishing service parameters", Work in Progress, Internet-Draft, draft-ietf-tls-wkech-09, 2 September 2025, . 12.2. Informative References [I-D.ietf-tls-esni] Rescorla, E., Oku, K., Sullivan, N., and C. A. Wood, "TLS Encrypted Client Hello", Work in Progress, Internet-Draft, draft-ietf-tls-esni-25, 14 June 2025, . Authors' Addresses Nick Sullivan Cryptography Consulting LLC Martin Thomson Mozilla Dennis Jackson Mozilla