@hpke/ohttp
Version:
Oblivious HTTP (RFC 9458) client and server using HPKE
169 lines (126 loc) • 5.01 kB
Markdown
<h1 align="center">@hpke/ohttp</h1>
<div align="center">
<a href="https://www.npmjs.com/package/@hpke/ohttp"><img src="https://img.shields.io/npm/v/@hpke/ohttp" alt="npm"/></a>
<a href="https://jsr.io/@hpke/ohttp"><img src="https://jsr.io/badges/@hpke/ohttp" alt="JSR"/></a>
</div>
<div align="center">
An <a href="https://www.rfc-editor.org/rfc/rfc9458">Oblivious HTTP (OHTTP)</a> client and server implementation using HPKE.
</div>
<p></p>
<div align="center">
Documentation: [jsr.io](https://jsr.io/@hpke/ohttp/doc)
</div>
This package is distributed as ESM only and requires Node.js 20 or later. It
implements the OHTTP client, gateway and key configuration formats defined by
RFC 9458.
- [Installation](
- [Node.js](
- [Deno](
- [Web Browsers](
- [Usage](
- [Fetch-like API](
- [Low-level API](
- [Contributing](
You can install the package with npm, yarn or pnpm.
```sh
npm install @hpke/ohttp @hpke/core @hpke/dhkem-x25519
yarn add @hpke/ohttp @hpke/core @hpke/dhkem-x25519
pnpm install @hpke/ohttp @hpke/core @hpke/dhkem-x25519
npx jsr add @hpke/ohttp @hpke/core @hpke/dhkem-x25519
yarn add jsr:@hpke/ohttp jsr:@hpke/core jsr:@hpke/dhkem-x25519
pnpm add jsr:@hpke/ohttp jsr:@hpke/core jsr:@hpke/dhkem-x25519
```
The above manner can be used with other JavaScript runtimes that support npm,
such as Cloudflare Workers and Bun.
For Deno, it is recommended to use the jsr.io registry.
```sh
deno add jsr:@hpke/ohttp jsr:@hpke/core jsr:@hpke/dhkem-x25519
```
This ESM package can be loaded from a CDN such as
[](https://esm.sh/@hpke/ohttp) or [unpkg](https://unpkg.com/@hpke/ohttp/).
Both the client and the server (Oblivious Gateway Resource) are created with an
asynchronous factory function that takes the HPKE primitives to use:
```ts
import { Aes128Gcm, HkdfSha256 } from "@hpke/core";
import { DhkemX25519HkdfSha256 } from "@hpke/dhkem-x25519";
import { createOhttpClient, createOhttpServer } from "@hpke/ohttp";
const kem = new DhkemX25519HkdfSha256();
const suites = [{ kdf: new HkdfSha256(), aead: new Aes128Gcm() }];
// Gateway: a key pair is generated unless `keyPair` is given.
const server = await createOhttpServer({ keyId: 1, kem, suites });
// Client: `server.keyConfig` is the `application/ohttp-keys` payload
// published by the gateway.
const client = await createOhttpClient({
kem,
suites,
keyConfig: server.keyConfig,
relayUrl: "https://relay.example/ohttp",
});
```
`client.fetch()` mirrors the standard `fetch()`: it encapsulates the request,
sends it to the relay and decrypts the response. Its counterpart
`server.handle()` takes the HTTP request forwarded by the relay, passes the
decrypted request to your handler and returns the encapsulated response:
```ts
// Client
const response = await client.fetch("https://target.example/hello");
console.log(await response.text());
// Gateway (e.g. Deno.serve, Cloudflare Workers, Bun.serve)
Deno.serve((request) =>
server.handle(request, (targetRequest) => fetch(targetRequest))
);
```
`server.handle()` answers requests that cannot be processed with a 4xx response
without calling the handler. Errors thrown by the handler are propagated to the
caller.
To exchange the encapsulated messages with the relay by yourself, use
`client.encapsulateRequest()` and `server.decapsulateRequest()`. The following
example performs an OHTTP request and response roundtrip without a relay:
```ts
import { Aes128Gcm, HkdfSha256 } from "@hpke/core";
import { DhkemX25519HkdfSha256 } from "@hpke/dhkem-x25519";
import { createOhttpClient, createOhttpServer } from "@hpke/ohttp";
async function main() {
const kem = new DhkemX25519HkdfSha256();
const suites = [{ kdf: new HkdfSha256(), aead: new Aes128Gcm() }];
const server = await createOhttpServer({ keyId: 1, kem, suites });
const client = await createOhttpClient({
kem,
suites,
keyConfig: server.keyConfig,
});
// Client: encrypt the request (message/ohttp-req).
const { encapsulatedRequest, decapsulateResponse } = await client
.encapsulateRequest(new Request("https://target.example/hello"));
// Gateway: decrypt the request and encrypt the response
// (message/ohttp-res).
const { request, encapsulateResponse } = await server.decapsulateRequest(
encapsulatedRequest,
);
console.log(request.method, request.url);
const encapsulatedResponse = await encapsulateResponse(
new Response("Hello, OHTTP!"),
);
// Client: decrypt the response.
const response = await decapsulateResponse(encapsulatedResponse);
console.log(await response.text());
}
main().catch((err: unknown) => {
console.error("OHTTP failed:", err);
});
```
We welcome all kind of contributions, filing issues, suggesting new features or
sending PRs.
MIT