mirror of
https://github.com/shankar0123/certctl.git
synced 2026-06-09 19:28:58 +00:00
M-001/M-006: strip HTTP auth from EST/SCEP + fail-loud SCEP preflight
Closes CWE-306 (missing authentication for critical function) for SCEP
via a fail-loud startup gate, and aligns EST/SCEP HTTP dispatch with
their respective RFCs. CRL/OCSP remain unauthenticated under
.well-known/pki/* per RFC 5280 §5 / RFC 6960 / RFC 8615. Option (D):
no mTLS in this milestone.
- RFC 7030 §3.2.3 (EST auth is deployment-specific) and §4.1.1
(/cacerts explicitly anonymous): EST paths served unauthenticated;
CSR-signature + profile policy enforce identity inside ESTService.
- RFC 8894 §3.2: SCEP authenticates via the challengePassword
PKCS#10 attribute (OID 1.2.840.113549.1.9.7), not an HTTP credential.
HTTP dispatch is unauthenticated; preflightSCEPChallengePassword
refuses to start when CERTCTL_SCEP_ENABLED=true without
CERTCTL_SCEP_CHALLENGE_PASSWORD. SCEPService.PKCSReq enforces the
same invariant defense-in-depth and compares with
crypto/subtle.ConstantTimeCompare.
cmd/server/main.go:
- Extract buildFinalHandler(apiHandler, noAuthHandler, webDir,
dashboardEnabled); route /.well-known/est/*, /scep, /scep/*,
/.well-known/pki/crl/{id}, /.well-known/pki/ocsp/{id}/{serial},
and health probes through noAuthHandler (RequestID +
structuredLogger + Recovery only).
- Add preflightSCEPChallengePassword fail-loud gate; startup log
emits challenge_password_set boolean for operator visibility.
cmd/server/finalhandler_test.go (new, 314 lines, 27 subtests):
- TestBuildFinalHandler_Dispatch (20) + TestBuildFinalHandler_NoDashboard
(7) pin the dispatch surface: EST 4-endpoint, SCEP exact +
trailing-slash + query-string, PKI CRL+OCSP, health, /api/v1/*
authenticated, /assets/* file server, SPA fallback.
internal/api/router/router.go, internal/config/config.go:
- Router-level comments explain why EST/SCEP/PKI dispatchers sit
outside the authenticated mux; SCEP challenge password config
plumbed through.
docs/architecture.md:
- New EST Authentication subsection (RFC 7030 §3.2.3 + §4.1.1,
buildFinalHandler + noAuthHandler references).
- Rewrite SCEP Authentication subsection; replaces pre-existing
factually-incorrect "any value accepted" claim with CWE-306
preflight, service-layer defense-in-depth, and
crypto/subtle.ConstantTimeCompare.
- Top-level Authentication section: qualify /api/v1/* scope on API
clients bullet; add standards-based-endpoints bullet referencing
the 27-subtest regression harness.
docs/compliance-soc2.md:
- CC6.1: scope API Key Authentication to /api/v1/*; add
standards-based endpoints bullet citing RFCs and CWE-306 closure.
- CC6.3: scope API Key Policy to /api/v1/* with cross-reference to
CC6.1.
- Evidence Locations augmented with buildFinalHandler,
preflightSCEPChallengePassword, scep.go defense path, regression
harness, and OpenAPI security:[] overrides.
api/openapi.yaml: verified already correct (global bearerAuth
default overridden with security:[] on /cacerts, /simpleenroll,
/simplereenroll, /csrattrs, /scep GET+POST, /crl/{issuer_id},
/ocsp/{issuer_id}/{serial}); no edits needed.
This commit is contained in:
@@ -258,7 +258,19 @@ func (r *Router) RegisterHandlers(reg HandlerRegistry) {
|
||||
}
|
||||
|
||||
// RegisterESTHandlers sets up EST (RFC 7030) routes under /.well-known/est/.
|
||||
// EST endpoints use a separate middleware chain (no API key auth — EST uses TLS client certs).
|
||||
//
|
||||
// EST endpoints are intentionally unauthenticated at the HTTP layer. Per RFC 7030
|
||||
// §3.2.3, authentication and authorization for enrollment are deployment-specific;
|
||||
// certctl relies on CSR signature verification, profile policy enforcement (allowed
|
||||
// key types, max TTL, permitted EKUs), and the underlying issuer connector's own
|
||||
// policy. Per RFC 7030 §4.1.1, /.well-known/est/cacerts is explicitly anonymous.
|
||||
//
|
||||
// cmd/server/main.go's finalHandler dispatches /.well-known/est/* to a dedicated
|
||||
// no-auth middleware chain (RequestID, structuredLogger, Recovery only) so EST
|
||||
// clients — IoT devices, 802.1X supplicants, MDM-enrolled laptops — never hit the
|
||||
// Bearer-token auth middleware they cannot satisfy. See M-001 audit 2026-04-19
|
||||
// (option D): prior builds routed EST through the authenticated apiHandler chain,
|
||||
// which reduced every enrollment to a 401 before the handler was reached.
|
||||
func (r *Router) RegisterESTHandlers(est handler.ESTHandler) {
|
||||
// EST endpoints per RFC 7030 Section 3.2.2
|
||||
r.Register("GET /.well-known/est/cacerts", http.HandlerFunc(est.CACerts))
|
||||
@@ -269,7 +281,11 @@ func (r *Router) RegisterESTHandlers(est handler.ESTHandler) {
|
||||
|
||||
// RegisterSCEPHandlers sets up SCEP (RFC 8894) routes.
|
||||
// SCEP uses a single endpoint with operation-based dispatch via query parameters.
|
||||
// Authentication is via challenge password in the CSR, not TLS client certs or API keys.
|
||||
// Authentication is via the challengePassword attribute in the PKCS#10 CSR, not
|
||||
// via HTTP Bearer tokens or TLS client certs. cmd/server/main.go's finalHandler
|
||||
// routes /scep* through the no-auth middleware chain (M-001 audit 2026-04-19,
|
||||
// option D), and Config.Validate() refuses to start the server if SCEP is enabled
|
||||
// without a non-empty CERTCTL_SCEP_CHALLENGE_PASSWORD (H-2, CWE-306).
|
||||
func (r *Router) RegisterSCEPHandlers(scep handler.SCEPHandler) {
|
||||
// SCEP uses a single path; the handler dispatches on ?operation= query param
|
||||
r.Register("GET /scep", http.HandlerFunc(scep.HandleSCEP))
|
||||
|
||||
Reference in New Issue
Block a user