Network Working Group V. Vasylenko Internet-Draft 4 June 2026 Intended status: Experimental Expires: 6 December 2026 End-to-End Encryption for HTTP APIs Using X25519 and AES-GCM draft-vasylenko-e2ee-http-00 Abstract This document specifies an application-layer end-to-end encryption (E2EE) scheme for HTTP APIs. The scheme uses X25519 Elliptic Curve Diffie-Hellman (ECDH) for key agreement, HKDF-SHA256 for key derivation, and AES-GCM (with 128-, 192-, or 256-bit keys) for authenticated encryption of request and response payloads. Server public keys are discovered through a Well-Known URI and authenticated either by TLS or by a stronger out-of-band trust mechanism, depending on the deployment threat model. The scheme is designed to provide confidentiality and integrity of payloads independent of, and in addition to, transport-layer security such as TLS. It provides replay protection and key rotation. 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 6 December 2026. Copyright Notice Copyright (c) 2026 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. Vasylenko Expires 6 December 2026 [Page 1] Internet-Draft E2EE HTTP June 2026 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 . . . . . . . . . . . . . . . . . . . . . . . . 3 2. Conventions and Definitions . . . . . . . . . . . . . . . . . 4 3. Protocol Overview . . . . . . . . . . . . . . . . . . . . . . 4 4. Key Discovery . . . . . . . . . . . . . . . . . . . . . . . . 5 4.1. Well-Known URI . . . . . . . . . . . . . . . . . . . . . 5 4.2. Key Set Document . . . . . . . . . . . . . . . . . . . . 6 4.3. Caching . . . . . . . . . . . . . . . . . . . . . . . . . 7 4.4. Out-of-Band Trust . . . . . . . . . . . . . . . . . . . . 7 5. AEAD Identifiers . . . . . . . . . . . . . . . . . . . . . . 8 6. Key Agreement and Derivation . . . . . . . . . . . . . . . . 8 6.1. Ephemeral Client Keys and Sessions . . . . . . . . . . . 8 6.2. Shared Secret . . . . . . . . . . . . . . . . . . . . . . 9 6.3. Encryption Key Derivation . . . . . . . . . . . . . . . . 9 7. Message Format . . . . . . . . . . . . . . . . . . . . . . . 10 7.1. Media Type . . . . . . . . . . . . . . . . . . . . . . . 10 7.2. Wire Format . . . . . . . . . . . . . . . . . . . . . . . 10 7.3. Interaction with Content-Digest . . . . . . . . . . . . . 11 7.4. Additional Authenticated Data . . . . . . . . . . . . . . 11 7.5. Binding HTTP Semantics . . . . . . . . . . . . . . . . . 12 8. The E2EE-Session Field . . . . . . . . . . . . . . . . . . . 13 8.1. Syntax . . . . . . . . . . . . . . . . . . . . . . . . . 13 8.2. Inner Content Type . . . . . . . . . . . . . . . . . . . 15 8.3. Example . . . . . . . . . . . . . . . . . . . . . . . . . 15 8.4. Use in Requests and Responses . . . . . . . . . . . . . . 15 8.5. Validation Order . . . . . . . . . . . . . . . . . . . . 16 9. Error Handling . . . . . . . . . . . . . . . . . . . . . . . 17 10. IANA Considerations . . . . . . . . . . . . . . . . . . . . . 18 10.1. Well-Known URI . . . . . . . . . . . . . . . . . . . . . 18 10.2. Media Type . . . . . . . . . . . . . . . . . . . . . . . 19 10.3. HTTP Field Names . . . . . . . . . . . . . . . . . . . . 20 10.4. Problem Type URN Parameter Identifier . . . . . . . . . 20 10.5. E2EE Error Codes . . . . . . . . . . . . . . . . . . . . 21 11. Security Considerations . . . . . . . . . . . . . . . . . . . 22 11.1. Threat Model . . . . . . . . . . . . . . . . . . . . . . 22 11.2. Transport Layer Security . . . . . . . . . . . . . . . . 23 11.3. Server Authentication and Key Trust . . . . . . . . . . 23 11.4. Forward Secrecy . . . . . . . . . . . . . . . . . . . . 23 11.5. Replay Protection . . . . . . . . . . . . . . . . . . . 24 11.6. Plaintext Error Responses . . . . . . . . . . . . . . . 25 11.7. Key Rotation . . . . . . . . . . . . . . . . . . . . . . 26 Vasylenko Expires 6 December 2026 [Page 2] Internet-Draft E2EE HTTP June 2026 11.8. Algorithm Agility . . . . . . . . . . . . . . . . . . . 26 11.9. Side Channels and Implementation . . . . . . . . . . . . 26 11.10. Denial of Service . . . . . . . . . . . . . . . . . . . 26 11.11. Comparison with Transport-Layer Solutions . . . . . . . 27 12. Privacy Considerations . . . . . . . . . . . . . . . . . . . 27 13. References . . . . . . . . . . . . . . . . . . . . . . . . . 27 13.1. Normative References . . . . . . . . . . . . . . . . . . 27 13.2. Informative References . . . . . . . . . . . . . . . . . 29 Acknowledgments . . . . . . . . . . . . . . . . . . . . . . . . . 30 Worked Example . . . . . . . . . . . . . . . . . . . . . . . . . 30 Inputs . . . . . . . . . . . . . . . . . . . . . . . . . . . . 30 Key Agreement and Derivation . . . . . . . . . . . . . . . . . 30 Request . . . . . . . . . . . . . . . . . . . . . . . . . . . . 31 Response . . . . . . . . . . . . . . . . . . . . . . . . . . . 32 Author's Address . . . . . . . . . . . . . . . . . . . . . . . . 32 1. Introduction Transport Layer Security (TLS) [RFC8446] protects HTTP [RFC9110] traffic between two endpoints that share a direct connection. In many modern deployments, however, request and response payloads traverse intermediaries such as reverse proxies, load balancers, content delivery networks, and API gateways. Each intermediary terminates TLS and observes plaintext, which expands the attack surface and weakens the confidentiality guarantee provided to end users. This document specifies a payload-level end-to-end encryption (E2EE) scheme for HTTP APIs. The scheme protects the request and response payload between the originating client and the terminating application server. Intermediaries that handle the HTTP message can route, log metadata, and apply policy. They cannot read or modify the protected payload when clients authenticate the server key set according to the deployment threat model described in Section 11. The scheme combines: * X25519 [RFC7748] for Elliptic Curve Diffie-Hellman (ECDH) key agreement. * HKDF with SHA-256 [RFC5869] for deriving symmetric keys from the shared secret. * AES-GCM [NIST-SP-800-38D] with 128-, 192-, or 256-bit keys for authenticated encryption of payloads. * A Well-Known URI [RFC8615] for publishing the server's static or rotating public key set. Vasylenko Expires 6 December 2026 [Page 3] Internet-Draft E2EE HTTP June 2026 The scheme does not replace TLS; it is intended to be deployed on top of TLS so that transport metadata such as the Host header and request path remain protected on the wire. 2. Conventions and Definitions The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "NOT RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be interpreted as described in BCP 14 [RFC2119] [RFC8174] when, and only when, they appear in all capitals, as shown here. This document uses the following terms: Client: The party that initiates an HTTP request and that owns an ephemeral X25519 key pair for the duration of a session. Server: The party that terminates the HTTP request, owns one or more X25519 key pairs whose public components are published, and processes the decrypted payload. Encryption Key (EK): A direction-specific symmetric key of 128, 192, or 256 bits derived from the X25519 shared secret using HKDF- SHA256 and used as the AES-GCM key. The length is determined by the negotiated AEAD identifier (see Section 5). AEAD Identifier (AEAD): A short string naming the AES-GCM variant in use. One of "AES-128-GCM", "AES-192-GCM", or "AES-256-GCM". Key Identifier (KID): A short, opaque label that identifies one public key in the server's published key set. Fingerprint: A base64url-encoded prefix of the SHA-256 hash of a public key, intended for human-readable verification. 3. Protocol Overview The protocol has three phases: key discovery, key agreement, and authenticated message exchange. Vasylenko Expires 6 December 2026 [Page 4] Internet-Draft E2EE HTTP June 2026 +---------+ +---------+ | Client | | Server | +----+----+ +----+----+ | | | GET /.well-known/encryption-keys | |--------------------------------------------->| | | | 200 OK { keys: [ ... ] } | |<---------------------------------------------| | | | generate ephemeral (csk, cpk) | | shared = X25519(csk, server_public_key) | | EK_req, EK_res = HKDF-SHA256(...) | | | | POST /api/v1/resource | | E2EE-Session: ""; | | aead="AES-256-GCM"; | | epk=:<32B base64>:; ts=; | | nid="" | | Content-Type: application/e2ee | | Body: nonce || ciphertext || tag | |--------------------------------------------->| | | | shared = X25519(...) | | EK_req, EK_res = ... | | decrypt, process | | | | 200 OK | | E2EE-Session: ""; | | aead="AES-256-GCM"; ts= | | Content-Type: application/e2ee | | Body: nonce || ciphertext || tag | |<---------------------------------------------| | | Figure 1: E2EE HTTP message flow 4. Key Discovery 4.1. Well-Known URI A server that supports this scheme MUST publish its public key set at the Well-Known URI [RFC8615]: /.well-known/encryption-keys Vasylenko Expires 6 December 2026 [Page 5] Internet-Draft E2EE HTTP June 2026 The resource MUST be served over HTTPS and MUST be retrievable with an HTTP GET request. The response MUST have media type application/ json. 4.2. Key Set Document The response body is a JSON object with the following members: issuer: A string identifying the server origin. The value MUST be an HTTPS origin and MUST match the origin from which the key set was retrieved unless the client has explicit out-of-band configuration allowing another issuer. REQUIRED. keys: A JSON array of one or more key objects, ordered from most- preferred to least-preferred. REQUIRED. Each key object has the following members: kid: A short opaque string identifying this key within the set. REQUIRED. KIDs MUST be unique within a single key set. The value MUST contain between 1 and 128 characters and MUST match the regular expression ^[A-Za-z0-9._~-]+$. KID comparison is case- sensitive. alg: The key agreement algorithm. MUST be the string "X25519" for this version of the protocol. REQUIRED. aeads: A non-empty JSON array of AEAD Identifier strings (see Section 5) that the server is willing to accept under this key, in order of server preference. REQUIRED. public_key: The 32-byte X25519 public key, base64url-encoded [RFC4648] without padding. REQUIRED. fingerprint: The base64url-encoded [RFC4648] first 16 bytes of the SHA-256 hash of the raw 32-byte public key. RECOMMENDED. not_before: An RFC 3339 [RFC3339] date-time value before which the key MUST NOT be used. OPTIONAL. not_after: An RFC 3339 [RFC3339] date-time value after which the key MUST NOT be used. REQUIRED. max_skew: A non-negative integer specifying the maximum acceptable absolute difference, in seconds, between the request ts parameter and the server clock. This value bounds the timestamp check in addition to the key validity window and determines the minimum replay-cache retention period (see Section 11). REQUIRED. Vasylenko Expires 6 December 2026 [Page 6] Internet-Draft E2EE HTTP June 2026 Clients MUST treat a key object that is missing a required member, has a member of the wrong JSON type, or contains an invalid public_key, not_before, not_after, or max_skew value as unusable. Clients MUST treat a key set containing duplicate kid values as invalid. Example: { "issuer": "https://api.example.com", "keys": [ { "kid": "2026-06", "alg": "X25519", "aeads": ["AES-256-GCM", "AES-128-GCM"], "public_key": "B6N8vBQgk8i3VdwbEOhstCY3StFqqFPtC9_AsrhtHHw", "fingerprint": "qqj_9wO1CyKX9PbhNQj3JA", "not_before": "2026-06-09T00:00:00Z", "not_after": "2026-07-09T00:00:00Z", "max_skew": 300 } ] } 4.3. Caching Responses SHOULD include a Cache-Control header. Clients MAY cache the key set for up to the duration indicated, but MUST refresh the key set before using a key whose not_after is in the past, and SHOULD refresh on receipt of a key_unknown error (see Section 9). 4.4. Out-of-Band Trust The integrity of the key set ultimately depends on the integrity of the channel used to retrieve it. Clients MUST verify the TLS server certificate of the server hosting the Well-Known URI per [RFC8446] and [RFC9325]. Clients MUST also verify that the issuer value in the key set matches the HTTPS origin used to retrieve it, unless an out- of-band configuration explicitly authorizes a different issuer. If the deployment threat model includes TLS-terminating intermediaries that are not trusted with plaintext payloads, TLS authentication of the Well-Known URI is not sufficient to authenticate the encryption keys: such an intermediary could substitute its own key set. In that threat model, clients MUST authenticate the key set independently of the TLS connection by using either a pinned key fingerprint or a verified HTTP Message Signature whose signing key was distributed out of band; see Section 11. Vasylenko Expires 6 December 2026 [Page 7] Internet-Draft E2EE HTTP June 2026 Servers SHOULD additionally protect the key set response with HTTP Message Signatures [RFC9421], signed under a long-lived signing key distributed out of band, and SHOULD include a Content-Digest field [RFC9530] over the key set body so that the signature covers a content hash rather than the raw bytes. Clients that have a pinned signing key MUST reject any key set response whose signature is missing or invalid. 5. AEAD Identifiers This document defines three AEAD Identifiers, all based on AES-GCM [NIST-SP-800-38D]: +=================+============+==============+============+ | AEAD Identifier | Key length | Nonce length | Tag length | +=================+============+==============+============+ | AES-128-GCM | 16 octets | 12 octets | 16 octets | +-----------------+------------+--------------+------------+ | AES-192-GCM | 24 octets | 12 octets | 16 octets | +-----------------+------------+--------------+------------+ | AES-256-GCM | 32 octets | 12 octets | 16 octets | +-----------------+------------+--------------+------------+ Table 1 All three variants share the same nonce and tag length and differ only in key length. Implementations MUST support AES-128-GCM and AES-256-GCM. Support for AES-192-GCM is OPTIONAL. The client selects one AEAD Identifier from the server's aeads list for each session and signals it in the request (see Section 8). The server MUST reject any request whose AEAD Identifier is not advertised in the current key set entry for the chosen kid. 6. Key Agreement and Derivation 6.1. Ephemeral Client Keys and Sessions A _session_ is a contiguous sequence of requests sent by one client under a single X25519 key pair (csk, cpk). The client public key cpk is sent on the wire as the epk parameter of the E2EE-Session field. Clients MUST generate (csk, cpk) using a cryptographically secure random number generator. The default session scope is a single request: clients SHOULD generate a fresh key pair for every request unless they explicitly opt into a broader scope as described below. Vasylenko Expires 6 December 2026 [Page 8] Internet-Draft E2EE HTTP June 2026 A client MAY reuse one (csk, cpk) across multiple requests provided that all of the following hold: * The reuse is confined to a single logical client instance (one process, one user, one device). * The key pair is not persisted to non-volatile storage and is destroyed on process exit, user logout, or after a deployment- defined inactivity timeout, whichever comes first. * The deployment-defined session lifetime does not exceed the shortest not_after of any server key it references. * The client maintains its own nid uniqueness within the session (see Section 11). Clients MUST NOT reuse (csk, cpk) across processes, after restart, across users on a shared device, or after any event that would invalidate the client's secure-random state (for example, virtual machine snapshot restore). Each unique value of epk observed by a server defines, together with the server kid, the scope of replay-cache state for that client (see Section 11). Per-request session scope therefore minimises both the lifetime of the client private key and the amount of replay-cache state retained for any one client. 6.2. Shared Secret The shared secret is computed as: Z = X25519(csk, server_public_key) per Section 5 of [RFC7748]. Implementations MUST check that Z is not the all-zero value and abort if it is. 6.3. Encryption Key Derivation The request encryption key (EK_req) and response encryption key (EK_res) are derived from Z using HKDF-SHA256 [RFC5869]: Vasylenko Expires 6 December 2026 [Page 9] Internet-Draft E2EE HTTP June 2026 PRK = HKDF-Extract(salt = cpk || server_public_key, IKM = Z) EK_req = HKDF-Expand(PRK, info = "e2ee/v1:req " || issuer || " " || aead || " " || kid, L = Nk) EK_res = HKDF-Expand(PRK, info = "e2ee/v1:res " || issuer || " " || aead || " " || kid, L = Nk) where: * || denotes octet-string concatenation, * cpk is the raw 32-byte client public key, * server_public_key is the raw 32-byte server public key, * issuer is the UTF-8 encoding of the key set issuer value, * aead is the UTF-8 encoding of the selected AEAD Identifier ("AES- 128-GCM", "AES-192-GCM", or "AES-256-GCM"), * kid is the UTF-8 encoding of the Key Identifier used, * Nk is the key length in octets implied by aead (16, 24, or 32). Binding issuer, aead, kid, and both public keys into the HKDF inputs binds the derived keys to the specific key agreement transcript and prevents cross-origin, cross-protocol, cross-key, cross-direction, or cross-algorithm confusion. Deriving separate request and response keys prevents an AES-GCM nonce collision in one direction from colliding with a nonce in the other direction. 7. Message Format 7.1. Media Type Protected payloads MUST be sent with the media type application/e2ee. The media type identifies the ciphertext envelope defined by this document; the plaintext media type, when needed, is carried in the cty parameter of the E2EE-Session field (see Section 8.2). 7.2. Wire Format The body of a protected request or response is the concatenation: body = nonce || ciphertext || tag Vasylenko Expires 6 December 2026 [Page 10] Internet-Draft E2EE HTTP June 2026 where: * nonce is 12 octets, generated with a cryptographically secure random number generator for every message. Nonces MUST NOT be reused with the same direction-specific EK. * ciphertext is the AES-GCM encryption of the inner plaintext using EK_req for requests or EK_res for responses and nonce, with additional authenticated data (AAD) as defined in Section 7.4. The AES-GCM key length is determined by the selected AEAD Identifier (see Section 5). * tag is the 16-octet AES-GCM authentication tag. Recipients MUST reject a protected body shorter than 28 octets (12-octet nonce plus 16-octet tag) as malformed. 7.3. Interaction with Content-Digest The AES-GCM tag already provides end-to-end integrity for the ciphertext, so a Content-Digest field [RFC9530] is not required for integrity. Senders MAY include Content-Digest over the raw ciphertext body for caching, deduplication, or intermediary integrity checks; in that case the digest MUST be computed over the exact serialized body (nonce || ciphertext || tag). Recipients MUST NOT treat a present Content-Digest as a substitute for AES-GCM tag verification, and MUST NOT compute or expose a digest over the decrypted plaintext. 7.4. Additional Authenticated Data The AAD passed to AES-GCM binds only the cryptographic transcript of the message. HTTP semantics such as method, target URI, status code, and selected headers are out of scope for AAD and are bound, when needed, by HTTP Message Signatures [RFC9421] (see Section 7.5). For AAD construction, an E2EE-Session field value MUST first be parsed as a Structured Field Item and then serialized using the deterministic serialization algorithm for Structured Fields [RFC9651]. The serialized field value does not include the field name, colon, or surrounding whitespace. Recipients MUST NOT use the raw field bytes as received from an HTTP/1.1 connection, because equivalent field values can have different wire serializations and HTTP/2 and HTTP/3 do not preserve an HTTP/1.1 header-field line. For requests, the AAD MUST be the ASCII concatenation: AAD = "e2ee/v1:req" || " " || encryption-field Vasylenko Expires 6 December 2026 [Page 11] Internet-Draft E2EE HTTP June 2026 For responses, the AAD MUST be the ASCII concatenation: AAD = "e2ee/v1:res" || " " || req-encryption-field || " " || res-encryption-field where: * encryption-field is the deterministic serialization of the request's parsed E2EE-Session Structured Field value (see Section 8), * req-encryption-field is the same value as encryption-field for the request that produced this response, * res-encryption-field is the deterministic serialization of the response's own parsed E2EE-Session field value. The leading "e2ee/v1:req" / "e2ee/v1:res" strings combine a protocol- version tag with a domain-separation tag. They prevent a request ciphertext from being accepted as a response (or vice versa) and isolate this protocol from any other use of AES-GCM under the same key. Including both E2EE-Session fields in the response AAD authenticates kid, aead, epk, ts, and nid from both directions jointly with the ciphertext, prevents an intermediary from altering any of them, and prevents response substitution across different requests. 7.5. Binding HTTP Semantics This specification does not bind HTTP semantics (method, target URI, status code, request or response headers) into the AES-GCM AAD. Doing so would conflict with the routine behaviour of TLS-terminating intermediaries that legitimately rewrite request targets, normalize paths, or add tracing parameters. Deployments that require an end-to-end binding of HTTP semantics SHOULD apply HTTP Message Signatures [RFC9421] to requests and responses, alongside the encryption defined by this document. The covered-components list SHOULD include at least: * For requests: @method, @target-uri, the E2EE-Session field, and a Content-Digest field [RFC9530] computed over the ciphertext body. * For responses: @status, the E2EE-Session field, and a Content- Digest field over the ciphertext body. Vasylenko Expires 6 December 2026 [Page 12] Internet-Draft E2EE HTTP June 2026 Because Content-Digest covers the ciphertext (which already contains the AES-GCM tag), an RFC 9421 signature over those components transitively covers the encrypted payload. The signature key and keyid are independent of the X25519 key set defined here and are managed per [RFC9421]. A deployment that omits HTTP Message Signatures relies on this protocol's confidentiality and integrity guarantees only at the payload layer, and on TLS and application-level checks for HTTP semantics. This is appropriate when the application cannot be confused by a redirected ciphertext (for example, when each endpoint parses a distinct payload schema and rejects unknown shapes). 8. The E2EE-Session Field All E2EE control metadata is carried in a single HTTP field named E2EE-Session. The field is a Structured Field [RFC9651] whose value is an Item: a String identifying the server key (kid), with named parameters carrying the remaining metadata. 8.1. Syntax In the ABNF of [RFC9651], the field value is an sf-item: E2EE-Session = sf-item The Item value is a String holding the kid. The following parameters are defined: Vasylenko Expires 6 December 2026 [Page 13] Internet-Draft E2EE HTTP June 2026 +======+==========+==========+============+=========================+ | Name | Type | Request | Response | Meaning | +======+==========+==========+============+=========================+ | aead | String | required | required | AEAD Identifier | | | | | | (see Section 5). | +------+----------+----------+------------+-------------------------+ | epk | Byte | required | prohibited | Client ephemeral | | | Sequence | | | X25519 public key. | +------+----------+----------+------------+-------------------------+ | ts | Integer | required | required | Seconds since Unix | | | | | | epoch. | +------+----------+----------+------------+-------------------------+ | nid | String | required | required | Per-message replay | | | | | | identifier (e.g. | | | | | | UUID). | +------+----------+----------+------------+-------------------------+ | cty | String | optional | optional | Media type of the | | | | | | inner plaintext | | | | | | (see Section 8.2). | +------+----------+----------+------------+-------------------------+ Table 2 epk is the raw 32-octet X25519 public key carried as a Structured Fields Byte Sequence (RFC 9651 base64, sf-binary, delimited by :), not base64url. Servers MUST reject any value whose decoded length is not 32 octets. ts MUST be a non-negative Integer. nid MUST contain between 1 and 128 characters and MUST match the regular expression ^[A-Za-z0-9._~-]+$. nid comparison is case- sensitive. cty is OPTIONAL and, when present, MUST be the media type of the inner plaintext (see Section 8.2). Parameters defined by future revisions of this specification or by extensions MUST use distinct names. Recipients MUST ignore unknown parameters semantically, but unknown parameters remain part of the deterministic serialization that is authenticated as AAD. A field value that contains the same parameter name more than once MUST be rejected as malformed; this check MUST occur before deterministic serialization. Implementations whose Structured Fields parser cannot report duplicate parameters MUST use a parser that can for this field. Vasylenko Expires 6 December 2026 [Page 14] Internet-Draft E2EE HTTP June 2026 8.2. Inner Content Type The plaintext recovered after AES-GCM decryption is opaque to this specification. To allow recipients to dispatch on its format without a separate inner header, senders MAY include a cty parameter on the E2EE-Session field carrying the inner plaintext's media type as a String, for example cty="application/json" or cty="application/cbor". The value MUST be a valid media type per Section 8.3 of [RFC9110]. Media-type parameters (for example, charset=utf-8) MAY be present in cty. Recipients SHOULD treat the absence of cty as "unspecified" and rely on out-of-band knowledge of the endpoint to interpret the plaintext. Because cty is a parameter of the E2EE-Session field, it is covered by the AES-GCM AAD (see Section 7.4), so an intermediary cannot alter the advertised inner media type without invalidating the tag. The outer HTTP Content-Type field, when present, MUST remain application/e2ee (or another media type defined for the ciphertext envelope); cty does not replace it. Recipients that receive both an outer Content-Type of application/e2ee and an inner cty MUST use cty only for the decrypted plaintext. 8.3. Example E2EE-Session: "2026-06"; \ aead="AES-256-GCM"; \ epk=:rUOL+uMfbAk9YdQzklXqeYCSyfrdB7l4J/Swrp3ufBw=:; \ ts=1781006400; \ nid="3b1c1c2e-2b6a-4a0d-9b6c-2a9f1b6a0e21"; \ cty="application/json" (Line folding shown for readability; the field is a single Structured Field value. AAD construction uses deterministic Structured Fields serialization, not these presentation line breaks.) 8.4. Use in Requests and Responses Every request that carries an encrypted payload MUST include exactly one E2EE-Session field with all required parameters. A response that carries an encrypted payload MUST include an E2EE-Session field whose kid Item value and aead parameter echo the request. The epk parameter MUST be omitted from responses; the server reuses the client's ephemeral public key from the request. The ts parameter on a response MUST reflect the server's current time. A response MUST echo the request's nid so that clients can correlate responses and detect duplicate or substituted responses. Vasylenko Expires 6 December 2026 [Page 15] Internet-Draft E2EE HTTP June 2026 Clients MUST verify that the kid Item value and the aead and nid parameters of the response E2EE-Session field match the values they sent in the corresponding request, and MUST discard the response without decrypting it on mismatch. A mismatch indicates either an in-path attacker or a misconfigured intermediary serving a cached or cross-routed response. Because the request's E2EE-Session field is included in the response AAD (see Section 7.4), a mismatch is also detectable as an AES-GCM tag failure during decryption; the pre- decryption check is RECOMMENDED to avoid spending a decryption attempt on an obviously wrong response. 8.5. Validation Order Recipients MUST validate the E2EE-Session field before any other protocol step. For requests, servers MUST validate in the following order: 1. Parse as a Structured Fields Item; reject as malformed on failure. 2. Check that the Item value is a String, that all required request parameters are present, that no parameter prohibited in requests by Section 8 is present, that no parameter name appears more than once, and that all known parameters have the types defined in Section 8; reject as malformed on failure. 3. If cty is present, validate it as a media type per Section 8.3 of [RFC9110]; reject as malformed on failure. 4. Resolve kid against the current key set; reject as key_unknown or key_expired. 5. Check aead against the aeads list for that kid; reject as aead_unsupported. 6. Decode and length-check epk; reject as malformed. 7. Check the protected body length; reject as malformed if it is shorter than 28 octets. 8. Check ts against the selected key's not_before/not_after validity window and max_skew; reject as timestamp_skew if outside the window. 9. Check nid against the replay cache; reject as replay_detected on hit, but do not insert the nid yet. Vasylenko Expires 6 December 2026 [Page 16] Internet-Draft E2EE HTTP June 2026 10. Derive EK_req and attempt AES-GCM decryption; reject as decrypt_failed on tag failure. 11. After successful authentication and before invoking application processing, insert the nid into the replay cache atomically. For responses, clients MUST validate in the following order: 1. Parse as a Structured Fields Item; reject the response on failure. 2. Check that the Item value is a String, that all required response parameters are present, that epk is absent, that no parameter name appears more than once, and that all parameters known to this specification have the types defined in Section 8; reject the response on failure. 3. If cty is present, validate it as a media type per Section 8.3 of [RFC9110]; reject the response on failure. 4. Check that kid, aead, and nid match the request. 5. Check the protected body length; reject the response if it is shorter than 28 octets. 6. Check that ts is a non-negative Integer and is acceptable under local response freshness policy. 7. Derive EK_res and attempt AES-GCM decryption; reject the response on tag failure. 9. Error Handling When a request cannot be processed due to a protocol error, the server MUST respond with an HTTP error status and a Problem Details object [RFC9457] serialized as application/problem+json. The type member MUST be a URI of the form: urn:ietf:params:e2ee:error: where is one of the codes defined below. The status member MUST equal the HTTP status code of the response. The title member SHOULD be a short, fixed human-readable summary of the code. The detail member, if present, SHOULD describe the specific occurrence subject to the constraints in Section 11.6. Example: Vasylenko Expires 6 December 2026 [Page 17] Internet-Draft E2EE HTTP June 2026 HTTP/1.1 400 Bad Request Content-Type: application/problem+json { "type": "urn:ietf:params:e2ee:error:key_unknown", "title": "Key identifier is not recognized", "status": 400 } The following codes are defined: key_unknown: HTTP 400. The kid Item value does not match any current key. key_expired: HTTP 400. The referenced key is outside its not_before/not_after window. aead_unsupported: HTTP 400. The aead parameter is not advertised for the referenced kid, or is not recognized by the server. decrypt_failed: HTTP 400. AES-GCM authentication failed. timestamp_skew: HTTP 400. The ts parameter is outside the acceptable window (see Section 11). replay_detected: HTTP 425. The server has already processed a message with this nid within the replay window. The 425 (Too Early) status [RFC8470] is reused here to signal that the server is unwilling to process a request that may be a replay. malformed: HTTP 400. The E2EE-Session field is missing, not parseable as a Structured Field Item, missing a required parameter, or has a parameter of the wrong type or length, or the protected body is too short to contain a nonce and tag. 10. IANA Considerations This document requests IANA actions in the following registries. 10.1. Well-Known URI IANA is requested to register the URI suffix encryption-keys in the "Well-Known URIs" registry established by [RFC8615], with this document as the reference. URI suffix: encryption-keys Change controller: IETF Vasylenko Expires 6 December 2026 [Page 18] Internet-Draft E2EE HTTP June 2026 Status: permanent Specification document: This document. Related information: This resource returns a JSON key set used by the protocol defined in this document. 10.2. Media Type IANA is requested to register the media type application/e2ee in the "Media Types" registry, with this document as the reference and per the procedures in [RFC6838]. Type name: application Subtype name: e2ee Required parameters: N/A Optional parameters: N/A Encoding considerations: binary Security considerations: See Section 11. Interoperability considerations: Implementations need to parse the E2EE-Session Structured Field and process the binary nonce || ciphertext || tag envelope defined by this document. Interoperability depends on agreement on the selected AEAD Identifier, the key set entry referenced by kid, and the AAD construction rules in Section 7.4. Published specification: This document. Applications that use this media type: HTTP APIs that use the encrypted payload envelope defined by this document. Fragment identifier considerations: N/A Additional information: Deprecated alias names for this type: N/A Magic number(s): N/A File extension(s): N/A Macintosh file type code(s): N/A Person and email address to contact for further information: See the Authors' Addresses section. Intended usage: LIMITED USE Vasylenko Expires 6 December 2026 [Page 19] Internet-Draft E2EE HTTP June 2026 Restrictions on usage: This media type is intended for HTTP request and response payloads that use the encrypted envelope defined by this document. It is not a general-purpose stored file format. Author: See the Authors' Addresses section. Change controller: IETF 10.3. HTTP Field Names IANA is requested to register the following entry in the "Hypertext Transfer Protocol (HTTP) Field Name Registry": * Field name: E2EE-Session * Status: permanent * Structured Type: Item * Reference: This document 10.4. Problem Type URN Parameter Identifier IANA is requested to add the following entry to the "IETF URN Sub- namespace for Registered Protocol Parameter Identifiers" registry: * Registered Parameter Identifier: e2ee * Reference: This document * IANA Registry Reference: E2EE Error Codes registry, created by this document The registration policy for this registry is IETF Review [RFC8126], as specified for the urn:ietf:params namespace by [RFC3553]. The template required by [RFC3553] is: Registry name: e2ee Specification: This document. Repository: The E2EE Error Codes registry created by this document. Index value: A problem type URI has the form Vasylenko Expires 6 December 2026 [Page 20] Internet-Draft E2EE HTTP June 2026 urn:ietf:params:e2ee:error:, where is a lowercase ASCII error code registered in the E2EE Error Codes registry. No transformation or canonicalization is applied. Comparison is by exact string match. 10.5. E2EE Error Codes IANA is requested to create the "E2EE Error Codes" registry. The registration policy is Specification Required [RFC8126]. Error codes are ASCII strings that MUST contain between 1 and 64 characters and MUST match the regular expression ^[a-z][a-z0-9_]{0,63}$. New registrations MUST provide: * Error code * HTTP status code * Description * Reference The initial contents of the registry are: Vasylenko Expires 6 December 2026 [Page 21] Internet-Draft E2EE HTTP June 2026 +==================+========+=======================+===========+ | Error code | HTTP | Description | Reference | | | status | | | +==================+========+=======================+===========+ | key_unknown | 400 | Key identifier is not | This | | | | recognized. | document | +------------------+--------+-----------------------+-----------+ | key_expired | 400 | Key identifier is | This | | | | outside its validity. | document | +------------------+--------+-----------------------+-----------+ | aead_unsupported | 400 | AEAD identifier is | This | | | | not supported. | document | +------------------+--------+-----------------------+-----------+ | decrypt_failed | 400 | AEAD authentication | This | | | | failed. | document | +------------------+--------+-----------------------+-----------+ | timestamp_skew | 400 | Timestamp is outside | This | | | | the accepted window. | document | +------------------+--------+-----------------------+-----------+ | replay_detected | 425 | Replay identifier was | This | | | | already observed. | document | +------------------+--------+-----------------------+-----------+ | malformed | 400 | Protocol metadata or | This | | | | body is malformed. | document | +------------------+--------+-----------------------+-----------+ Table 3 11. Security Considerations 11.1. Threat Model This scheme is designed to protect the confidentiality and integrity of request and response payloads against: * Intermediaries that terminate TLS (reverse proxies, CDNs, API gateways), and * Passive observers of any plaintext channel between the TLS terminator and the application backend. Vasylenko Expires 6 December 2026 [Page 22] Internet-Draft E2EE HTTP June 2026 It is not designed to protect HTTP metadata (method, path, headers other than the protected body, response status) or to defeat traffic analysis. It does not authenticate the client by itself. Client authentication MAY be layered on top using the protected payload (for example, bearer tokens carried inside the ciphertext) or, preferably, by signing the request with HTTP Message Signatures [RFC9421]; in the latter case the signature input MUST include the E2EE-Session field and the Content-Digest field over the ciphertext body so that the signature also binds the AAD and ciphertext. 11.2. Transport Layer Security This scheme MUST be used over TLS [RFC8446] configured per current best practice [RFC9325]. TLS protects request metadata, the Well- Known key set retrieval, and provides server authentication. 11.3. Server Authentication and Key Trust When the TLS endpoint that serves the Well-Known URI is also the application endpoint trusted with plaintext payloads, the server's identity is authenticated by the TLS certificate of that host and by the issuer value in the key set. When TLS-terminating intermediaries are present and are not trusted with plaintext payloads, TLS authentication alone does not authenticate the encryption keys end to end. In that deployment, clients MUST verify the fingerprint of any key they use against a value obtained out of band (for example, distributed with the client software) or MUST verify a signature over the key set using a signing key distributed out of band. Mobile and desktop client implementations are RECOMMENDED to pin at least one fingerprint or signing key. Where stronger guarantees are required, servers SHOULD sign the key set with HTTP Message Signatures [RFC9421] and cover the body with a Content-Digest field [RFC9530]. The signing key is necessarily distributed out of band; clients that pin the signing key obtain key authenticity that is independent of the Web PKI used by TLS. 11.4. Forward Secrecy The client's key pair is ephemeral, but the server's published key is static for the lifetime of its key set entry. The scheme therefore provides forward secrecy only with respect to client-side compromise. Compromise of a server private key allows decryption of all sessions that used it. Vasylenko Expires 6 December 2026 [Page 23] Internet-Draft E2EE HTTP June 2026 Operators SHOULD rotate server keys frequently and use short not_after windows. Implementations of this specification SHOULD publish at least two overlapping keys in the key set to enable seamless rotation. A future revision of this protocol MAY define a mode in which the server returns a fresh ephemeral public key on first contact, providing full perfect forward secrecy at the cost of an additional round trip. 11.5. Replay Protection Servers MUST validate the ts parameter against the key validity window for the referenced kid: any ts before not_before, when present, or after not_after MUST be rejected (timestamp_skew). Servers MUST also reject any ts whose absolute difference from the server clock exceeds the key set entry's max_skew value (timestamp_skew). Servers SHOULD publish a max_skew value no larger than the maximum retry interval they are willing to support; a value of 300 seconds is RECOMMENDED unless the deployment has stricter clock synchronization or longer retry requirements. Clients MUST read max_skew from the selected key set entry and account for it when scheduling retries. A retry sent with a fresh nid after the original request's timestamp has aged beyond max_skew is expected to fail timestamp validation. Servers MUST maintain a cache of recently seen nid values, keyed by (kid, epk), for at least max_skew plus a small tolerance for processing latency and clock granularity. A repeated nid MUST result in replay_detected. Clients MUST generate a fresh, unpredictable nid for every request; a version 4 UUID or any 128-bit value drawn from a cryptographically secure random number generator is sufficient. Servers MUST NOT insert a nid into the replay cache until the request has been authenticated successfully by AES-GCM. Inserting a nid before tag verification would allow an attacker to poison the replay cache with unauthenticated requests. Servers MUST insert the nid atomically after successful authentication and before application side effects occur; if another request with the same (kid, epk, nid) wins that atomic insertion, the later request MUST fail with replay_detected. The nid parameter is an anti-replay identifier and is not an application idempotency key. The two have opposite behavior on a cache hit: a duplicate nid MUST cause the server to reject the Vasylenko Expires 6 December 2026 [Page 24] Internet-Draft E2EE HTTP June 2026 request, whereas a duplicate application idempotency key is normally expected to cause the server to return the stored response of the original request. Applications that require both fault-tolerant retries and end-to-end replay protection MUST use distinct values for the two purposes. An application idempotency key, if used, SHOULD be carried inside the encrypted payload so that the application sees it but intermediaries do not, and so that it is bound to the request by the AES-GCM tag. The 12-byte AES-GCM nonce is independently random per message and MUST NOT be reused under the same direction-specific EK. 11.6. Plaintext Error Responses Error responses defined in Section 9 are sent as Problem Details [RFC9457] in plaintext and are therefore visible to any TLS- terminating intermediary. Error responses do not carry encrypted application payloads and do not require an E2EE-Session field; some errors occur before the request E2EE-Session field can be parsed safely. Operators MUST treat error metadata as observable to intermediaries. In particular: * The detail and instance members of the Problem Details object MUST NOT contain user identifiers, request contents, stack traces, or any value derived from the decrypted plaintext. * The title member SHOULD be a fixed string per error code and MUST NOT vary with request content. * Extension members defined by future revisions or by deployments MUST be subject to the same constraints. * Error type URIs SHOULD be chosen from the fixed set in Section 9; application-specific error information SHOULD instead be returned as an encrypted payload with an appropriate HTTP status. * The presence of decrypt_failed or replay_detected reveals to an observer that an attack or a stale retry occurred; this is considered acceptable as it aids defenders more than attackers. Successful responses MUST carry their application payload encrypted under this specification; servers MUST NOT fall back to a plaintext success response when the request was encrypted. Vasylenko Expires 6 December 2026 [Page 25] Internet-Draft E2EE HTTP June 2026 11.7. Key Rotation Each request carries a kid, allowing the server to retain the private keys associated with all currently valid kids and decrypt requests that arrive during a rotation. Clients MUST refresh the key set when the cached entry has expired, when no key remains within its validity window, or on key_unknown. 11.8. Algorithm Agility This document specifies one key agreement algorithm (X25519), one key-derivation function (HKDF-SHA256), and three AEAD variants (AES- 128/192/256-GCM). Servers MAY publish keys with alg values or list aeads entries defined by future specifications; clients that do not recognize an alg MUST ignore the key, and clients that do not recognize any of the aeads entries for a key MUST treat the key as unusable. AES-128-GCM and AES-256-GCM are mandatory to implement; AES-192-GCM is OPTIONAL because the 192-bit variant is rarely accelerated in hardware and provides no security advantage over AES-128-GCM in this setting. Implementations SHOULD prefer AES-256-GCM by default. AES- 128-GCM is acceptable when interoperability or constrained-device performance takes precedence. Implementations MUST reject any single plaintext that exceeds the AES-GCM per-invocation input limit (approximately 2^39 - 256 bits per [NIST-SP-800-38D]). Deployments that reuse a client session across multiple requests SHOULD also enforce a maximum number of messages per direction-specific EK; with randomly generated 96-bit nonces, 2^32 messages under one direction- specific EK is an upper bound and is far above typical API usage. 11.9. Side Channels and Implementation X25519 implementations MUST be constant-time per [RFC7748]. AES-GCM implementations SHOULD use hardware acceleration where available to reduce the risk of cache-timing leaks. HKDF and base64url implementations MUST validate input lengths to avoid out-of-bounds reads. 11.10. Denial of Service Decryption is comparatively cheap, but X25519 scalar multiplication is not free. Servers SHOULD rate-limit requests bearing unknown or expired kids, and SHOULD reject malformed bodies before performing key agreement. Vasylenko Expires 6 December 2026 [Page 26] Internet-Draft E2EE HTTP June 2026 11.11. Comparison with Transport-Layer Solutions This scheme is complementary to, and not a replacement for, alternatives such as mTLS or QUIC [RFC9000]. It is specifically targeted at deployments where TLS-terminating intermediaries are part of the application architecture and removing them is not feasible. 12. Privacy Considerations This protocol improves payload confidentiality from the perspective of TLS-terminating intermediaries, but it does not hide HTTP metadata. Intermediaries can still observe the client and server endpoints, request timing, request method, target URI, status code, message sizes, and all unprotected HTTP fields. Applications that carry privacy-sensitive values in URIs or unprotected headers will still expose those values. The E2EE-Session field is also visible to intermediaries. Its kid, aead, ts, nid, epk, and cty parameters can reveal deployment state, timing, retry behavior, client session scope, and the media type of the inner plaintext. Clients that need to reduce linkability SHOULD use the default per-request client key scope and generate fresh nid values for every request. Plaintext error responses expose protocol failure information, as described in Section 11.6. Deployments SHOULD avoid putting user- specific or content-derived information in error details and SHOULD carry application-specific error information inside encrypted response payloads when feasible. These considerations are intended to supplement the privacy guidance in [RFC6973]. 13. References 13.1. Normative References [NIST-SP-800-38D] National Institute of Standards and Technology, "Recommendation for Block Cipher Modes of Operation: Galois/Counter Mode (GCM) and GMAC", NIST Special Publication 800-38D, 2007. [RFC2119] Bradner, S., "Key words for use in RFCs to Indicate Requirement Levels", BCP 14, RFC 2119, DOI 10.17487/RFC2119, March 1997, . Vasylenko Expires 6 December 2026 [Page 27] Internet-Draft E2EE HTTP June 2026 [RFC3339] Klyne, G. and C. Newman, "Date and Time on the Internet: Timestamps", RFC 3339, DOI 10.17487/RFC3339, July 2002, . [RFC3553] Mealling, M., Masinter, L., Hardie, T., and G. Klyne, "An IETF URN Sub-namespace for Registered Protocol Parameters", BCP 73, RFC 3553, DOI 10.17487/RFC3553, June 2003, . [RFC4648] Josefsson, S., "The Base16, Base32, and Base64 Data Encodings", RFC 4648, DOI 10.17487/RFC4648, October 2006, . [RFC5869] Krawczyk, H. and P. Eronen, "HMAC-based Extract-and-Expand Key Derivation Function (HKDF)", RFC 5869, DOI 10.17487/RFC5869, May 2010, . [RFC6838] Freed, N., Klensin, J., and T. Hansen, "Media Type Specifications and Registration Procedures", BCP 13, RFC 6838, DOI 10.17487/RFC6838, January 2013, . [RFC7748] Langley, A., Hamburg, M., and S. Turner, "Elliptic Curves for Security", RFC 7748, DOI 10.17487/RFC7748, January 2016, . [RFC8126] Cotton, M., Leiba, B., and T. Narten, "Guidelines for Writing an IANA Considerations Section in RFCs", BCP 26, RFC 8126, DOI 10.17487/RFC8126, June 2017, . [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, . [RFC8470] Thomson, M., Nottingham, M., and W. Tarreau, "Using Early Data in HTTP", RFC 8470, DOI 10.17487/RFC8470, September 2018, . [RFC8615] Nottingham, M., "Well-Known Uniform Resource Identifiers (URIs)", RFC 8615, DOI 10.17487/RFC8615, May 2019, . Vasylenko Expires 6 December 2026 [Page 28] Internet-Draft E2EE HTTP June 2026 [RFC9110] Fielding, R., Ed., Nottingham, M., Ed., and J. Reschke, Ed., "HTTP Semantics", STD 97, RFC 9110, DOI 10.17487/RFC9110, June 2022, . [RFC9325] Sheffer, Y., Saint-Andre, P., and T. Fossati, "Recommendations for Secure Use of Transport Layer Security (TLS) and Datagram Transport Layer Security (DTLS)", BCP 195, RFC 9325, DOI 10.17487/RFC9325, November 2022, . [RFC9421] Backman, A., Ed., Richer, J., Ed., and M. Sporny, "HTTP Message Signatures", RFC 9421, DOI 10.17487/RFC9421, February 2024, . [RFC9457] Nottingham, M., Wilde, E., and S. Dalal, "Problem Details for HTTP APIs", RFC 9457, DOI 10.17487/RFC9457, July 2023, . [RFC9530] Polli, R. and L. Pardue, "Digest Fields", RFC 9530, DOI 10.17487/RFC9530, February 2024, . [RFC9651] Nottingham, M. and P. Kamp, "Structured Field Values for HTTP", RFC 9651, DOI 10.17487/RFC9651, September 2024, . 13.2. Informative References [RFC6973] Cooper, A., Tschofenig, H., Aboba, B., Peterson, J., Morris, J., Hansen, M., and R. Smith, "Privacy Considerations for Internet Protocols", RFC 6973, DOI 10.17487/RFC6973, July 2013, . [RFC9000] Iyengar, J., Ed. and M. Thomson, Ed., "QUIC: A UDP-Based Multiplexed and Secure Transport", RFC 9000, DOI 10.17487/RFC9000, May 2021, . [VASYLENKO-BLOG] Vasylenko, V., "End-to-End Encryption for APIs: X25519 and AES", 27 July 2025, . Vasylenko Expires 6 December 2026 [Page 29] Internet-Draft E2EE HTTP June 2026 Acknowledgments The protocol described in this document derives from an informal blog-post implementation of end-to-end encryption for HTTP APIs using X25519 and AES-GCM [VASYLENKO-BLOG]. This document formalizes the wire format, generalizes the cipher to AES-128/192/256-GCM with explicit negotiation, specifies additional authenticated data and replay protection, and defines key rotation semantics. Worked Example This appendix shows one complete request/response pair using deterministic inputs. All values are hexadecimal unless noted; lines of the form = are byte-identical to what an interoperable implementation would compute. The inputs (private keys, AES-GCM nonces) are fixed so the example is reproducible; production code MUST NOT reuse these values. Inputs server private (ssk) = 0102030405060708090a0b0c0d0e0f10 1112131415161718191a1b1c1d1e1f20 server public (spk) = 07a37cbc142093c8b755dc1b10e86cb4 26374ad16aa853ed0bdfc0b2b86d1c7c client private (csk) = a1a2a3a4a5a6a7a8a9aaabacadaeafb0 b1b2b3b4b5b6b7b8b9babbbcbdbebfc0 client public (cpk) = ad438bfae31f6c093d61d4339255ea79 8092c9fadd07b97827f4b0ae9dee7c1c kid = "2026-06" aead = "AES-256-GCM" issuer = "https://api.example.com" ts = 1781006400 nid = "3b1c1c2e-2b6a-4a0d-9b6c-2a9f1b6a0e21" cty = "application/json" Key Agreement and Derivation Vasylenko Expires 6 December 2026 [Page 30] Internet-Draft E2EE HTTP June 2026 Z = X25519(csk, spk) = 1eadf045f970f3619aa3a82d3ce461d6 8ee42839f0563ff052d8db20bf927d29 salt = cpk || spk (64 octets) info_req = "e2ee/v1:req https://api.example.com AES-256-GCM 2026-06" info_res = "e2ee/v1:res https://api.example.com AES-256-GCM 2026-06" EK_req = HKDF-SHA256(Z, salt, info_req, 32) = 88927bb69c7fce5a26b88ccf3b8638c5 e876080eae5349c7a014787e80382f81 EK_res = HKDF-SHA256(Z, salt, info_res, 32) = 2784f1a637499c327e97ad56a0a199b9 50680c41e57597cea41a220233304a8b Request The serialized E2EE-Session field (single logical line): E2EE-Session: "2026-06"; aead="AES-256-GCM"; epk=:rUOL+uMfbAk9YdQzklXqeYCSyfrdB7l4J/Swrp3ufBw=:; ts=1781006400; nid="3b1c1c2e-2b6a-4a0d-9b6c-2a9f1b6a0e21"; cty="application/json" The AAD is the ASCII string below, using deterministic Structured Fields serialization of the E2EE-Session field value: e2ee/v1:req "2026-06"; aead="AES-256-GCM"; epk=:...:; \ ts=1781006400; nid="3b1c1c2e-2b6a-4a0d-9b6c-2a9f1b6a0e21"; \ cty="application/json" (The :...: here stands for the full sf-binary form shown above and is shortened only for readability; the actual AAD contains the full deterministic serialization.) plaintext = {"op":"transfer","amount":1000,"to":"acct-42"} nonce = deadbeef0000000000000001 ciphertext = a6b3551bec16e7866943502146d893b2 baa8bc6a4ef76712f7e4febcb576c821 41551464b46eb0f096750ed69020 tag = 4cc3c77e4c463d111f81bf6cf83f08d5 Vasylenko Expires 6 December 2026 [Page 31] Internet-Draft E2EE HTTP June 2026 The HTTP request body is nonce || ciphertext || tag, base64-encoded here for compactness: body (base64) = 3q2+7wAAAAAAAAABprNVG+wW54ZpQ1AhRtiTsrqovGpO92cS 9+T+vLV2yCFBVRRktG6w8JZ1DtaQIEzDx35MRj0RH4G/bPg/CNU= Response The response uses a fresh nonce and a new ts, echoes kid, aead, and nid, and omits epk: E2EE-Session: "2026-06"; aead="AES-256-GCM"; ts=1781006401; nid="3b1c1c2e-2b6a-4a0d-9b6c-2a9f1b6a0e21"; cty="application/json" AAD (the request's deterministic field serialization followed by the response's deterministic field serialization): e2ee/v1:res plaintext = {"status":"ok","txid":"a1b2c3"} nonce = feedface0000000000000002 ciphertext = f111c0a217756b5f967108e32ce392d6 2f4de9380b2267c53b81cc4679bc59 tag = 5b64d39058d1bb23e2cec5f9c69880e1 body (base64) = /u36zgAAAAAAAAAC8RHAohd1a1+WcQjjLOOS1i9N6TgLImfFO4H MRnm8WVtk05BY0bsj4s7F+caYgOE= Author's Address Vitaliy Vasylenko Email: ietf@vitalvas.com Vasylenko Expires 6 December 2026 [Page 32]