feat(pki-server): add OCSP responder and close server release

Add durable multi-authority OCSP responders with strict request parsing,
issuer-bound serial lookup, stable revocation views, signed responses,
nonce policies and bounded protocol execution.

Complete in-process and packaged OCSP validation and close the PKI server
after the final architecture, security and release audit.
This commit is contained in:
2026-08-05 18:32:00 +02:00
parent b19edf17fd
commit 44cbb0a37d
38 changed files with 2831 additions and 107 deletions

137
docs/pki-server-ocsp.md Normal file
View File

@@ -0,0 +1,137 @@
# ZeroEcho OCSP responder
ZeroEcho serves OCSP only on the separately bounded public listener at
`/ocsp/{responderAlias}`. Administrative operations remain under `/admin/v1`,
the public repository remains under `/public/v1`, and ACME remains on its own
listener. All four surfaces share one realm and one long-lived `PkiSession`, but
they do not share authorization or admission authority.
## Responder authority
An OCSP responder is a durable server-control record. Its alias binds exactly
one realm, logical authority, issuer generation, responder certificate,
`KeyRef`, explicit chain path, signature algorithm/binding commitment, nonce
policy, response-validity policy, accepted CertID hashes, and finite request
bounds. Activation revalidates those dependencies and freezes their
commitments. There is no first/last issuer selection, filename inference,
timestamp inference, runtime path building, or fallback signing algorithm.
The unified administrative catalog exposes:
- `ocsp.responder.register`
- `ocsp.responder.inspect`
- `ocsp.responder.list`
- `ocsp.responder.activate`
- `ocsp.responder.deactivate`
Inspection requires `OCSP_RESPONDER_READ`; mutation requires the scoped
`OCSP_ADMINISTER` permission. Registration and activation are high-risk
operations and use the existing approval policy. Registration input does not
accept authoritative creation timestamps, lifecycle state, or record
commitments; the server creates those values. Ordinary results omit `KeyRef`,
certificate DER, and provider configuration.
## Signing modes
`ISSUER_SIGNED` is intended for explicitly approved internal deployments. The
configured credential and `KeyRef` must be the exact issuer-generation binding.
It should not be used to place an offline root key in an online service.
`DELEGATED_RESPONDER` is preferred for public online operation. Before
activation ZeroEcho verifies the responder certificate, its exact issuer
relationship, validity, `id-kp-OCSPSigning` EKU, digital-signature key usage
when present, explicit chain path, signing binding, and configured key
capability. Private keys remain confined behind `KeyRef` and the existing
signing workflow; the HTTP layer never obtains a `PrivateKey`.
Conceptual registration policies:
```text
issuer-signed internal:
signingMode = ISSUER_SIGNED
responderCredentialId = exact issuer-generation credential
noncePolicy = REQUIRED
delegated public:
signingMode = DELEGATED_RESPONDER
responderCredentialId = exact OCSP-signing credential
noncePolicy = REJECT
cacheable public:
noncePolicy = REJECT
cacheLifetime <= responseValidity
nonce echo:
noncePolicy = OPTIONAL_ECHO
nonce-bearing responses use no-store
```
## Requests and responses
POST uses `Content-Type: application/ocsp-request`. GET uses an unpadded,
canonical URL-safe Base64 request path:
```text
/ocsp/{responderAlias}/{base64url-request}
```
Query-string requests and alternate Base64 normalization are rejected. The
strict DER parser accepts unsigned requests only, consumes the complete input,
rejects duplicate or unknown critical extensions, and enforces configured byte
and entry bounds. Signed OCSP requests are rejected in this release; a request
signature is never treated as administrative or certificate-owner authority.
Configured CertID hashing supports SHA-1 and SHA-256. SHA-1 is permitted only
for RFC-compatible issuer-name/key hashes. It is never enabled as a certificate
or OCSP response signature algorithm. The responder validates hashes against
its exact issuer-generation certificate and performs a disk-backed direct
issuer-generation-plus-serial lookup; it never searches another authority or
scans all credentials per request.
One response captures one revocation revision and commitment. `good` means the
exact credential was issued by that issuer and is not revoked in that stable
ZeroEcho view; it is not a general statement of application validity.
`revoked` preserves the authoritative revocation time and supported reason.
`unknown` means exact issuance could not be established. Corrupt or
recovery-required authority returns a transient protocol/transport failure, not
`unknown`.
Successful responses are strict signed DER with
`application/ocsp-response`, `nosniff`, `producedAt`, `thisUpdate`, and
`nextUpdate`. Nonce-free responses carry a strong ETag and bounded public cache
policy. An echoed nonce is returned exactly and makes the response non-cacheable.
No request DER, response DER, serial, issuer hash, nonce, certificate identity,
key reference, or parser/provider exception is logged or audited.
## Deployment topology and capacity
Direct mode permits anonymous OCSP over the public TLS listener. An optional
public client certificate does not change certificate status, and forwarded
identity headers are rejected. In trusted-reverse-proxy mode the backend proxy
hop still requires mTLS and the dedicated forwarding permission; proxy and
forwarded end-client identities do not authorize or select OCSP status. Source
IP, `Forwarded`, and `X-Forwarded-For` are never responder authority.
OCSP has a separate bounded runtime lane even though it shares the public
transport listener. Parsing, lookup, signing, and response validation are
protected by finite request, queue, admission, concurrency, and deadline bounds.
OCSP saturation cannot consume administrative, public-stream, or ACME operation
permits. Shutdown quiesces admission, cancels remaining operations after the
finite grace period, and never retries an uncertain signature automatically.
Example topologies use the existing strict public-listener configuration:
```text
direct public listener:
public TLS identity + DIRECT_MTLS optional-client mapping policy
trusted proxy public listener:
proxy-to-ZeroEcho mTLS + dedicated proxy principal
+ FORWARD_AUTHENTICATED_CLIENT_IDENTITY
```
The current release is single-node. It has no distributed responder cache,
cluster coordination, or automatic status/checkpoint scheduling. Deployments
must route a responder alias to the node owning its realm and provision normal
external availability monitoring without treating that monitoring as PKI
authority.