Repository navigation
feat(operatorca): check operator certificates with the CA's OCSP responder - #151
Merged
Merged
Conversation
Bugs5382
marked this pull request as ready for review
September 30, 2026 23:27
2 tasks done
4 tasks done
…onder Each operator CA now has an OCSP responder source: off, aia (the leaf's authorityInfoAccess URI, read only after the chain check) or url. The file source takes it from operatorOCSP and a registered CA from its row. - Requests carry a 32-byte nonce (RFC 8954) and a SHA-1 CertID, built with encoding/asn1. They go by POST, with a GET fallback only after a 405 and for a base64 request under 255 bytes. Fetches keep the CRL limits with a 64 KiB body cap. - Responses are parsed with golang.org/x/crypto/ocsp, and the manager adds what it leaves out: the signer is the anchor or a delegated responder issued directly by it with id-kp-OCSPSigning, valid now and named by the ResponderID (noCheck honoured, otherwise the denylist and CRL apply); the CertID hashes and serial match; thisUpdate is at most 5 minutes ahead and nextUpdate hasn't passed; an echoed nonce matches; no unknown critical extension. - revoked and unknown refuse (1610 REVOKED_OCSP, OCSP_UNKNOWN; unknown is audited once per cache period). good never overrides the denylist or the CRL. With no fresh response the web path falls back to a fresh CRL, then to operatorRevocationPolicy: soft allows with a banner, an hourly log line and one operator-ocsp-unavailable audit row per outage; hard refuses with 1608 STALE_OCSP. A fresh good still allows under hard with a stale CRL. - MCP still needs a fresh CRL; a fresh OCSP revoked or unknown refuses, an OCSP failure is covered by the CRL. - Per-replica LRU for min(nextUpdate, 1 h), 5 minutes without nextUpdate, refreshed in the background at half life while in use, single-flighted per certificate, with failures remembered for 30 s. - OCSPClient.Probe asks about a random serial and needs a validly signed answer (1605 OCSP_UNREACHABLE or OCSP_INVALID), for registration. golang.org/x/crypto v0.55.0 becomes a direct dependency for the OCSP parser (the Go project's own subrepository, at the version cryptos uses). golang.org/x/sync moves from indirect to direct for singleflight. Closes #142 Refs #78 Signed-off-by: Bugs5382 <12115015+Bugs5382@users.noreply.github.com>
Bugs5382
force-pushed
the
feat/142-operatorca-ocsp
branch
from
October 1, 2026 13:31
9edb766 to
9882c47
Compare
Contributor
Author
|
Merging for the v0.1.0 test run: CI green and mergeable at merge time. |
This was referenced Oct 1, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What and why
The manager now asks each operator CA's OCSP responder about operator certificates, next to the denylist and the CRL. OCSP can only add a refusal; it never removes one. This finishes the operator CA trust and revocation work that #146 started.
Closes #142
Refs #78
Merge order: independent of the other open manager PRs. Operator CA rotation (
SetOperatorCAOCSP, phase 5) builds on this one and should merge after it.Docs: companion PR CryptOS-PKI/website#72, to merge together with this one. The manager's
docs/operator-ca.md,docs/mcp.mdand the README are updated here, and the "OCSP isn't checked yet" note is gone.What changes
aia(default) uses the leaf's authorityInfoAccess OCSP URI, read only after the leaf verified against the anchor;urluses the anchor's URL (operatorOCSP.urlfor the file source, the row'socsp_urlwhen registered);offsends nothing. A leaf without an AIA URI inaiamode gets no OCSP check.encoding/asn1, a SHA-1 CertID and a 32-byte nonce (RFC 8954), sent by POST. GET (RFC 6960 appendix A.1) is used only after a 405, and only when the base64 request is under 255 bytes. Fetches keep the CRL limits (http or https, 10 s, 3 redirects, explicit transport, no body in errors) with a 64 KiB cap.golang.org/x/crypto/ocspparses the response and the manager adds what it leaves out. The signer is the anchor, or a delegated responder issued directly by the anchor withid-kp-OCSPSigning, valid now and named by the ResponderID;noCheckis honoured, and without it the delegated cert is checked against the anchor's denylist and CRL (never OCSP). The CertID hash algorithm, issuer name hash, issuer key hash and serial must match. thisUpdate at most 5 minutes ahead, nextUpdate not passed. An echoed nonce must match; no nonce is accepted. A non-successful status, a non-basic type or an unknown critical extension (single or response) isOCSP_INVALID.REVOKED); then OCSP:revokedgives 1610REVOKED_OCSP,unknowngives 1610OCSP_UNKNOWNand is auditedoperator-ocsp-unknownonce per cache period, a freshgoodallows. With no fresh response: a CRL within nextUpdate allows; otherwisesoftallows with theOCSP responder unreachablebanner (AnchorStatus.OCSPBanner), an hourly log line per anchor and oneoperator-ocsp-unavailableaudit row per outage, andhardrefuses with 1608STALE_OCSP. Underhardwith a stale CRL, a fresh OCSPgoodstill allows.NO_CRL,STALE_CRL). When OCSP is configured and a fresh response is available it must begood; an OCSP failure is allowed because the fresh CRL covers it.OCSPClient.Probeasks about a random serial and needs a validly signed response of any status, else 1605OCSP_UNREACHABLEorOCSP_INVALID. The registration andSetOperatorCAOCSPRPCs that call it come with bootstrap and rotation.OCSPClient.ClearAnchoris there forSetOperatorCAOCSP.PeerAuthorizerandAdmitMCPpass the whole certificate (CheckWebCert,CheckMCPCert).mcpauth.ResolverusesCheckMCPCertwhen the checker offers it, so the MCP path sees the AIA URI too; the serial-onlyCheckWebandCheckMCPkeep their signatures and applyurl-mode OCSP.Design readings
OCSP_INVALID.Dependencies
golang.org/x/cryptov0.55.0 becomes a direct dependency forocsp(the Go project's own subrepository, at the version cryptos uses). The other module versions are main's.golang.org/x/syncmoves from indirect to direct forsingleflight.The error code table needs no change: the 16xx sub-reasons were already registered.
Verification
How this was verified
task ci(gofmt, vet, golangci-lint, tests) passes withMANAGER_TEST_DATABASE_URLpointing at a throwaway Postgres 18 container: 840 tests and subtests pass, none skipped (rebased onto main after build: build with Go 1.26.8 and bump grpc and x/text #149 and feat(operators): credential requests, recording and denylist revoke #152). 18 top-level tests are new, each written first and seen failing against stubs.task licenseexits 0.httptest) signs withgolang.org/x/crypto/ocsp; responses that need responseExtensions or several embedded certificates are re-encoded and re-signed in the test. The tests cover each refusal (wrong signer, delegated cert without OCSPSigning, from another CA or expired, ResponderID not the signer, each CertID field, future thisUpdate, stale response, non-successful status, unknown critical extensions, a body over 64 KiB),noCheck, the nonce, POST and the GET fallback, the transport limits, the cache lifetimes and half-life refresh on a fake clock, single-flight with 50 concurrent checks, the CRL fallback, soft and hard, the MCP rules, and that an AIA URI is never read from a leaf that fails the chain check.