Skip to main content

Crate hpke_http

Crate hpke_http 

Source
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§

AuthenticatedRequest
Result of authentication, paused before replay admission and plaintext release.
AuthenticatedStreamRequest
Authenticated START, with plaintext withheld until replay admission.
BuildInfo
Immutable engine identity returned before a binding accepts secrets.
Client
A client configured for one recipient key and one explicit PSK identity.
CredentialRequest
Public information needed by a host credential resolver.
HeaderField
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.
OpenedRequest
A fully authenticated request and its one-use response capability.
OpenedStreamRequest
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.
PreparsedRequest
Result of the bounded public preparse stage.
PreparsedStreamRequest
Public credential ID and a one-use bounded START token.
ProtectedRequest
A request envelope and the one-use capability needed to open its response.
ReplayDecision
One atomic replay-provider result bound to its request ID.
ReplayRequest
Stable request identifier passed to an atomic replay-admission provider.
ReplayToken
Opaque one-use continuation after request authentication.
Request
A complete plaintext request supplied to or returned by the engine.
RequestHead
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.
ResponseCapability
One-use server capability for protecting the response to one opened request.
ResponseHead
Fields released after a valid START tag.
ResponseOpener
A checked record reader that accepts any byte cuts.
ResponseSealer
One response writer. Its key and sequence cannot be reused after failure.
ResponseToken
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.
StartToken
Opaque one-use continuation after public preparse.
StreamReplayToken
One-use continuation after START authentication.
StreamRequestOpener
Incremental request reader with bounded frame storage.
StreamRequestSealer
One request writer. A failed call closes it and prevents a valid END.
StreamStartToken
Opaque START bytes before credential resolution.
SystemEntropy
The operating system or Web Crypto entropy source selected by getrandom.

Enums§

Error
Errors returned by the protocol engine.
Method
Supported HTTP request methods.
ResponseMode
The checked response kind, based on its single Content-Type field.
ResponseRecord
One checked record. Finite body bytes remain in the opener until END.
StreamRequestRecord
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§

EntropySource
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.