Package Exports
- ohttp-ts
Readme
ohttp-ts
TypeScript implementation of Oblivious HTTP (RFC 9458) with streaming support.
Features
- RFC 9458 - Oblivious HTTP
- Chunked OHTTP - Streaming extension (draft-ietf-ohai-chunked-ohttp-08)
- WebCrypto - Works in browsers, Cloudflare Workers, Node.js 22+
Installation
npm install ohttp-ts hpkeQuick Start
import { CipherSuite, KEM_DHKEM_X25519_HKDF_SHA256, KDF_HKDF_SHA256, AEAD_AES_128_GCM } from "hpke";
import { KeyConfig, OHTTPClient, OHTTPServer, KdfId, AeadId } from "ohttp-ts";
// Gateway: generate key configuration
const suite = new CipherSuite(KEM_DHKEM_X25519_HKDF_SHA256, KDF_HKDF_SHA256, AEAD_AES_128_GCM);
const keyConfig = await KeyConfig.generate(suite, 0x01, [
{ kdfId: KdfId.HKDF_SHA256, aeadId: AeadId.AES_128_GCM },
]);
const gateway = new OHTTPServer([keyConfig]);
// Client: fetch and parse gateway's public key
const publicKeyBytes = KeyConfig.serialize(keyConfig);
const clientKeyConfig = KeyConfig.parse(publicKeyBytes);
const client = new OHTTPClient(suite, clientKeyConfig);
// Client: encapsulate HTTP request
const httpRequest = new Request("https://target.example/api", {
method: "POST",
body: JSON.stringify({ data: "sensitive" }),
});
const { request: relayRequest, context } = await client.encapsulateRequest(
httpRequest,
"https://relay.example/ohttp",
);
// Send to relay: fetch(relayRequest)
// Gateway: decapsulate request
const { request: innerRequest, context: serverContext } = await gateway.decapsulateRequest(relayRequest);
// innerRequest is the original Request object
// Gateway: encapsulate response
const httpResponse = new Response(JSON.stringify({ result: "ok" }), { status: 200 });
const encapsulatedResponse = await serverContext.encapsulateResponse(httpResponse);
// Client: decapsulate response
const innerResponse = await context.decapsulateResponse(encapsulatedResponse);
// innerResponse is the original Response objectProtocol Flow
+---------+ +-------+ +---------+ +--------+
| Client | | Relay | | Gateway | | Target |
+---------+ +-------+ +---------+ +--------+
| | | |
| Encapsulated | | |
| Request | | |
+--------------->| Forward | |
| +--------------->| Decrypt & |
| | | Forward |
| | +------------>|
| | | |
| | |<------------+
| | | Encrypt |
| |<---------------+ Response |
|<---------------+ | |
| Decapsulated | | |
| Response | | |Binary HTTP
OHTTP encapsulates Binary HTTP (RFC 9292) messages. The high-level API (encapsulateRequest, decapsulateRequest, etc.) handles encoding automatically.
For advanced use cases, the low-level bytes API is also available:
// Low-level API: work with raw Binary HTTP bytes
const { encapsulatedRequest, context } = await client.encapsulate(binaryHttpBytes);
const { request: binaryBytes, context: serverCtx } = await gateway.decapsulate(encapsulatedRequest);See examples/bhttp.example.ts for a complete example.
Chunked OHTTP
For streaming large responses, use ChunkedOHTTPClient/ChunkedOHTTPServer with the low-level bytes API. The high-level Request/Response API requires buffering the entire body for Binary HTTP encoding, so chunked mode operates on raw bytes only.
Examples
| Example | Description |
|---|---|
ohttp.example.ts |
Basic OHTTP round-trip |
chunked.example.ts |
Streaming with chunked OHTTP (low-level bytes API) |
bhttp.example.ts |
Request/Response API |
mlkem.example.ts |
Post-quantum with ML-KEM-768 |
Post-Quantum Support
For post-quantum key encapsulation (ML-KEM), use @panva/hpke-noble:
npm install @panva/hpke-nobleimport { CipherSuite } from "hpke";
import { KEM_ML_KEM_768, KDF_HKDF_SHA256, AEAD_AES_128_GCM } from "@panva/hpke-noble";
const suite = new CipherSuite(KEM_ML_KEM_768, KDF_HKDF_SHA256, AEAD_AES_128_GCM);
// Use with KeyConfig.generate(), OHTTPClient, OHTTPServer as usualSecurity Considerations
Not audited. Use at your own risk.
- Replay protection is out of scope (RFC 9458 Section 6.5)
- Decryption errors are opaque to prevent oracle attacks
License
MIT