Expand description
HPKE requests and checked response records for HTTP.
This crate implements PROTOCOL_ID hpke-http/3. The
central protocol specification
gives the exact request, response, and HHKD v2 bytes; cryptographic inputs;
limits; checks; and client and server steps. It links the frozen wire vectors.
Package versions and BINDING_ABI_VERSION do not change the wire version.
The crate does no network I/O. A host uses HTTPS, resolves PSKs, and makes one atomic replay decision shared by its workers. The public PSK ID is an untrusted lookup hint until START authentication passes. An admitted request gives one response right. The host checks the full request, END, real outer body EOF, and logical target policy before it sends clear bytes to the app.
Client::protect and Server::preparse serve complete bounded calls.
Client::begin_stream and Server::preparse_stream serve uploads. The
stream path checks replay after START; the complete path checks all
records before replay. Both paths withhold clear request data until the
host admits replay. The host reports real outer EOF to ResponseOpener
after END; the opener then gives a finite response. It gives each checked
SSE block after that block passes its own checks.
The examples below show API use. Use the linked specification to build another language implementation or to read the exact wire contract.
§Complete transaction
The host must make a shared atomic replay decision before it admits a request. This example passes complete request and response bytes directly, in place of HTTP bodies, to show the API sequence.
use hpke_http::{Client, Limits, Method, Request, Response, Server, generate_key_pair};
fn main() -> Result<(), hpke_http::Error> {
let keys = generate_key_pair()?;
let (private_key, public_key) = keys.into_parts();
let psk = b"example pre-shared key with 32 bytes!!";
let client = Client::new(
&public_key, b"key-1".to_vec(), psk.to_vec(), b"tenant-1".to_vec(),
Limits::default(),
)?;
let server = Server::new(&private_key, b"key-1".to_vec(), Limits::default())?;
let request = Request {
method: Method::Post,
authority: b"api.example.test".to_vec(),
path: b"/items".to_vec(),
headers: Vec::new(),
body: b"hello".to_vec(),
};
let (request_bytes, response_right) = client.protect(&request)?.into_parts();
let parsed = server.preparse(&request_bytes)?;
let authenticated = server.authenticate(parsed.token, psk)?;
let opened = authenticated.token.admit(authenticated.replay.decision(true))?;
assert_eq!(opened.request, request);
let reply = Response { status: 200, headers: Vec::new(), body: b"ok".to_vec() };
let reply_bytes = opened.response.protect_finite(&reply)?;
assert_eq!(response_right.open_finite(&reply_bytes)?, reply);
Ok(())
}§Streamed upload
Send the begin_stream bytes first, then each record from push, then the
bytes from finish. End the outer HTTP body after finish. On the server,
hold checked DATA until the host observes real outer EOF and finish_eof
checks END. This example accepts one replay decision and passes bytes
directly between the client and server in place of an HTTP transport.
A host must use a shared atomic replay store:
use hpke_http::{
Client, Error, Limits, Method, RequestHead, Server, StreamRequestOpener,
StreamRequestRecord, generate_key_pair,
};
fn feed(reader: &mut StreamRequestOpener, bytes: &[u8], body: &mut Vec<u8>) -> Result<(), Error> {
let mut remaining = bytes;
while !remaining.is_empty() {
let (used, record) = reader.feed(remaining)?;
remaining = &remaining[used..];
if let Some(StreamRequestRecord::Data(part)) = record {
body.extend_from_slice(&part);
}
}
Ok(())
}
fn main() -> Result<(), Error> {
let keys = generate_key_pair()?;
let (private_key, public_key) = keys.into_parts();
let psk = b"example pre-shared key with 32 bytes!!";
let client = Client::new(&public_key, b"key-1".to_vec(), psk.to_vec(), b"tenant-1".to_vec(), Limits::default())?;
let server = Server::new(&private_key, b"key-1".to_vec(), Limits::default())?;
let head = RequestHead {
method: Method::Post,
authority: b"api.example.test".to_vec(),
path: b"/upload".to_vec(),
headers: Vec::new(),
};
let (mut writer, first) = client.begin_stream(&head)?;
let parsed = server.preparse_stream(&first)?;
let authenticated = server.authenticate_stream(parsed.token, psk)?;
let opened = authenticated.token.admit(authenticated.replay.decision(true))?;
let mut reader = opened.reader;
let mut body = Vec::new();
for part in [b"hello".as_slice(), b" world".as_slice()] {
let mut remaining = part;
while !remaining.is_empty() {
let (used, record) = writer.push(remaining)?;
remaining = &remaining[used..];
if let Some(record) = record {
feed(&mut reader, &record, &mut body)?;
}
}
}
let (end, _response_right) = writer.finish()?;
feed(&mut reader, &end, &mut body)?;
let _server_response_right = reader.finish_eof()?;
assert_eq!(body.as_slice(), b"hello world");
Ok(())
}Structs§
- Authenticated
Request - Result of authentication, paused before replay admission and plaintext release.
- Authenticated
Stream Request - Authenticated START, with plaintext withheld until replay admission.
- Build
Info - Immutable engine identity returned before a binding accepts secrets.
- Client
- A client configured for one recipient key and one explicit PSK identity.
- Credential
Request - Public information needed by a host credential resolver.
- Header
Field - One canonical end-to-end HTTP field in an ordered field section.
- KeyPair
- A generated X25519 recipient key pair.
- Limits
- Per-engine limits. Set values within the hard limits.
- Opened
Request - A fully authenticated request and its one-use response capability.
- Opened
Stream Request - Checked request head and a live body reader. The host checks its logical target policy and holds DATA from the app until END and real outer EOF.
- Preparsed
Request - Result of the bounded public preparse stage.
- Preparsed
Stream Request - Public credential ID and a one-use bounded START token.
- Protected
Request - A request envelope and the one-use capability needed to open its response.
- Replay
Decision - One atomic replay-provider result bound to its request ID.
- Replay
Request - Stable request identifier passed to an atomic replay-admission provider.
- Replay
Token - Opaque one-use continuation after request authentication.
- Request
- A complete plaintext request supplied to or returned by the engine.
- Request
Head - Request fields without a body. Client input before protection and checked fields after server admission.
- Response
- A complete plaintext response supplied to or returned by the engine.
- Response
Capability - One-use server capability for protecting the response to one opened request.
- Response
Head - Fields released after a valid START tag.
- Response
Opener - A checked record reader that accepts any byte cuts.
- Response
Sealer - One response writer. Its key and sequence cannot be reused after failure.
- Response
Token - One-use client capability for opening the response to one request.
- Server
- A server with one advertised recipient key and other accepted keys.
- SseSplitter
- One bounded partial SSE block. Line endings on the wire are LF.
- Start
Token - Opaque one-use continuation after public preparse.
- Stream
Replay Token - One-use continuation after START authentication.
- Stream
Request Opener - Incremental request reader with bounded frame storage.
- Stream
Request Sealer - One request writer. A failed call closes it and prevents a valid END.
- Stream
Start Token - Opaque START bytes before credential resolution.
- System
Entropy - The operating system or Web Crypto entropy source selected by
getrandom.
Enums§
- Error
- Errors returned by the protocol engine.
- Method
- Supported HTTP request methods.
- Response
Mode - The checked response kind, based on its single Content-Type field.
- Response
Record - One checked record. Finite body bytes remain in the opener until END.
- Stream
Request Record - One checked upload record.
Constants§
- BINDING_
ABI_ VERSION - Boundary ABI version used by the first-party bindings.
- CLOCK_
SKEW_ SECS - Maximum accepted client/server clock difference (30 seconds).
- HARD_
MAX_ BODY_ LEN - Absolute body limit accepted by the implementation (64 MiB).
- HARD_
MAX_ HEADER_ BYTES - Absolute combined header-name and header-value limit (64 KiB).
- HARD_
MAX_ HEADER_ COUNT - Absolute number of header fields accepted in one message.
- HARD_
MAX_ ID_ LEN - Absolute public identifier limit accepted by the implementation.
- HARD_
MAX_ REQUEST_ BYTES - Absolute clear request body limit (4 GiB).
- HARD_
MAX_ TARGET_ LEN - Absolute combined authority and path limit (8 KiB).
- PROTOCOL_
ID - Language-neutral protocol identifier.
- REQUEST_
LIFETIME_ SECS - Time for which a newly created request can be accepted (five minutes).
- STREAM_
DATA_ LEN - Largest clear DATA part in a protected upload.
Traits§
- Entropy
Source - A source of cryptographically secure random bytes.
Functions§
- build_
info - Return the engine identity used for binding skew checks.
- generate_
key_ pair - Generate a recipient key pair with the platform entropy source.
- generate_
key_ pair_ with_ entropy - Generate a recipient key pair with an injected fallible entropy source.