End-to-End Encryption for HTTP APIs Using X25519 and AES-GCM
draft-vasylenko-e2ee-http-00
This document is an Internet-Draft (I-D).
Anyone may submit an I-D to the IETF.
This I-D is not endorsed by the IETF and has no formal standing in the
IETF standards process.
| Document | Type | Active Internet-Draft (individual) | |
|---|---|---|---|
| Author | Vitaliy Vasylenko | ||
| Last updated | 2026-06-04 | ||
| RFC stream | (None) | ||
| Intended RFC status | (None) | ||
| Formats | |||
| Stream | Stream state | (No stream defined) | |
| Consensus boilerplate | Unknown | ||
| RFC Editor Note | (None) | ||
| IESG | IESG state | I-D Exists | |
| Telechat date | (None) | ||
| Responsible AD | (None) | ||
| Send notices to | (None) |
draft-vasylenko-e2ee-http-00
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: "<kid>"; |
| aead="AES-256-GCM"; |
| epk=:<32B base64>:; ts=<int>; |
| nid="<uuid>" |
| Content-Type: application/e2ee |
| Body: nonce || ciphertext || tag |
|--------------------------------------------->|
| |
| shared = X25519(...) |
| EK_req, EK_res = ... |
| decrypt, process |
| |
| 200 OK |
| E2EE-Session: "<kid>"; |
| aead="AES-256-GCM"; ts=<int> |
| 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:<code>
where <code> 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:<code>, where <code> 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,
<https://www.rfc-editor.org/rfc/rfc2119>.
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,
<https://www.rfc-editor.org/rfc/rfc3339>.
[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, <https://www.rfc-editor.org/rfc/rfc3553>.
[RFC4648] Josefsson, S., "The Base16, Base32, and Base64 Data
Encodings", RFC 4648, DOI 10.17487/RFC4648, October 2006,
<https://www.rfc-editor.org/rfc/rfc4648>.
[RFC5869] Krawczyk, H. and P. Eronen, "HMAC-based Extract-and-Expand
Key Derivation Function (HKDF)", RFC 5869,
DOI 10.17487/RFC5869, May 2010,
<https://www.rfc-editor.org/rfc/rfc5869>.
[RFC6838] Freed, N., Klensin, J., and T. Hansen, "Media Type
Specifications and Registration Procedures", BCP 13,
RFC 6838, DOI 10.17487/RFC6838, January 2013,
<https://www.rfc-editor.org/rfc/rfc6838>.
[RFC7748] Langley, A., Hamburg, M., and S. Turner, "Elliptic Curves
for Security", RFC 7748, DOI 10.17487/RFC7748, January
2016, <https://www.rfc-editor.org/rfc/rfc7748>.
[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,
<https://www.rfc-editor.org/rfc/rfc8126>.
[RFC8174] Leiba, B., "Ambiguity of Uppercase vs Lowercase in RFC
2119 Key Words", BCP 14, RFC 8174, DOI 10.17487/RFC8174,
May 2017, <https://www.rfc-editor.org/rfc/rfc8174>.
[RFC8446] Rescorla, E., "The Transport Layer Security (TLS) Protocol
Version 1.3", RFC 8446, DOI 10.17487/RFC8446, August 2018,
<https://www.rfc-editor.org/rfc/rfc8446>.
[RFC8470] Thomson, M., Nottingham, M., and W. Tarreau, "Using Early
Data in HTTP", RFC 8470, DOI 10.17487/RFC8470, September
2018, <https://www.rfc-editor.org/rfc/rfc8470>.
[RFC8615] Nottingham, M., "Well-Known Uniform Resource Identifiers
(URIs)", RFC 8615, DOI 10.17487/RFC8615, May 2019,
<https://www.rfc-editor.org/rfc/rfc8615>.
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,
<https://www.rfc-editor.org/rfc/rfc9110>.
[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, <https://www.rfc-editor.org/rfc/rfc9325>.
[RFC9421] Backman, A., Ed., Richer, J., Ed., and M. Sporny, "HTTP
Message Signatures", RFC 9421, DOI 10.17487/RFC9421,
February 2024, <https://www.rfc-editor.org/rfc/rfc9421>.
[RFC9457] Nottingham, M., Wilde, E., and S. Dalal, "Problem Details
for HTTP APIs", RFC 9457, DOI 10.17487/RFC9457, July 2023,
<https://www.rfc-editor.org/rfc/rfc9457>.
[RFC9530] Polli, R. and L. Pardue, "Digest Fields", RFC 9530,
DOI 10.17487/RFC9530, February 2024,
<https://www.rfc-editor.org/rfc/rfc9530>.
[RFC9651] Nottingham, M. and P. Kamp, "Structured Field Values for
HTTP", RFC 9651, DOI 10.17487/RFC9651, September 2024,
<https://www.rfc-editor.org/rfc/rfc9651>.
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,
<https://www.rfc-editor.org/rfc/rfc6973>.
[RFC9000] Iyengar, J., Ed. and M. Thomson, Ed., "QUIC: A UDP-Based
Multiplexed and Secure Transport", RFC 9000,
DOI 10.17487/RFC9000, May 2021,
<https://www.rfc-editor.org/rfc/rfc9000>.
[VASYLENKO-BLOG]
Vasylenko, V., "End-to-End Encryption for APIs: X25519 and
AES", 27 July 2025,
<https://blog.vitalvas.com/post/2025/07/27/e2e-encryption-
api-x25519-aes/>.
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 <request-encryption-field> <response-encryption-field>
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]