Per operator decision the framework-mapping docs are gone. They
were aspirational (no audit, no certification, no validated
mapping); keeping them around was misleading.
Files deleted (1,883 lines):
- docs/compliance/index.md
- docs/compliance/soc2.md
- docs/compliance/pci-dss.md
- docs/compliance/nist-sp-800-57.md
Hyperlinks removed:
- README.md: 'Auditor / compliance' row in the doc table; the
'(compliance mapping included)' parenthetical in the
positioning paragraph
- docs/README.md: the '## Compliance' section table; the
'Auditor / compliance team' reading-order-by-role row
Prose name-drops swept across 24 files:
- README.md: 'FedRAMP boundary CAs / financial-services policy
CAs' → '4-level boundary CAs / 3-level policy CAs';
'Compliance-grade for PCI-DSS Level 1, FedRAMP Moderate / High,
SOC 2 Type II, HIPAA' → cut entirely
- getting-started/{quickstart,concepts,examples,why-certctl,
advanced-demo}.md: 'compliance' → 'audit' / 'policy';
'PCI-DSS / SOC 2 / NIST SP 800-57' framework lists cut;
''pci': 'true'' tag example → ''environment': 'production''
- migration/cert-manager-coexistence.md: 'compliance rules' →
'policy rules'
- operator/approval-workflow.md: 'Compliance customers (PCI-DSS
Level 1, FedRAMP Moderate / High, SOC 2 Type II, HIPAA)' →
'Operators'; entire 'Compliance control mapping' table
(PCI-DSS §6.4.5 / NIST SP 800-53 SA-15 / SOC 2 Type II CC6.1
/ HIPAA §164.308(a)(4)) deleted; 'compliance contract' →
'two-person-integrity contract'; 'compliance auditors' →
'reviewers'
- operator/legacy-clients-tls-1.2.md: 'PCI-DSS v4.0 Req 4 §2.2.5'
audit-reference → CWE-326 (kept); 'PCI-DSS Req 4 §2.2.5
attestation' section retitled to 'TLS posture summary' and
rewritten without framework framing; 'PCI-DSS, NIST, and
major browsers will eventually deprecate TLS 1.2' →
'Major browsers and OS vendors will eventually deprecate
TLS 1.2'
- operator/database-tls.md: PCI-DSS Req 4 §2.2.5 audit-ref →
CWE-319 only; 'PCI-DSS scope' → 'sensitive data'; PCI-DSS
Req 4 v4.0 prose footing → cut
- operator/runbooks/disaster-recovery.md: 'SOC 2 / PCI
procurement-team deliverable' → 'on-call deliverable';
'compliance auditors' → 'reviewers'
- reference/connectors/{acme,aws-acm,azure-kv,globalsign,
local-ca,openssl,ssh,index}.md: 'compliance reporting
(PCI-DSS §3.6, HIPAA §164.312)' → 'audit reporting';
'Compliance environments (PCI-DSS Level 1, FedRAMP High,
HIPAA)' → 'Regulated environments'; 'compliance audits' →
'audit'; 'FedRAMP boundary CA' pattern names →
'4-level boundary CA' (technically descriptive)
- reference/protocols/est.md: 'compliance-hook seam' →
'device-state hook seam'; 'compliance gating' → 'device-state
gating'; 'est_compliance_failed' → 'est_device_state_failed'
- reference/protocols/scep-intune.md: 'Optional compliance
check' → 'Optional device-state check'; failure-counter
'compliance_failed' → 'device_state_failed'; 'Conditional
Access compliance gating' → 'Conditional Access
device-state gating'
- reference/intermediate-ca-hierarchy.md: 'FedRAMP boundary-CA
deployments where the regulator requires...' →
'Boundary-CA deployments where you want separation of policy
and issuing authorities'; pattern A retitled '4-level FedRAMP
boundary CA' → '4-level boundary CA'
- reference/architecture.md: broken Related-docs link to
compliance.md removed; the rest of that block had stale
pre-Phase-2 paths (quickstart.md, demo-advanced.md,
connectors.md, openapi.md, testing-guide.md, test-env.md) —
retargeted to current locations
- reference/deployment-model.md: 'SOC 2 evidence-report
generator' → 'Audit-evidence report generator'
- reference/vendor-matrix.md: 'SOC 2 / PCI auditors paste this
into evidence packs' → 'reviewers paste this into
vendor-evaluation packs'
- contributor/qa-test-suite.md: 'compliance exist' coverage
description cut; 'Compliance (PCI / SOC2 / HIPAA-relevant)'
risk-class label → 'Audit-relevant'
What was kept:
- CWE references (legitimate technical pointers)
- Microsoft API/feature names that happen to use 'compliance'
literally ('Microsoft Graph compliance API',
'device-compliance validators' — these are MS product names,
not framework name-drops)
- 'NIST PQC' on the landing page (Post-Quantum Cryptography is
the actual NIST standard family, not a compliance framework)
Verified: zero hyperlinks into docs/compliance/ remain. All 24
ci-guards/*.sh pass locally. qa-doc-seed-count.sh clean.
Net diff: 26 files / -1,883 deletions in compliance/ + -32 net
across the prose sweep.
Companion edits in cowork/ (CLAUDE.md doc-tree summary +
WORKSPACE-CHANGELOG.md retirement note) land separately.
6.4 KiB
Local CA Issuer Connector — Operator Deep-Dive
Last reviewed: 2026-05-05
Operator-grade documentation for the Local CA issuer. For the connector-development context (interface contract, registry, ports/adapters), see the connector index.
Overview
The Local CA issuer signs certificates using Go's crypto/x509
library directly inside certctl-server. There is no external CA
service involved — certctl owns the signing key and emits
certificates synchronously.
Implementation lives at internal/connector/issuer/local/.
When to use this connector
Use the Local CA when:
- You're standing up an internal-only PKI and don't want to operate a separate CA service (Vault, step-ca, EJBCA).
- You want certctl to be the single point of administration: signing key, profile policy, CRL and OCSP responder, and lifecycle automation all live in one process.
- You want sub-CA mode to chain into an enterprise root (ADCS, HSM-backed root, or another upstream CA) so existing trust stores validate certctl-issued leaves automatically.
Look elsewhere when:
- You need a public-trust certificate — the Local CA is internal only. Use ACME or DigiCert / Sectigo for public trust.
- You want signing material backed by an HSM or cloud KMS — that
is on the roadmap (the
internal/crypto/signer/driver abstraction exists; HSM, cloud KMS, and SSH-CA drivers don't yet ship). Until those drivers ship, sub-CA mode pointing at a hardware-protected root is the closest production posture.
Modes
Self-signed mode (default)
Creates a CA on first use (in memory), issues certificates with proper serial numbers, validity periods, SANs, and key usage extensions. Designed for development and demos — certificates are self-signed and not trusted by browsers without operator-side trust-store work.
Sub-CA mode (production)
Loads a CA certificate and private key from disk
(CERTCTL_CA_CERT_PATH + CERTCTL_CA_KEY_PATH). The CA cert was
signed by an upstream CA (e.g. ADCS), so all issued certificates
chain to the enterprise root trust hierarchy. Clients that
already trust the enterprise root automatically trust
certctl-issued certs.
Supports RSA, ECDSA, and PKCS#8 key formats. If the paths are not
set, the connector falls back to self-signed mode. The loaded
certificate must have IsCA=true and KeyUsageCertSign.
Tree mode (Rank 8 — multi-level CA hierarchy)
When Issuer.HierarchyMode = "tree" is set on the issuer row, the
connector reads the active CA hierarchy from the
intermediate_cas table and assembles IssuanceResult.ChainPEM
by walking the parent_ca_id ancestry from the issuing leaf CA up
to the root.
Tree mode is operator-managed via the admin-gated
/api/v1/issuers/{id}/intermediates and
/api/v1/intermediates/{id} endpoints (POST to create / sign
children, GET to list / inspect, POST .../retire to two-phase
retire). The signing path is shared with single-mode (cert is
signed via c.caCert + c.caSigner from the on-disk issuing CA
cert+key); only the chain bytes differ.
RFC 5280 §3.2 (self-signed root validation), §4.2.1.9 (path-length
tightening), and §4.2.1.10 (NameConstraints subset semantics) are
enforced at the service layer fail-closed. The default is
single, byte-identical to the pre-Rank-8 historical flow.
See intermediate-ca-hierarchy.md for the operator runbook covering 4-level boundary, 3-level policy, and 2-level internal-PKI patterns, and the migration runbook for flipping a single-mode issuer to tree.
Configuration
{
"ca_common_name": "CertCtl Local CA",
"validity_days": 90,
"ca_cert_path": "/etc/certctl/ca/ca.pem",
"ca_key_path": "/etc/certctl/ca/ca-key.pem"
}
CRL and OCSP (M15b)
The Local CA serves DER-encoded X.509 CRLs unauthenticated at
GET /.well-known/pki/crl/{issuer_id} (RFC 5280 §5, RFC 8615,
Content-Type: application/pkix-crl) with 24-hour validity.
An embedded OCSP responder at
GET /.well-known/pki/ocsp/{issuer_id}/{serial} (RFC 6960,
Content-Type: application/ocsp-response) returns signed OCSP
responses for issued certificates (good / revoked / unknown
status).
Both endpoints are reachable by relying parties with no certctl API credentials, which is how standard TLS clients, browsers, and hardware appliances consume these resources.
Certificates with profile TTL < 1 hour automatically skip CRL/OCSP — expiry is treated as sufficient revocation for short-lived credentials.
Extended Key Usage support (M27)
The Local CA respects EKU constraints from certificate profiles and adjusts key usage flags accordingly:
- S/MIME (
emailProtectionEKU) →DigitalSignature | ContentCommitment. - TLS (
serverAuth/clientAuthEKU) →DigitalSignature | KeyEncipherment.
This enables a single CA to issue TLS, S/MIME, code signing, and timestamping certificates from one issuer row.
MaxTTL enforcement (M11c)
When a certificate profile defines a maximum TTL, the Local CA
caps the NotAfter field to min(validity_days, maxTTL). This
ensures certificates never exceed the profile's configured
lifetime regardless of the issuer's validity_days setting.
L-014 file-on-disk threat-model carve-out
In file-driver mode (the default), the CA private key sits on the
certctl-server filesystem as a PEM at CERTCTL_CA_KEY_PATH. This
is a standard internal-PKI posture but means filesystem
compromise of the certctl host equals signing-key compromise.
Mitigations:
- Filesystem permissions. Mode 0600, owned by the certctl service user. The connector preflight refuses to load a key whose mode is wider than 0600.
- Sub-CA rotation. Rotate the certctl sub-CA cert+key periodically (yearly is a sensible default) so a captured key has a bounded blast-radius window.
- Filesystem audit. Add an
auditctlwatch on the key path; any read/write attempt outside certctl-server's process is logged. - Move to alternate signer drivers when they ship. The
internal/crypto/signer/interface is the integration seam; HSM (PKCS#11), cloud KMS, and SSH-CA drivers will close the filesystem-residency leg without changing the rest of the signing path.
Related docs
- Connector index — interface contract, registry, port/adapter wiring
- ADCS integration — sub-CA mode rooted at ADCS
- Intermediate CA hierarchy — tree mode operator runbook
- CRL and OCSP — RFC 5280 / RFC 6960 endpoint reference