Files
certctl/docs/reference/auth-standards-implemented.md
T
shankar0123 2245b91130 auth-bundle-2 Phase 15: docs/reference/auth-standards-implemented.md (RFC + CWE evidence list, NOT a compliance-mapping doc)
Closes Phase 15 of cowork/auth-bundle-2-prompt.md. Ships a single
operator-facing doc that lists every RFC the auth bundles implement
and every CWE class the implementation closes, with concrete file
paths + test anchors per row.

Files
=====

docs/reference/auth-standards-implemented.md (NEW):
* Table 1: 13 RFCs / standards rows (RFC 6749, 7636, 7519, 7517,
  OIDC Core 1.0, OIDC BCL 1.0, RFC 6265, RFC 9700, RFC 8414,
  RFC 7633, RFC 8555, RFC 7515 plus the OIDC Core §5.3.2 UserInfo
  endpoint). Every row has a concrete source file path + a
  negative-test anchor.
* Table 2: 14 CWE rows (CWE-287, 352, 384, 294, 916/329, 307,
  345, 200, 770, 330, 311, 326, 1004, 614, 1275). Every row
  points at where the defense lives + where it is pinned.
* Bundle 1 RBAC standards covered separately at the end with
  CWE-285, 862, 863, 732 pointers into Bundle 1's surface.
* Explicit 'What this document is NOT' section preserving the
  operator's 2026-05-05 retired-compliance-docs decision: the
  doc is an evidence list, NOT a SOC 2 / PCI-DSS / HIPAA /
  NIST SP 800-53 / NIST SSDF / FedRAMP framework-mapping doc.
  Framework name-drops appear ONLY inside the explicit
  'this is NOT' disclaimer paragraphs; no marketing-flavored
  prose claims certctl 'satisfies CC6.1' or similar.

docs/README.md (MODIFIED):
* Adds the auth-standards-implemented.md doc to the Reference
  section nav table between intermediate-ca-hierarchy.md and
  the deployment-model.md entry, with a one-line description
  flagging it as RFC + CWE evidence (NOT a compliance-mapping
  doc).

Verification
============

* Last-reviewed header: 2026-05-10.
* Internal-link sweep: every relative link resolves cleanly.
* Framework-name grep: SOC 2 / PCI-DSS / HIPAA / NIST SSDF /
  FedRAMP appear ONLY inside the 'this is NOT a compliance-
  mapping doc' disclaimer paragraphs (lines 7 and 66 of the
  new doc). No marketing-flavored claims.
* No Go-side impact; pure docs commit, make verify gate
  unchanged.
2026-05-10 16:58:06 +00:00

17 KiB

Authentication standards implemented

Last reviewed: 2026-05-10

This document is an honest informational reference for operators, external testers, and acquirers who want to know which RFCs and standards Auth Bundle 1 (RBAC) and Auth Bundle 2 (OIDC + sessions + back-channel logout + break-glass) implement, and which CWE weakness classes the implementation closes. Every row points at a real file or migration in this repository.

This document is intentionally NOT a compliance-mapping doc. The operator retired the framework-mapping subtree (docs/compliance/{index,soc2,pci-dss,nist-sp-800-57}.md) on 2026-05-05; framework-name-drops (SOC 2 / PCI-DSS / HIPAA / NIST SSDF / FedRAMP) are also swept from prose mentions across README.md and docs/ per that decision. RFC and CWE references stay because they are precise technical pointers; framework labels were marketing-flavored and prone to overclaim. If you are an auditor mapping certctl's controls to a framework, treat the rows below as evidence and do the framework mapping yourself against the framework you are auditing against.

For the wider security posture, see security.md. For the threat model behind these controls, see auth-threat-model.md. For the per-IdP setup guides, see oidc-runbooks/index.md.

Table 1: RFCs and standards implemented end-to-end

Each row carries at least one negative test (a test that asserts the fail-closed branch fires when a malformed input violates the spec).

Standard What we implement Source Negative-test anchor
RFC 6749 (OAuth 2.0) Authorization-code grant via OIDC; confidential-client credentials only internal/auth/oidc/service.go (HandleAuthRequest, HandleCallback) internal/auth/oidc/service_test.go (21+ negatives covering wrong aud / wrong iss / expired / etc.)
RFC 7636 (PKCE) S256 challenge mandatory; plain rejected at the service-layer sentinel; verifier persisted in pre-login row, single-use internal/auth/oidc/service.go (oauth2.S256ChallengeOption hard-coded), internal/auth/oidc/prelogin.go TestService_PKCEPlainRejectedSentinel, TestService_StateReplayDeniedByConsumeOnce
RFC 7519 (JWT) ID-token validation via go-oidc; service-layer alg allow-list (RS256/RS512/ES256/ES384/EdDSA); HS-family + none rejected internal/auth/oidc/service.go (disallowedAlgs map, isDisallowedAlg) TestService_HandleCallback_RejectsHSAlgsConfusion, TestService_IdPDowngradeDefense_RejectsHSAdvertised
RFC 7517 (JWK) JWKS fetch + cache + rotation handled transparently by coreos/go-oidc; operator-triggered RefreshKeys + auto-refresh on TTL expiry internal/auth/oidc/service.go (RefreshKeys; cfg.JWKSCacheTTLSeconds default 3600) TestService_RefreshKeys_CatchesPostLoadDowngrade, TestKeycloakIntegration_JWKSRotation_RefreshKeysPicksUpNewKey (Phase 10 integration)
OIDC Core 1.0 §3.1.3.7 iss exact match, aud membership, azp for multi-aud, at_hash REQUIRED-when-access_token-present (Phase 3 tightening of the spec MAY → MUST), nonce constant-time-compare internal/auth/oidc/service.go (HandleCallback steps 5-9) TestService_HandleCallback_RejectsWrongAudience, TestService_HandleCallback_AZPRequiredOnMultiAud, TestService_HandleCallback_ATHashRequiredWhenAccessTokenPresent, TestService_HandleCallback_RejectsNonceMismatch
OIDC Core 1.0 §5.3.2 (UserInfo endpoint) Optional fallback when ID-token groups claim is empty; bounded by configured FetchUserinfo bool internal/auth/oidc/service.go (fetchUserinfoGroups) 4-case userinfo-fallback matrix in service_test.go (happy + endpoint-missing + endpoint-failing + userinfo-also-empty)
OpenID Connect Back-Channel Logout 1.0 events claim + sid/sub revocation; nonce MUST be absent; jti-based replay defense internal/api/handler/auth_session_oidc.go (BackChannelLogout, DefaultBCLVerifier) 6 negatives in auth_session_oidc_test.go: BCL missing events, BCL nonce-present, BCL unknown-key-sig, etc.
RFC 6265 (HTTP State Management) Session cookie attributes: Secure + HttpOnly + SameSite=Lax (default; configurable to Strict via CERTCTL_SESSION_SAMESITE); Path=/; host-only internal/auth/session/service.go (cookie minting), internal/api/handler/auth_session_oidc.go (Set-Cookie wiring) Phase 6 middleware-chain test matrix (7 cases) in internal/auth/session/middleware_test.go
RFC 9700 (OAuth 2.0 Security Best Current Practice) PKCE mandatory; no implicit flow; strict redirect_uri (registered + exact-match per OIDCProvider.RedirectURI); state non-guessable (32-byte random); single-use internal/auth/oidc/service.go; OIDCProvider.Validate() enforces redirect_uri shape TestOIDCProvider_Validate_RejectsHTTPRedirectInProd, state-replay test
RFC 8414 (OAuth 2.0 Authorization Server Metadata) Discovery doc fetched via go-oidc at provider creation + RefreshKeys; id_token_signing_alg_values_supported consulted for IdP-downgrade-attack defense internal/auth/oidc/service.go (getOrLoad, guardAdvertisedAlgs) TestService_IdPDowngradeDefense_RejectsHSAdvertised and RejectsNoneAdvertised
RFC 7633 (X.509 TLS Feature Extension; Must-Staple) Per-profile certctl issuance flag; out-of-scope for Bundle 2 but cited here because RFC 7633 OID id-pe-tlsfeature is in the same crypto-stack umbrella internal/connector/issuer/local/local.go Bundle 9 SCEP master-bundle Phase 5.6 tests; not Bundle-2 territory
RFC 8555 §7 (ACME directory metadata) certctl-side ACME server tier; out-of-scope for Bundle 2 but cited because it shares the alg-pinning + nonce-handling discipline that Bundle 2 carries forward internal/api/handler/acme/* per-route handler tests in internal/api/handler/acme/
RFC 7515 (JWS) JWS verification delegated to go-oidc/v3 + go-jose/v4; alg pin enforced at gooidc.NewIDTokenVerifier config + service-layer re-check internal/auth/oidc/service.go (oauthConfig + verifier wiring) TestService_HandleCallback_RejectsExpired and TestService_HandleCallback_RejectsIATInFuture

Table 2: CWE / weakness classes the implementation closes

Each row points at the file(s) that implement the defense and the test file(s) that pin the invariant.

CWE Description Where defended Where pinned
CWE-287 (Improper Authentication) Session-cookie HMAC verification (length-prefixed input defeats concat-collision) + alg-pinned ID-token verify internal/auth/session/service.go (computeHMAC, parseCookie, Validate); internal/auth/oidc/service.go (HandleCallback) TestComputeHMAC_LengthPrefixDefeatsConcatCollision; TestService_Validate_ConcatenationCollisionDefeatedByLengthPrefix; full Phase 3 21+ negatives matrix
CWE-352 (Cross-Site Request Forgery) Double-submit cookie + SameSite=Lax/Strict + hashed CSRF token on session row; constant-time compare in CSRFMiddleware internal/auth/session/middleware.go (CSRFMiddleware) Phase 6 7-case middleware-chain matrix (internal/auth/session/middleware_test.go); TestSessionMiddleware_CSRFRequiredOnStateChangingMethods
CWE-384 (Session Fixation) Session ID is opaque random ses-<base64url> (32 bytes entropy) generated server-side at login; cookie value rotates on every login (no inheritance from pre-login); CSRF token rotates alongside internal/auth/session/service.go (Create, RotateCSRFToken) TestService_Create_AssignsFreshSessionID; CSRF rotation pinned via TestService_RotateCSRFToken_AfterLogin
CWE-294 (Authentication Bypass by Capture-Replay) Single-use state, single-use nonce (both stored in pre-login row, atomic DELETE...RETURNING on consume); single-use authorization code (Keycloak/IdP-side); jti-based BCL replay defense internal/auth/oidc/prelogin.go (LookupAndConsume); internal/api/handler/auth_session_oidc.go (BCL handler) TestService_StateReplayDeniedByConsumeOnce; TestService_HandleCallback_RejectsForgedPreLoginCookie; BCL replay negative in handler tests
CWE-916 / CWE-329 (Use of Password Hash With Insufficient Computational Effort / Use of a Key Past its Expiration Date) Argon2id with OWASP 2024 params (m=64 MiB, t=3, p=4, 16-byte salt, 32-byte output) for break-glass passwords; per-credential random salt; PHC-format hash internal/auth/breakglass/service.go (HashPassword, VerifyPassword); v3 ciphertext blob format with PBKDF2-SHA256 600,000 rounds for config-at-rest encryption TestPhase7_5_HashPasswordOWASP2024Params; TestPhase7_5_HashFormatPHC; internal/crypto/encryption_test.go for v3 PBKDF2 floor
CWE-307 (Improper Restriction of Excessive Authentication Attempts) Failure count + lockout window on break-glass credential; threshold default 5, reset window default 1h, lockout duration default 30s; atomic single-statement IncrementFailure defeats concurrent racing attempts internal/auth/breakglass/service.go (Login, IncrementFailure); internal/repository/postgres/breakglass.go TestPhase7_5_LockoutAfterThresholdFailures; TestPhase7_5_FailureCountResetsAfterWindow
CWE-345 (Insufficient Verification of Data Authenticity) OIDC at_hash REQUIRED-when-access_token-present ties access token to ID token (Phase 3 tightening of OIDC core MAY → MUST); OIDC iss + aud + azp checks ensure token came from the configured IdP for the configured client internal/auth/oidc/service.go (HandleCallback steps 5-9, atHashMatches) TestService_HandleCallback_ATHashRequiredWhenAccessTokenPresent; TestService_HandleCallback_RejectsATHashMismatch
CWE-200 (Information Exposure) Token-leak hygiene tests on every secret-bearing path: ID tokens, access tokens, refresh tokens, authorization codes, PKCE verifiers, state, nonce, signing keys, break-glass passwords NEVER appear in any log line at any level internal/auth/oidc/service.go, internal/auth/session/service.go, internal/auth/breakglass/service.go (all log calls audited); internal/service/audit_redact.go (Bundle 6 redactor) internal/auth/oidc/logging_test.go (4 grep-asserts); internal/auth/breakglass/service_test.go (token-leak hygiene + json.Marshal probe); internal/auth/bootstrap/service_test.go (Bundle 1 pattern)
CWE-770 (Allocation of Resources Without Limits or Throttling) Per-IP rate limit on /auth/breakglass/login via the global middleware.NewRateLimiter (default RPS / burst from CERTCTL_RATE_LIMIT_* env vars) wrapped around the entire mux; the breakglass login endpoint inherits this protection. Per-route override available via middleware.NewRateLimiter per-bucket configuration if the operator wants stricter caps cmd/server/main.go (rateLimiter wiring at the root middleware stack); internal/api/middleware/middleware.go (NewRateLimiter) internal/api/middleware/ratelimit_test.go; internal/api/middleware/ratelimit_keyed_test.go
CWE-330 (Use of Insufficiently Random Values) crypto/rand for state, nonce, PKCE verifier (via oauth2.GenerateVerifier), session signing keys (32 random bytes), session IDs (ses-<base64url-no-pad> from 32 random bytes), pre-login IDs (pl-<base64url-no-pad> from 16 random bytes), CSRF tokens (32 random bytes), break-glass salts (16 random bytes via crypto/rand) internal/auth/oidc/service.go (randomB64URL); internal/auth/session/service.go (newOpaqueID, newCSRFToken); internal/auth/oidc/prelogin.go (newID); internal/auth/breakglass/service.go (HashPassword salt) TestPreLoginAdapter_CreatePreLogin_RNGFailure (entropy-source error path); RNG failure pinned for every callsite
CWE-311 (Missing Encryption of Sensitive Data) OIDC client_secret AES-256-GCM encrypted at rest (v3 blob format: magic 0x03 + salt(16) + nonce(12) + ciphertext+tag); session signing keys same scheme; empty CERTCTL_CONFIG_ENCRYPTION_KEY returns ErrEncryptionKeyRequired (fail-closed) internal/crypto/encryption.go (EncryptIfKeySet, DecryptIfKeySet); internal/api/handler/auth_session_oidc.go (encryptClientSecret); internal/auth/session/service.go (KeyMaterialEncrypted) internal/repository/postgres/oidc_encryption_invariant_test.go (Phase 13 invariant test: ciphertext != plaintext, v2/v3 blob shape, round-trip + wrong-passphrase fails)
CWE-326 (Inadequate Encryption Strength) TLS 1.3 only on the certctl control plane (post-v2.2 milestone); HSTS-equivalent posture via HTTPS-only listener; AES-256-GCM for at-rest config encryption; PBKDF2-SHA256 600,000 rounds for v3 blob key derivation (OWASP 2024 floor) cmd/server/main.go (TLS 1.3 listener config); internal/crypto/encryption.go (v3 PBKDF2 iteration count) TestServerTLSConfig_RejectsTLS12 (Bundle 5); TestEncryption_V3IterationCount_PinnedAtOWASP2024Floor
CWE-1004 (Sensitive Cookie Without HttpOnly) Session cookie set with HttpOnly=true; CSRF cookie intentionally HttpOnly=false so the GUI can read it for the X-CSRF-Token header (the read is by-design per the double-submit-cookie pattern) internal/auth/session/service.go (cookie attrs); internal/api/handler/auth_session_oidc.go (Set-Cookie wiring) Cookie-attribute pinning in handler tests; documented in auth-threat-model.md "Session minting + cookies" subsection
CWE-614 (Sensitive Cookie in HTTPS Session Without 'Secure' Attribute) Session + CSRF cookies set with Secure=true; rejected at cookie-write time on http:// listeners (HTTPS-only control plane post-v2.2) internal/auth/session/service.go; cmd/server/main.go HTTPS-only listener TLS-listener tests in cmd/server/; cookie attrs pinned in handler tests
CWE-1275 (Sensitive Cookie with Improper SameSite Attribute) Session cookie SameSite=Lax default (configurable to Strict via CERTCTL_SESSION_SAMESITE); CSRF defense via the double-submit pattern means Lax is sufficient even if the operator does not flip to Strict internal/auth/session/service.go (cookie attrs); internal/config/config.go (SAMESITE env var) Cookie-attribute pinning; SameSite enforcement is per-cookie

Bundle 1 (RBAC) standards covered separately

The above tables focus on Bundle 2's OIDC + sessions + back-channel logout + break-glass surface. Bundle 1's RBAC primitive carries its own implementation pointers; the Bundle 1 auth-threat-model.md section "Defenses Bundle 1 ships" enumerates the full RBAC + bootstrap + auditor + approval-workflow surface. CWE-pointers that apply to Bundle 1's surface:

  • CWE-285 (Improper Authorization) — defended by the Phase 3 RequirePermission middleware + Authorizer.CheckPermission service-layer call. Pinned by 90+ tests across internal/auth/ and internal/service/auth/.
  • CWE-862 (Missing Authorization) — pinned by Phase 12's phase12_protocol_allowlist_test.go (asserts protocol endpoints are explicitly allowlisted, NOT silently bypassing the gate).
  • CWE-863 (Incorrect Authorization) — pinned by the auditor-split invariant in internal/domain/auth/auditor_test.go (auditor role holds exactly audit.read + audit.export ONLY).
  • CWE-732 (Incorrect Permission Assignment for Critical Resource) — five admin-only fine-grained perms (cert.bulk_revoke, crl.admin, scep.admin, est.admin, ca.hierarchy.manage) seeded into r-admin only; pinned by migration 000030 + r-admin-only seed test.

What this document is NOT

To preserve the operator's 2026-05-05 retired-compliance-docs decision:

  • This is NOT a SOC 2 / PCI-DSS / HIPAA / NIST SP 800-53 / NIST SSDF / FedRAMP framework-mapping doc.
  • This is NOT a marketing claim that certctl "satisfies CC6.1" or "complies with §164.312(a)(2)(iii)" or any similar framework label.
  • This IS an evidence list. An auditor doing framework mapping for their own compliance purposes can use this list as the source-of-truth pointer, then map each row to the framework control they are auditing against under their own judgment.

If you are an external tester, an operator's auditor, or an acquirer doing technical diligence, this document gives you concrete file paths to read and concrete tests to run. If you want a framework-mapping document, build it yourself against the rows here using the framework-mapping methodology your audit firm prescribes; this project does not own that mapping.

Cross-references

  • auth-threat-model.md — threat model behind these defenses.
  • security.md — overall security posture.
  • oidc-runbooks/index.md — per-IdP operator setup guides.
  • auth-benchmarks.md — Phase 14 perf baselines for the validation paths cited above.
  • internal/auth/oidc/ — OIDC service + groupclaim resolver + pre-login adapter + bootstrap hook.
  • internal/auth/session/ — Session service + middleware + CSRF + signing-key rotation.
  • internal/auth/breakglass/ — break-glass admin (Argon2id + lockout + constant-time + surface-invisibility).
  • internal/crypto/encryption.go — AES-256-GCM v3 blob format for at-rest encryption.
  • migrations/000029 through 000038 — schema for RBAC, OIDC providers, sessions, signing keys, users, group mappings, pre-login, break-glass.
  • scripts/ci-guards/multi-tenant-query-coverage.sh — Phase 13 forward-compat multi-tenant query coverage.