Skip to main content

hpke_http/
lib.rs

1//! HPKE requests and checked response records for HTTP.
2//!
3//! This crate implements `PROTOCOL_ID` hpke-http/3. The
4//! [central protocol specification](https://github.com/dualeai/hpke-http/blob/main/PROTOCOL.md)
5//! gives the exact request, response, and HHKD v2 bytes; cryptographic inputs;
6//! limits; checks; and client and server steps. It links the frozen wire vectors.
7//! Package versions and `BINDING_ABI_VERSION` do not change the wire version.
8//!
9//! The crate does no network I/O. A host uses HTTPS, resolves PSKs, and makes
10//! one atomic replay decision shared by its workers. The public PSK ID is an
11//! untrusted lookup hint until START authentication passes. An admitted
12//! request gives one response right. The host checks the full request, END,
13//! real outer body EOF, and logical target policy before it sends clear bytes
14//! to the app.
15//!
16//! `Client::protect` and `Server::preparse` serve complete bounded calls.
17//! `Client::begin_stream` and `Server::preparse_stream` serve uploads. The
18//! stream path checks replay after START; the complete path checks all
19//! records before replay. Both paths withhold clear request data until the
20//! host admits replay. The host reports real outer EOF to `ResponseOpener`
21//! after END; the opener then gives a finite response. It gives each checked
22//! SSE block after that block passes its own checks.
23//!
24//! The examples below show API use. Use the linked specification to build
25//! another language implementation or to read the exact wire contract.
26//!
27//! # Complete transaction
28//!
29//! The host must make a shared atomic replay decision before it admits a
30//! request. This example passes complete request and response bytes directly,
31//! in place of HTTP bodies, to show the API sequence.
32//!
33//! ```
34//! use hpke_http::{Client, Limits, Method, Request, Response, Server, generate_key_pair};
35//!
36//! fn main() -> Result<(), hpke_http::Error> {
37//!     let keys = generate_key_pair()?;
38//!     let (private_key, public_key) = keys.into_parts();
39//!     let psk = b"example pre-shared key with 32 bytes!!";
40//!     let client = Client::new(
41//!         &public_key, b"key-1".to_vec(), psk.to_vec(), b"tenant-1".to_vec(),
42//!         Limits::default(),
43//!     )?;
44//!     let server = Server::new(&private_key, b"key-1".to_vec(), Limits::default())?;
45//!     let request = Request {
46//!         method: Method::Post,
47//!         authority: b"api.example.test".to_vec(),
48//!         path: b"/items".to_vec(),
49//!         headers: Vec::new(),
50//!         body: b"hello".to_vec(),
51//!     };
52//!     let (request_bytes, response_right) = client.protect(&request)?.into_parts();
53//!     let parsed = server.preparse(&request_bytes)?;
54//!     let authenticated = server.authenticate(parsed.token, psk)?;
55//!     let opened = authenticated.token.admit(authenticated.replay.decision(true))?;
56//!     assert_eq!(opened.request, request);
57//!     let reply = Response { status: 200, headers: Vec::new(), body: b"ok".to_vec() };
58//!     let reply_bytes = opened.response.protect_finite(&reply)?;
59//!     assert_eq!(response_right.open_finite(&reply_bytes)?, reply);
60//!     Ok(())
61//! }
62//! ```
63//!
64//! # Streamed upload
65//!
66//! Send the `begin_stream` bytes first, then each record from `push`, then the
67//! bytes from `finish`. End the outer HTTP body after `finish`. On the server,
68//! hold checked DATA until the host observes real outer EOF and `finish_eof`
69//! checks END. This example accepts one replay decision and passes bytes
70//! directly between the client and server in place of an HTTP transport.
71//! A host must use a shared atomic replay store:
72//!
73//! ```
74//! use hpke_http::{
75//!     Client, Error, Limits, Method, RequestHead, Server, StreamRequestOpener,
76//!     StreamRequestRecord, generate_key_pair,
77//! };
78//!
79//! fn feed(reader: &mut StreamRequestOpener, bytes: &[u8], body: &mut Vec<u8>) -> Result<(), Error> {
80//!     let mut remaining = bytes;
81//!     while !remaining.is_empty() {
82//!         let (used, record) = reader.feed(remaining)?;
83//!         remaining = &remaining[used..];
84//!         if let Some(StreamRequestRecord::Data(part)) = record {
85//!             body.extend_from_slice(&part);
86//!         }
87//!     }
88//!     Ok(())
89//! }
90//!
91//! fn main() -> Result<(), Error> {
92//!     let keys = generate_key_pair()?;
93//!     let (private_key, public_key) = keys.into_parts();
94//!     let psk = b"example pre-shared key with 32 bytes!!";
95//!     let client = Client::new(&public_key, b"key-1".to_vec(), psk.to_vec(), b"tenant-1".to_vec(), Limits::default())?;
96//!     let server = Server::new(&private_key, b"key-1".to_vec(), Limits::default())?;
97//!     let head = RequestHead {
98//!         method: Method::Post,
99//!         authority: b"api.example.test".to_vec(),
100//!         path: b"/upload".to_vec(),
101//!         headers: Vec::new(),
102//!     };
103//!     let (mut writer, first) = client.begin_stream(&head)?;
104//!     let parsed = server.preparse_stream(&first)?;
105//!     let authenticated = server.authenticate_stream(parsed.token, psk)?;
106//!     let opened = authenticated.token.admit(authenticated.replay.decision(true))?;
107//!     let mut reader = opened.reader;
108//!     let mut body = Vec::new();
109//!     for part in [b"hello".as_slice(), b" world".as_slice()] {
110//!         let mut remaining = part;
111//!         while !remaining.is_empty() {
112//!             let (used, record) = writer.push(remaining)?;
113//!             remaining = &remaining[used..];
114//!             if let Some(record) = record {
115//!                 feed(&mut reader, &record, &mut body)?;
116//!             }
117//!         }
118//!     }
119//!     let (end, _response_right) = writer.finish()?;
120//!     feed(&mut reader, &end, &mut body)?;
121//!     let _server_response_right = reader.finish_eof()?;
122//!     assert_eq!(body.as_slice(), b"hello world");
123//!     Ok(())
124//! }
125//! ```
126//!
127#![forbid(unsafe_code)]
128
129mod codec;
130mod compression;
131mod engine;
132mod entropy;
133mod error;
134mod limits;
135mod message;
136mod method;
137mod response;
138mod sse;
139
140pub use engine::{
141    AuthenticatedRequest, AuthenticatedStreamRequest, Client, CredentialRequest, KeyPair,
142    OpenedRequest, OpenedStreamRequest, PreparsedRequest, PreparsedStreamRequest, ProtectedRequest,
143    ReplayDecision, ReplayRequest, ReplayToken, ResponseCapability, ResponseToken, STREAM_DATA_LEN,
144    Server, StartToken, StreamReplayToken, StreamRequestOpener, StreamRequestRecord,
145    StreamRequestSealer, StreamStartToken, generate_key_pair, generate_key_pair_with_entropy,
146};
147pub use entropy::{EntropySource, SystemEntropy};
148pub use error::Error;
149pub use limits::{
150    HARD_MAX_BODY_LEN, HARD_MAX_HEADER_BYTES, HARD_MAX_HEADER_COUNT, HARD_MAX_ID_LEN,
151    HARD_MAX_REQUEST_BYTES, HARD_MAX_TARGET_LEN, Limits,
152};
153pub use message::{HeaderField, Request, RequestHead, Response};
154pub use method::Method;
155pub use response::{ResponseHead, ResponseMode, ResponseOpener, ResponseRecord, ResponseSealer};
156pub use sse::SseSplitter;
157
158/// Language-neutral protocol identifier.
159pub const PROTOCOL_ID: &str = "hpke-http/3";
160/// Boundary ABI version used by the first-party bindings.
161pub const BINDING_ABI_VERSION: u32 = 8;
162/// Time for which a newly created request can be accepted (five minutes).
163pub const REQUEST_LIFETIME_SECS: u64 = 300;
164/// Maximum accepted client/server clock difference (30 seconds).
165pub const CLOCK_SKEW_SECS: u64 = 30;
166
167/// Immutable engine identity returned before a binding accepts secrets.
168#[derive(Clone, Copy, Debug, Eq, PartialEq)]
169pub struct BuildInfo {
170    /// Rust crate version.
171    pub version: &'static str,
172    /// Language-neutral protocol identifier.
173    pub protocol: &'static str,
174    /// Boundary ABI version.
175    pub binding_abi: u32,
176}
177
178/// Return the engine identity used for binding skew checks.
179#[must_use]
180pub const fn build_info() -> BuildInfo {
181    BuildInfo {
182        version: env!("CARGO_PKG_VERSION"),
183        protocol: PROTOCOL_ID,
184        binding_abi: BINDING_ABI_VERSION,
185    }
186}