OCSP responder
A pre-signed, replayable, multi-CA OCSP responder with keyless edge serving and post-quantum ML-DSA support. Built in Rust.
hoike (Hawaiian) — to show, to exhibit, to testify. An OCSP responder does exactly one thing: it testifies to the status of someone else's certificate.
Signer/edge split architecture. The machine that signs status and the machine that serves it are never the same machine.
Responses are batch-signed by the signer tier and sealed into ahu bundles. Edge nodes serve stored bytes verbatim — no keys, no signing, no HSM access on the hot path.
Edge nodes hold no signing keys. An edge compromise cannot produce a
false good — only denial of service or stale replay
within nextUpdate.
One responder instance serves many CAs. Routing uses the
issuerKeyHash multimap from each OCSP request's
CertID, handling re-keyed and cross-signed CAs correctly.
ML-DSA-44, ML-DSA-65, and ML-DSA-87 signing as first-class configurations, not patches. Batching amortizes post-quantum signature size across certificate buckets.
Self-describing containers with CBOR manifest, CMS seal, and a sorted
index for O(log n) binary search. Designed for zero-copy serving
via mmap.
Edge fleet coordination via the SWIM protocol (foca). Generation announcements, membership tracking, and urgent revocation notices — gossip is never authoritative for status.
Bundles are sealed files that cross air gaps on removable media. Enclave mode uses byte-identical code paths — only acquisition differs. No gossip, no upstream, full functionality.
Epoch chain with persisted high-water marks prevents replay of older
generations. Fork detection catches duplicate signers immediately.
Stale-generation alerts before nextUpdate expires.
Responses are mmap'd and written directly via
writev. No parse, no copy, no re-encode on the hot path.
Horizontal scaling limited only by network bandwidth.
Incremental updates distribute only changes since the last full generation. Steady-state mirrors receive deltas; full bundles only on join or chain-length exhaustion.
Per-CA nonce handling: ignore for pre-signed responses,
forward to proxy to a signer. Configuring live signing
on an edge is a startup error, not a runtime surprise.
One BasicOCSPResponse carries both SHA-1 and SHA-256
CertID entries per RFC 9919. One signature, one payload,
two index records. Log SHA-1 usage to track migration.
Sign a bundle, inspect it, start the responder.
$ hoike sign --ca enterprise-ca --crl revoked.crl -o bundle.ahu
INFO hoike::sign: reading CRL from 'revoked.crl' (42 entries)
INFO hoike::sign: signing with ecdsa-p256, epoch 1
INFO hoike::sign: wrote bundle.ahu (42 good, 3 revoked, 84 index entries)
$ ahu inspect bundle.ahu
format : ahu v1
producer : hoike 0.1.0
epoch : 1
scope : enterprise-ca (partial)
algorithm : ecdsa-p256
entries : 84 (42 good, 3 revoked, dual CertID)
size : 48.2 KB (zstd compressed)
$ hoike serve --config hoike.toml
INFO hoike: loading config from 'hoike.toml'
INFO hoike::core: loaded CA 'enterprise-ca' (45 entries, epoch 1)
INFO hoike::server: OCSP responder listening on 0.0.0.0:2560RFCs and standards implemented — 20 conformance assertions
Full OCSP protocol with pre-signed response production, HTTP caching profile, nonce handling rules, and AIA discovery. Every protocol claim validated by wire-format conformance tests against OpenSSL and Go clients.
Container
# Build the container image
podman build -t hoike .
podman run --rm \
-v ./hoike.toml:/etc/hoike/hoike.toml:ro \
-v ./bundles:/var/lib/hoike/bundles:ro \
-p 2560:2560 \
hoike
Build from source
git clone https://github.com/czinda/hoike
cd hoike
cargo build --release
# Two binaries: hoike (~8 MB) and ahu (~1 MB)
cargo run --release -p hoike-cli -- \
serve --config hoike.toml
A Cargo workspace with six crates. The signer holds keys; the edge serves bytes.
Six crates with clean dependency boundaries.
Bundle format: read, write, verify. Apache-2.0 OR MIT. No server dependencies.
CertID routing, request parsing, config, policy, state store.
Signing, CRL adapter, OCSP response generation, batch production.
HTTP request path via axum. GET and POST handlers, RFC 9919 headers.
SWIM membership and generation announcements via foca.
hoike + ahu binaries. The operator-facing surface.