This document traces the controls required by the CA/Browser Forum TLS
Baseline Requirements (BR), the S/MIME Baseline Requirements (SMBR), and
the WebTrust for Certification Authorities principles to the secsy-pki
feature, package, and file that implements them — with an explicit
gaps & assumptions column. It is the technical companion to the
Certificate Policy / CPS.
Every entry was checked against the code on the main branch. The goal is
an accurate map, not an aspirational one: where a requirement is only
partially met, is config-dependent, or is an operator responsibility the
software cannot discharge, the row says so plainly.
The requirement, with the closest normative clause. Clause numbers track the BR/SMBR/WebTrust versions current at the time of writing; [OPERATOR: re-verify clause numbers against the exact version you are audited to.]
Implementation
The feature and the server/… package/file that provides it.
Status
✅ enforced in code · ⚙️ implemented but config-dependent (must be enabled/tuned) · 👤 operator/organizational control the software supports but cannot itself satisfy · ⛔ not implemented (gap).
Gaps & assumptions
What the control does not cover, what must be assumed true, or what the operator must do.
Scope caveat. secsy-pki is CA software. Publicly-trusted status,
WebTrust/ETSI attestation, physical/personnel security, and the legal CP/CPS
are properties of the operating organization and its audit, not of the
code. Rows marked 👤 are included because a real audit will ask for them; the
"Implementation" column then points at the supporting tooling, and the gap
column names the operator obligation.
Key provider generates RSA-2048/4096, ECDSA P-256/384/521, Ed25519; RSA < 2048 rejected. internal/keyprovider, internal/pki/keygen.go; FIPS floor in internal/fips
✅
Ed25519 & ML-DSA/hybrid are offered for non-public/internal use; they are not publicly-trusted TLS algorithms — restrict public profiles to RSA/ECDSA. RSA 3072 not a distinct built-in size.
Signature algorithm selected by key type (SHA-256/384/512); SHA-1 never used and rejected under FIPS. internal/pki, internal/fips (CheckSignatureAlgorithm)
✅
—
CA private key generated in & protected by a validated cryptographic module; non-exportable (BR §6.1.1, §6.2)
Keys generated on-device with CKA_TOKEN/PRIVATE/SENSITIVE=true, CKA_EXTRACTABLE=false, sign-only; no export API. internal/pki/signer.go:536-548, internal/keyprovider; ADR 0002
✅ / 👤
The FIPS 140-2/3 validation level is a property of the operator's HSM. SoftHSM (dev/CI) is not validated. Operator must run a validated HSM and record its certificate.
Implements the ACME-based §3.2.2.4 methods only. Other methods (e.g. email-to-DNS-contact, phone, IP §3.2.2.5) are not implemented. Validation-reuse windows are operator policy.
CAA checking, fail-closed (BR §3.2.2.8, RFC 8659)
Fail-closed pre-issuance CAA gate on every DNS issuance; issue/issuewild/iodef. internal/caa, gate in internal/ca/ct.gobuildLeaf
✅ / ⚙️
Operator must set caa.identifier to the CA's domain. Mode is per-profile (off/permissive/enforce); enforce is required for public trust.
CAA accounturi / validationmethods (RFC 8657)
Enforced with per-request context threaded from ACME finalize. internal/caa (RequestContext)
✅
Account-URI matching depends on the ACME account URL the operator publishes.
Not implemented. DNS/validation is single-vantage-point. A CA seeking public trust after the MPIC effective date must add multi-perspective corroboration (proxy or external service) in front of validation.
Public policy caps at 398 days; server profile default/max 397 d. internal/certlint (validity_tls_max), internal/ca/profile.go
✅ / ⚙️
The phased reductions (SC-081: 200/100/47-day) are not auto-enforced by date — tighten profiles.*.max_validity_days to comply as each milestone lands. Non-public profiles use their own caps.
Uses a built-in reserved-TLD/CIDR list (RFC 1918/6598/6761 etc.); not the live public-suffix list — add domains to the profile if your policy is stricter.
Per-profile; fail-open is a supported opt-in (ADR 0003). Operator must configure qualified logs and enough SCTs to meet current browser CT policy — the software does not track browser-specific log-diversity rules.
Pre-issuance linting (industry best practice; misissuance prevention)
Hand-rolled BR checks always on; optional zlint suite under -tags zlint. internal/certlint, internal/certlint/zlint.go
✅ / ⚙️
The always-on checks are a curated subset, not the full zlint corpus. For maximal coverage build with -tags zlint and set the profile lint.zlint level.
Reject known-weak keys (ROCA, Debian, small factors) (BR §4.9.1.1(4), §6.1.1.3)
Fail-closed pre-issuance key-quality gate on every issuance surface (REST/ACME/EST/SCEP/CMP/SPIFFE) and the dry-run preview: ROCA/CVE-2017-15361 fingerprint, RSA exponent (e≥65537, odd) and modulus (odd, ≥2048-bit) checks, optional Debian OpenSSL weak-key blocklist, and an operator-managed compromised-key blocklist. internal/keycheck, internal/ca/keycheck.go; key-checks
✅ / ⚙️
Per-profile enforce/warn. The Debian weak-key list is operator-supplied (no blob vendored); load it via keychecks.weak_key_blocklist_paths. Small-factor/Fermat cofactor scanning is not performed (structural ROCA + blocklists only).
Delegated responder is optional (else the CA key signs).
OCSP must not answer "good" for a non-issued serial (BR §4.9.10)
Responder returns unknown for unrecorded serials and unauthorized when not the issuer. internal/ca/ocsp.go:133, :157-161
✅
Assumes the issued-certificate store reflects every serial the CA has signed (true for the normal issuance path; migrated/imported inventories must be complete).
Status availability during HSM outage / CDN offload (availability)
Batch OCSP pre-signing into the cache (survives HSM outage); static publisher (dir/S3, atomic swap + manifest). internal/ca/presign.go, internal/publish
⚙️
Nonce requests always bypass the cache (signed fresh), as intended.
Revocation within 24 h / 5 days of a valid problem report (BR §4.9.1.1)
Full cryptographic proof requires a YubiHSM-class device with hardware attestation + forced audit; SoftHSM gives the hash-chain checks without the hardware attestation.
Segregation of duties / trusted roles (BR §5.2.1; WebTrust personnel)
Mailbox control proof for public S/MIME is via ACME email-reply-00 (RFC 8823, internal/mailtransport); operator-driven issuance relies on the RA process.
Forbid non-mailbox SAN types (§7.1.4.2.1)
Reject dNSName/iPAddress/URI SANs in mailbox certs. internal/certlint/smime.go (smime_san_types)
WebTrust's criteria are organizational; this table maps each principle to the
supporting software control and names the operator obligation. See §1.5–1.6
above for the detailed BR rows.
These are the honest limitations an auditor will find. They are design choices
or unimplemented items, not claims the software makes and fails.
MPIC (BR §3.2.2.9) is not implemented — validation is single-vantage.
Required for public trust after its effective date.
Weak-key rejection is structural, not full-factoring — the pre-issuance
gate (key-checks) covers ROCA/CVE-2017-15361, RSA
exponent/modulus policy, the Debian OpenSSL weak-key list, and an operator
compromised-key blocklist, but does not scan for small factors / Fermat-close
primes / shared-factor (batch-GCD) weaknesses.
Validity-cap phased reductions (SC-081) are not date-driven — the cap is
398 days unless the operator tightens the profile.
CT can be configured fail-open per profile, and browser-specific log
diversity/SCT-count policy is not tracked by the software.
Always-on linting is a curated subset; full zlint coverage requires the
-tags zlint build.
DV-class only: OV/EV organization/individual identity vetting is a manual
RA process, not automated.
Publicly-trusted status, HSM FIPS-validation level, physical/personnel
security, revocation SLA, and the legal CP/CPS are operator/organizational
responsibilities the software supports but cannot satisfy.
certificateHold exists and must be disabled for public TLS profiles by
the operator (the BRs forbid TLS suspension).