HTTP D. Schinazi Internet-Draft Google LLC Intended status: Standards Track 22 July 2026 Expires: 23 January 2027 A Perfect Forward Secure Extension to Oblivious HTTP draft-schinazi-httpbis-ohttp-pfs-01 Abstract Oblivious HTTP (OHTTP) is a protocol for forwarding encrypted HTTP messages. It does not provide Perfect Forward Secrecy (PFS). Chunked OHTTP expands OHTTP to be suitable for longer-lived streams, but still does not offer PFS. Combined, this is leading sensitive traffic to de deployed at scale without PFS. This document proposes a solution. About This Document This note is to be removed before publishing as an RFC. The latest revision of this draft can be found at https://DavidSchinazi.github.io/draft-schinazi-httpbis-ohttp-pfs/ draft-schinazi-httpbis-ohttp-pfs.html. Status information for this document may be found at https://datatracker.ietf.org/doc/draft- schinazi-httpbis-ohttp-pfs/. Discussion of this document takes place on the HTTP Working Group mailing list (mailto:ietf-http-wg@w3.org), which is archived at https://lists.w3.org/Archives/Public/ietf-http-wg/. Working Group information can be found at https://httpwg.org/. Source for this draft and an issue tracker can be found at https://github.com/DavidSchinazi/draft-schinazi-httpbis-ohttp-pfs. 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/. Schinazi Expires 23 January 2027 [Page 1] Internet-Draft Perfect Forward Secure OHTTP July 2026 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 23 January 2027. 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. Please review these documents carefully, as they describe your rights and restrictions with respect to this document. Code Components extracted from this document must include Revised BSD License text as described in Section 4.e of the Trust Legal Provisions and are provided without warranty as described in the Revised BSD License. Table of Contents 1. Introduction . . . . . . . . . . . . . . . . . . . . . . . . 2 2. Conventions and Definitions . . . . . . . . . . . . . . . . . 3 3. Mechanism . . . . . . . . . . . . . . . . . . . . . . . . . . 3 4. Media Type . . . . . . . . . . . . . . . . . . . . . . . . . 6 5. Chunked OHTTP . . . . . . . . . . . . . . . . . . . . . . . . 6 6. Security Considerations . . . . . . . . . . . . . . . . . . . 8 7. IANA Considerations . . . . . . . . . . . . . . . . . . . . . 8 7.1. OHTTP-PFS HTTP Header Field . . . . . . . . . . . . . . . 8 7.2. Media Types . . . . . . . . . . . . . . . . . . . . . . . 8 8. References . . . . . . . . . . . . . . . . . . . . . . . . . 9 8.1. Normative References . . . . . . . . . . . . . . . . . . 9 8.2. Informative References . . . . . . . . . . . . . . . . . 10 Acknowledgments . . . . . . . . . . . . . . . . . . . . . . . . . 10 Author's Address . . . . . . . . . . . . . . . . . . . . . . . . 10 1. Introduction Oblivious HTTP ([OHTTP]) is a protocol for forwarding encrypted HTTP messages. It does not provide Perfect Forward Secrecy (PFS). Chunked OHTTP ([CHUNKED]) expands OHTTP to be suitable for longer- lived streams, but still does not offer PFS. Schinazi Expires 23 January 2027 [Page 2] Internet-Draft Perfect Forward Secure OHTTP July 2026 Unfortunately, providing a streaming abstraction over OHTTP makes it an attractive tool to provide privacy. This is leading application designers to build Remote Procedure Call (RPC) systems over this bidirectional stream, without realizing the security cost of losing PFS. When PFS is needed, a simpler solution is to avoid OHTTP entirely and instead use TLS ([TLS]) over HTTP CONNECT (see Section 9.3.6 of [HTTP]). However, completing the TLS handshake takes a full round trip. For latency-sensitive applications, TLS 0-RTT (see Section 2.3 of [TLS]) can be used; that has the advantage of allowing non-PFS data for the client's first flight and then PFS for all other data. TLS 0-RTT does however have one relevant limitation: it allows a TLS server to link distinct 0-RTT requests by encoding identifying information in TLS session tickets. That makes TLS 0-RTT unsuitable for use cases that require unlinkability between requests, which Chunked OHTTP provides. This document proposes a solution that offers PFS to all data sent over OHTTP apart from the client's first flight. This provides privacy and security properties similar to TLS 0-RTT over HTTP CONNECT without losing the performance nor request-correlation- prevention properties of OHTTP. This mechanism is designed to be backwards compatible with unextended OHTTP. 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 SerializePublicKey(), DeserializePublicKey(), GenerateKeyPair(), SetupBaseS(), SetupBaseR(), Export(), Seal(), and Open() from [HPKE]. 3. Mechanism This mechanism relies on the generation of two more ephemeral key pairs per OHTTP request: one for the client (denoted skC, pkC) and one for the gateway (not denoted since it only exists inside of SetupBaseS()). The client starts by generating a second ephemeral key pair, using the same KEM it has selected for this request: skC, pkC = GenerateKeyPair() Schinazi Expires 23 January 2027 [Page 3] Internet-Draft Perfect Forward Secure OHTTP July 2026 The client then adds the serialized public key SerializePublicKey(pkC) to its Binary HTTP ([BHTTP]) request headers using the "OHTTP-PFS" header. That header is a Structured Header Field Item of type Byte Sequence as defined in Section 3.3.5 of [STRUCTURED]. For example: ohttp-pfs: :dGhpcyBpcyBhIHB1YmxpYyBrZXk=: The client then encrypts the Binary HTTP request following the procedure in Section 4.3 of [OHTTP]. The gateway follows the procedure in that same section to recover the Binary HTTP request. The gateway then checks the request for the presence of the "ohttp- pfs" header to determine whether this extension is in use. If it is, it uses DeserializePublicKey() to parse the key then uses the HPKE receiver context (rctxt) from the OHTTP request as follows: 1. Export a secret, req_secret, from rctxt, using the string "OHTTP PFS Request Derivation" as the exporter_context parameter to context.Export; see Section 5.3 of [HPKE]. The length of this secret is Nsecret, the length of the shared secret for the KEM associated with context. 2. Build info2 by concatenating the ASCII-encoded string "OHTTP PFS Response", a zero byte, the values of key_id, kem_id, kdf_id, and aead_id, as one 8-bit integer and three 16-bit integers, respectively, each in network byte order, and req_secret. 3. Create a sending HPKE context by invoking SetupBaseS() (Section 5.1.1 of [HPKE]) with the public key of the client pkE and info2. This yields the context sctxt2 and an encapsulation key enc2. 4. Encrypt response by invoking the Seal() method on sctxt2 (Section 5.2 of [HPKE]) with empty associated data aad, yielding ciphertext ct2. 5. Concatenate enc2 and ct2, yielding an Encapsulated Response, enc_response. In pseudocode, this procedure is as follows: Schinazi Expires 23 January 2027 [Page 4] Internet-Draft Perfect Forward Secure OHTTP July 2026 req_secret = rctxt.Export("OHTTP PFS Request Derivation", Nsecret) info2 = concat(encode_str("OHTTP PFS Response"), encode(1, 0), encode(1, key_id), encode(2, kem_id), encode(2, kdf_id), encode(2, aead_id), req_secret) enc2, sctxt2 = SetupBaseS(pkC, info2) ct2 = sctxt2.Seal("", response) enc_response = concat(enc2, ct2) The client then reverses this process to extract the response. To decapsulate an Encapsulated Response, enc_response, it uses the HPKE sender context (sctxt) from the OHTTP request as follows: 1. Parses enc_request into enc2 and ct2 (indicated using the function parse() in pseudocode). Note that enc2 is of fixed- length, so there is no ambiguity in parsing this structure. 2. Export a secret, req_secret, from sctxt, using the string "OHTTP PFS Request Derivation" as the exporter_context parameter to context.Export; see Section 5.3 of [HPKE]. The length of this secret is Nsecret, the length of the shared secret for the KEM associated with context. 3. Build info2 by concatenating the ASCII-encoded string "OHTTP PFS Response", a zero byte, the values of key_id, kem_id, kdf_id, and aead_id, as one 8-bit integer and three 16-bit integers, respectively, each in network byte order, and req_secret. 4. Create a receiving HPKE context, rctxt2, by invoking SetupBaseR() (Section 5.1.1 of [HPKE]) with skE, enc2, and info2. 5. Decrypt ct2 by invoking the Open() method on rctxt2 (Section 5.2 of [HPKE]), with an empty associated data aad, yielding response or an error on failure. In pseudocode, this procedure is as follows: Schinazi Expires 23 January 2027 [Page 5] Internet-Draft Perfect Forward Secure OHTTP July 2026 enc2, ct2 = parse(enc_response) req_secret = sctxt.Export("OHTTP PFS Request Derivation", Nsecret) info2 = concat(encode_str("OHTTP PFS Response"), encode(1, 0), encode(1, key_id), encode(2, kem_id), encode(2, kdf_id), encode(2, aead_id), req_secret) rctxt2 = SetupBaseR(enc2, skE, info2) response, error = rctxt2.Open("", ct2) 4. Media Type This mechanism uses a media type to differentiate the response from OHTTP responses without this extension. The "message/ohttp-res-pfs" is used to indicate that this extension is in use for the response. Gateways MAY respond with "message/ohttp-res" responses and use the response encoding described in Section 4.2 of [OHTTP] if it does not PFS to be useful for the response. This allows saving computational resources, for example, if the response is a generic response with an error status code where no part of the response is tied to information in the request. If chunked OHTTP [CHUNKED] is in use, the response media type for this extension is "message/ohttp-chunked-res-pfs". 5. Chunked OHTTP Chunked OHTTP ([CHUNKED]) is different in that the client can send more data after its first flight. For the reponse, all chunks are encrypted by the gateway using the sctxt2 context and decrypted by the client using the rctxt2 context, and using the AAD construction described in [CHUNKED]. For the request, there is a distinction between the first flight and subsequent chunks. The first chunks are encrypted using sctxt as described in [CHUNKED]. The client keeps using sctxt to send subsequent chunks until is receives the first chunk from the gateway. Once the client receives that, it creates rctxt2 (see Section 3), and performs the following steps to switch to it for sending: 1. Encrypt an empty chunk, the encrypted last first flight chunk elff_chunk with sctxt using empty associated data aad. Send this chunk with a variable-length integer length prefix. Schinazi Expires 23 January 2027 [Page 6] Internet-Draft Perfect Forward Secure OHTTP July 2026 2. Use that as the associated data aad to encrypt the first PFS chunk sealed_pfs_chunk1 using sctxt2. Send that chunk with a variable-length integer length prefix. 3. Encrypt all subsequent non-final chunks with an empty AAD and a variable-length integer length prefix, using sctxt2. 4. Encrypt the final chunk with the AAD set to the ASCII-encoded string "final" and prefixed with a zero length, using sctxt2. In pseudocode, this procedure is as follows: elff_chunk = sctxt.Seal("", "") elff_chunk_len = varint_encode(len(elff_chunk)) send(concat(elff_chunk_len, elff_chunk)) sealed_pfs_chunk1 = sctxt2.Seal(elff_chunk, pfs_chunk1) sealed_pfs_chunk1_len = varint_encode(len(sealed_pfs_chunk1)) send(concat(sealed_pfs_chunk1_len, sealed_pfs_chunk1)) sealed_pfs_chunk2 = sctxt2.Seal("", pfs_chunk2) sealed_pfs_chunk_len = varint_encode(len(sealed_pfs_chunk)) send(concat(sealed_pfs_chunk_len, sealed_pfs_chunk2)) ... sealed_final_chunk = sctxt2.Seal("final", chunk) send(concat(varint_encode(0), sealed_final_chunk)) The gateway needs to handle this transition. It initially handles chunks using the procedure from [CHUNKED]. When the gateway decrypts a chunk with a zero-length plaintext, this means that the client is transitioning to the PFS context (zero-length plaintext non-final chunks are explicitly disallowed in chunked OHTTP without PFS, see Section 6 of [CHUNKED]). When the gateway decrypts its first zero-length plaintext using the rctxt context, it acts on the ciphertext elff_chunk as follows: 1. Save elff_chunk until the next chunk is received. 2. Wait for and parse the next variable-length integer length- prefixed chunk, sealed_pfs_chunk1. 3. Decrypt sealed_pfs_chunk1 using rctxt2 with the associated data aad set to elff_chunk. 4. Keep decrypting variable-length integer length-prefixed chunks with the associated data aad empty. Schinazi Expires 23 January 2027 [Page 7] Internet-Draft Perfect Forward Secure OHTTP July 2026 5. When encoutering a variable-length integer length-prefix set to zero, decrypt the final chunk using the associated data aad set to the ASCII-encoded string "final". 6. Security Considerations The security considerations described in Section 6 of [OHTTP] and Section 7 of [CHUNKED] apply to this document as well. As mentioned in Section 7.2 of [CHUNKED], interactivity can cause the gateway to learn the round trip to the client. The PFS mechanism can increase that risk if the client sends its last first flight chunk (see Section 5) immediately when it receives the gateway's first flight. To avoid this, clients MUST NOT send their last first flight chunk until they have more application data to send to the gateway. 7. IANA Considerations 7.1. OHTTP-PFS HTTP Header Field This document requests IANA to register the following entry in the "Hypertext Transfer Protocol (HTTP) Field Name Registry" maintained at : Field Name: OHTTP-PFS Template: None Status: provisional (permanent if this document is approved) Reference: This document Comments: None 7.2. Media Types This document requests IANA to register the "message/ohttp-res-pfs" and "message/ohttp-chunked-res-pfs" media types in the "Media Types Registry" maintained at . The subtype names are "ohttp-res-pfs" and "ohttp-chunked-res-pfs", respectively. The other fields have the following values: Type name: message Required parameters: N/A Optional parameters: N/A Encoding considerations: "binary" Security considerations: see Section 6 Interoperability considerations: N/A Published specification: This document Applications that use this media type: Oblivious HTTP and Schinazi Expires 23 January 2027 [Page 8] Internet-Draft Perfect Forward Secure OHTTP July 2026 applications that use Oblivious HTTP use this media type to identify encapsulated binary HTTP responses when using the PFS extension. Fragment identifier considerations: N/A Additional information: Magic number(s): N/A Deprecated alias names for this type: N/A File extension(s): N/A Macintosh file type code(s): N/A Person and email address to contact for further information: see Authors' Addresses section Intended usage: COMMON Restrictions on usage: N/A Author: see Authors' Addresses section Change controller: IETF 8. References 8.1. Normative References [BHTTP] Thomson, M. and C. A. Wood, "Binary Representation of HTTP Messages", RFC 9292, DOI 10.17487/RFC9292, August 2022, . [CHUNKED] Pauly, T. and M. Thomson, "Chunked Oblivious HTTP Messages", Work in Progress, Internet-Draft, draft-ietf- ohai-chunked-ohttp-08, 18 February 2026, . [HPKE] Barnes, R., Bhargavan, K., Lipp, B., and C. Wood, "Hybrid Public Key Encryption", RFC 9180, DOI 10.17487/RFC9180, February 2022, . [OHTTP] Thomson, M. and C. A. Wood, "Oblivious HTTP", RFC 9458, DOI 10.17487/RFC9458, January 2024, . [RFC2119] Bradner, S., "Key words for use in RFCs to Indicate Requirement Levels", BCP 14, RFC 2119, DOI 10.17487/RFC2119, March 1997, . [RFC8174] Leiba, B., "Ambiguity of Uppercase vs Lowercase in RFC 2119 Key Words", BCP 14, RFC 8174, DOI 10.17487/RFC8174, May 2017, . Schinazi Expires 23 January 2027 [Page 9] Internet-Draft Perfect Forward Secure OHTTP July 2026 [STRUCTURED] Nottingham, M. and P. Kamp, "Structured Field Values for HTTP", RFC 9651, DOI 10.17487/RFC9651, September 2024, . 8.2. Informative References [HTTP] Fielding, R., Ed., Nottingham, M., Ed., and J. Reschke, Ed., "HTTP Semantics", STD 97, RFC 9110, DOI 10.17487/RFC9110, June 2022, . [TLS] Rescorla, E., "The Transport Layer Security (TLS) Protocol Version 1.3", RFC 8446, DOI 10.17487/RFC8446, August 2018, . Acknowledgments Thank you to Martin Thomson for asking me (https://mailarchive.ietf.org/arch/msg/ohai/ Vrh25BxK4wmIDJxeRYrYj6U1-g0/) to write this draft, and to Chris Wood for supporting it (https://mailarchive.ietf.org/arch/msg/ohai/ bLD_klUzBwVHUPeXI4a5NRIaoT8/) more than two years before it was written. Thanks to Martin Thomson for reviewing this document. Author's Address David Schinazi Google LLC Email: dschinazi.ietf@gmail.com Schinazi Expires 23 January 2027 [Page 10]