# Proposal: Certificates, TLS, and Certificate Transparency

Capability-native abstractions for X.509 certificates, trust stores,
chain verification, Certificate Transparency (CT), revocation, pinning,
automated issuance (ACME), and the TLS contexts built from all of
these.


## Why a Separate Proposal

Keys and certificates are related but different concerns. Keys are
secret material whose contract is "compute with me." Certificates are
public assertions whose contract is "believe this identity, if the
chain and CT/revocation evidence pass policy." The two failure modes
(key compromise vs. mis-issuance, revocation vs. renewal, HSM custody
vs. CA trust) barely overlap.

[cryptography-and-key-management-proposal.md](cryptography-and-key-management-proposal.md)
already covers `SymmetricKey`, `PrivateKey`, `PublicKey`, `KeySource`,
and `KeyVault`. This proposal covers everything on top: certificates,
trust anchors, CT logs, OCSP, CRLs, pinning, ACME, and TLS
configuration. A TLS server is composed from a `PrivateKey` cap (from
the key proposal) plus the certificate/verification/revocation caps
defined here.

## Problem

capOS will need certificate and TLS infrastructure for:

- TLS termination in the web text shell gateway
  ([boot-to-shell-proposal.md](boot-to-shell-proposal.md)).
- mTLS between services on a multi-host capability graph
  ([networking-proposal.md](networking-proposal.md)).
- WebAuthn attestation statement verification
  ([boot-to-shell-proposal.md](boot-to-shell-proposal.md)).
- Code signing verification for binaries, boot manifests, update
  bundles
  ([storage-and-naming-proposal.md](storage-and-naming-proposal.md)
  Open Question #5).
- Cloud KMS HTTPS API clients
  ([cryptography-and-key-management-proposal.md](cryptography-and-key-management-proposal.md)
  `CloudKmsKeySource`).
- Attestation report verification chains
  ([cryptography-and-key-management-proposal.md](cryptography-and-key-management-proposal.md)
  `AttestationKeySource`).
- Any outbound HTTPS client invoked from a service.

Without a shared abstraction each consumer invents its own "where do
trust anchors live", its own CT policy (or skips CT silently), its
own revocation story (or skips revocation silently), and its own
config surface for rustls. That is how the Linux ecosystem ended up
with `/etc/ssl/certs`, NSS, GnuTLS' own store, OpenSSL's `SSL_CTX`,
`update-ca-certificates`, and per-language HTTPS clients with
divergent trust policies. capOS is young enough to avoid that.

## Design Principle: Certificates Are Typed Capabilities

A certificate in capOS is a `Certificate` CapObject, not an opaque
byte blob flowing between services. Trust evaluation, CT and
revocation policy, and TLS configuration are expressed as cap
compositions — never as well-known paths (`/etc/ssl/certs`) or
library singletons (`rustls::RootCertStore::load_native_certs()`).

Consequences mirror the key-cap case:

- **Attenuation by scope.** A service that only needs to verify one
  signer receives a `TrustStore` cap containing that one anchor, not
  the full Mozilla root bundle. A service that must not bypass CT
  receives a `CertVerifier` whose policy has `minScts >= 2`; no
  method on that cap lets the caller lower the bar.
- **Revocation is a cap drop.** A compromised anchor is removed from
  the `TrustStore` it lives in; holders of a stale restricted view
  that still trusts it keep trusting it until they pick up the new
  version. No library's "just reload the roots" ambient step.
- **Audit is intrinsic.** Every `verifyChain`, every `addAnchor`,
  every OCSP query flows through the audit cap. A service that
  bypasses revocation shows up in the audit log as a service that
  stopped calling `OcspResponder.status`.
- **Rotation without restart.** A TLS server holds a
  `CertificateStore.watch` subscription; when an ACME renewal lands a
  fresh chain under the server's handle, the TLS stack swaps chains
  on the next handshake. No filesystem signaling, no SIGHUP, no
  "reloaded 0 of 1 certs" log lines.
- **Composition, not configuration.** A `TlsServerConfig` is a cap
  that encapsulates the key, chain source, stapler, client-auth
  verifier, and cipher policy. Building a TLS server means acquiring
  those caps and composing them, not filling in a struct with raw
  bytes.

## Schemas

### Certificates and chains

```capnp
interface Certificate {
    # Raw DER encoding — for logging, CT submission, export.
    der             @0 () -> (encoded :Data);

    # Structured fields — callers should prefer these over re-parsing.
    subject         @1 () -> (name :DistinguishedName);
    issuer          @2 () -> (name :DistinguishedName);
    serial          @3 () -> (bytes :Data);
    notBefore       @4 () -> (epochSeconds :Int64);
    notAfter        @5 () -> (epochSeconds :Int64);
    subjectAltNames @6 () -> (names :List(GeneralName));

    # Public key as a cap — callers verify signatures through this.
    publicKey       @7 () -> (pk :PublicKey);

    # Extensions the platform cares about. Returning typed views
    # forces the implementation to parse once.
    keyUsage        @8 () -> (usage :KeyUsageFlags);
    extendedKeyUsage @9 () -> (ekus :List(ExtendedKeyUsage));
    basicConstraints @10 () -> (ca :Bool, pathLenConstraint :Int32);
    nameConstraints  @11 () -> (constraints :NameConstraints);

    # Embedded SCTs (RFC 6962 §3.3). Callers that only allow
    # CT-qualified certs filter on this.
    embeddedScts    @12 () -> (scts :List(SignedCertificateTimestamp));

    # Must-staple marker (RFC 7633).
    mustStaple      @13 () -> (required :Bool);

    # Fingerprint used for pinning, logging, and human display.
    fingerprint     @14 (hash :HashAlgorithm) -> (digest :Data);

    info            @15 () -> (kind :CertificateKind,
                               algorithm :AsymmetricAlgorithm);
}

interface CertificateChain {
    # Leaf first, root (or closest-to-root) last. Length-one chains are
    # permitted (self-signed leaf).
    certificates    @0 () -> (chain :List(Certificate));
    leaf            @1 () -> (cert :Certificate);

    # Convenience: verify this chain against a trust store using a
    # given verifier. Shortcuts the CertVerifier flow for simple cases.
    verify          @2 (against :TrustStore,
                        verifier :CertVerifier,
                        atEpochSeconds :Int64,
                        hostname :Text)
                    -> (outcome :VerificationOutcome);
}

enum CertificateKind {
    endEntity    @0;
    intermediate @1;
    trustAnchor  @2;
    crossSigned  @3;
}
```

`GeneralName`, `DistinguishedName`, `KeyUsageFlags`,
`ExtendedKeyUsage`, and `NameConstraints` are plain struct/enum
definitions mirroring RFC 5280 (omitted here for brevity).

### Trust stores

```capnp
interface TrustStore {
    # List anchors as certs. Consumers typically only need metadata,
    # but anchors themselves are Certificate caps so the full
    # structured surface is available.
    anchors         @0 () -> (certs :List(Certificate));

    # Attenuate to a subset (e.g. only WebPKI roots, only corporate
    # CAs, only a specific CA). The resulting cap is a fresh
    # TrustStore that no longer references anchors outside the filter.
    restrict        @1 (filter :TrustFilter) -> (subset :TrustStore);

    # Add a trusted anchor. Only holders with write authority succeed;
    # read-only TrustStore caps reject this method.
    addAnchor       @2 (cert :Certificate, pin :AnchorPin) -> ();

    # Remove an anchor. Matches either fingerprint or subject DN.
    removeAnchor    @3 (selector :AnchorSelector) -> ();

    # Monotonic version bumped on every mutation; consumers cache by
    # version to avoid revalidating unchanged trust chains.
    version         @4 () -> (n :UInt64);
}

struct TrustFilter {
    purposes        @0 :List(CertPurpose);     # Only anchors usable for these
    fingerprints    @1 :List(Data);            # Allow-list by SHA-256
    subjects        @2 :List(Data);            # Allow-list by subject DN
    excludeFingerprints @3 :List(Data);        # Deny-list
}

struct AnchorPin {
    spkiHash        @0 :Data;                  # SHA-256 of SPKI
    hashAlgorithm   @1 :HashAlgorithm;
}

struct AnchorSelector {
    union {
        fingerprint @0 :Data;
        subject     @1 :DistinguishedName;
    }
}

enum CertPurpose {
    tlsServerAuth   @0;
    tlsClientAuth   @1;
    codeSigning     @2;
    emailSmime      @3;
    clientIdentity  @4;
    ctLog           @5;   # TrustStore of CT log public keys
    ocspSigning     @6;
    webauthnRoot    @7;   # FIDO metadata / attestation roots
}
```

### Verifier

```capnp
interface CertVerifier {
    verifyChain     @0 (chain :CertificateChain,
                        trust :TrustStore,
                        purpose :CertPurpose,
                        atEpochSeconds :Int64,
                        hostname :Text)
                    -> (outcome :VerificationOutcome);

    # Thin wrapper over a single signature check against a cert's
    # public key. Useful for WebAuthn attestation, signed manifests,
    # signed audit records.
    verifySignature @1 (cert :Certificate,
                        message :Data,
                        signature :Data,
                        scheme :SignatureScheme)
                    -> (ok :Bool);

    policy          @2 () -> (policy :VerificationPolicy);
}

struct VerificationPolicy {
    minScts                 @0 :UInt8;
    ctLogs                  @1 :TrustStore;  # which logs count
    allowedAlgorithms       @2 :List(AsymmetricAlgorithm);
    allowedSignatureSchemes @3 :List(SignatureScheme);
    requireOcsp             @4 :Bool;
    maxChainLength          @5 :UInt8;
    permitNameConstraints   @6 :Bool;
    clockSkewSeconds        @7 :UInt32;
    # When set, certificates not carrying the must-staple extension
    # are still required to deliver a stapled OCSP response.
    staplingRequired        @8 :Bool;
}

struct VerificationOutcome {
    union {
        valid   @0 :ValidChain;
        invalid @1 :VerificationFailure;
    }
}

struct ValidChain {
    anchor      @0 :Certificate;
    sctCount    @1 :UInt8;
    ocspStatus  @2 :OcspStatus;
    notAfter    @3 :Int64;     # min notAfter across the chain
}

struct VerificationFailure {
    reason      @0 :FailureReason;
    detail      @1 :Text;
}

enum FailureReason {
    unknownAnchor           @0;
    expired                 @1;
    notYetValid             @2;
    signatureMismatch       @3;
    nameMismatch            @4;
    insufficientScts        @5;
    revoked                 @6;
    ocspUnavailable         @7;
    weakAlgorithm           @8;
    policyViolation         @9;
    badEku                  @10;
    chainTooLong            @11;
    nameConstraintViolation @12;
    mustStapleMissing       @13;
    pinMismatch             @14;
}
```

Default `VerificationPolicy` presets:

- `webPkiStrict` — `minScts = 2`, `requireOcsp = true`, allowed
  algorithms and schemes drawn from Mozilla's "modern" profile.
- `webPkiLenient` — `minScts = 0`, `requireOcsp = false`. Used by
  low-value clients where misrouting is acceptable.
- `privateMtls` — `minScts = 0`, `requireOcsp = true`,
  `maxChainLength = 3`. Used between capOS services holding
  CA-issued identity certs.
- `codeSigning` — `minScts = 0`, long `notAfter` tolerances, narrow
  allowed EKU set.

### Certificate Transparency

capOS treats CT as a first-class verification input, not an add-on.
Consumers that need WebPKI trust configure a `CertVerifier` with
`minScts >= 2` and a `ctLogs` trust store; verification fails closed
if the leaf lacks that many valid SCTs signed by logs the policy
accepts.

```capnp
struct SignedCertificateTimestamp {
    logId              @0 :Data;    # SHA-256 of the log's public key
    timestamp          @1 :UInt64;  # ms since epoch
    extensions         @2 :Data;
    signature          @3 :Data;
    hashAlgorithm      @4 :HashAlgorithm;
    signatureAlgorithm @5 :SignatureScheme;
    origin             @6 :SctOrigin;
}

enum SctOrigin {
    embedded      @0;   # X.509 extension (RFC 6962 §3.3)
    ocspStapled   @1;   # OCSP response extension
    tlsExtension  @2;   # TLS handshake extension
}

interface CtLog {
    # Submission — used by ACME responders and capOS-internal CAs to
    # obtain SCTs before serving newly issued certs.
    addChain          @0 (chain :CertificateChain)
                      -> (sct :SignedCertificateTimestamp);
    addPreChain       @1 (precert :CertificateChain)
                      -> (sct :SignedCertificateTimestamp);

    # Monitoring — STH, entries, consistency proofs.
    signedTreeHead    @2 () -> (sth :SignedTreeHead);
    entries           @3 (start :UInt64, count :UInt32)
                      -> (entries :List(LogEntry));
    consistencyProof  @4 (first :UInt64, second :UInt64)
                      -> (proof :List(Data));

    info              @5 () -> (name :Text,
                                publicKey :PublicKey,
                                url :Text);
}

interface CtMonitor {
    # Watch for certificates issued under a subject-name pattern (for
    # phishing / mis-issuance detection). Events flow to the audit cap.
    watchSubject      @0 (pattern :Text) -> (subscription :CtSubscription);
    listWatched       @1 () -> (subscriptions :List(CtSubscription));
}

interface CtSubscription {
    events            @0 () -> (events :List(CtEvent));  # since last call
    cancel            @1 () -> ();
}

struct SignedTreeHead {
    treeSize       @0 :UInt64;
    timestamp      @1 :UInt64;
    rootHash       @2 :Data;
    signature      @3 :Data;
}

struct LogEntry {
    index          @0 :UInt64;
    timestamp      @1 :UInt64;
    entryType      @2 :CtEntryType;
    certificate    @3 :Data;   # ASN.1 TimestampedEntry payload
}

enum CtEntryType {
    x509Entry      @0;
    precertEntry   @1;
}

struct CtEvent {
    union {
        observed   @0 :CtObservation;
        error      @1 :CtWatchError;
    }
}

struct CtObservation {
    log          @0 :Text;           # log name or URL
    index        @1 :UInt64;
    certificate  @2 :Certificate;
    matched      @3 :Text;           # matched pattern
}
```

CT integration depends on networking and audit being available. A
capOS build without networking falls back to `minScts = 0` and skips
monitoring. The `CtMonitor` service is optional — its absence means
capOS does not detect mis-issuance against its own domains but does
not affect leaf verification, which uses only `embeddedScts` and any
SCTs delivered in the TLS handshake.

The log trust store (the `ctLogs` field of `VerificationPolicy`) is
itself a `TrustStore` cap, populated from Chrome's CT log list with
the same bundling and signing approach used for WebPKI roots. CT logs
are rotated regularly; the log list is the first place a deployment
without fresh updates starts failing in a visible way, which is the
intended failure mode.

### Revocation

```capnp
interface OcspResponder {
    # Query an OCSP responder for status. `issuer` supplies the cert
    # used to verify the responder signature chain back to a trust
    # anchor.
    status    @0 (cert :Certificate,
                  issuer :Certificate,
                  atEpochSeconds :Int64)
              -> (response :OcspResponse);
}

interface OcspStapler {
    # TLS server side: fetch and cache an OCSP response for the
    # server's own certificate. The TLS stack staples the cached
    # response into every handshake.
    currentResponse  @0 () -> (response :OcspResponse);
    refresh          @1 () -> ();
    setCertificate   @2 (chain :CertificateChain,
                         responder :OcspResponder) -> ();
}

interface CrlStore {
    # Look up a CRL for a given issuer DN; fallback when OCSP is
    # unavailable. Discouraged; CRLs do not scale.
    crlFor    @0 (issuer :DistinguishedName) -> (crl :Data);
    contains  @1 (issuer :DistinguishedName, serial :Data)
              -> (revoked :Bool);
}

struct OcspResponse {
    der         @0 :Data;        # RFC 6960 DER-encoded response
    status      @1 :OcspStatus;
    thisUpdate  @2 :Int64;
    nextUpdate  @3 :Int64;
}

enum OcspStatus {
    good                 @0;
    revoked              @1;
    unknown              @2;
    stapledAbsent        @3;   # handshake carried no stapled response
    responderUnreachable @4;
}
```

Policy choices capOS bakes into the defaults:

- `VerificationPolicy.requireOcsp = true` means OCSP-unreachable is a
  hard verification failure. Default for `CertPurpose.tlsClientAuth`
  on services facing untrusted networks; soft-fail otherwise.
- A certificate carrying the `id-pe-tlsfeature` must-staple extension
  fails verification if no stapled response is present, regardless of
  `requireOcsp`.
- `VerificationPolicy.staplingRequired = true` extends must-staple
  behavior to all certs checked under that verifier, not only the
  ones that set the extension.
- CRL support exists for legacy compatibility and explicit code-signing
  fallback. Services that can choose prefer OCSP stapling, which pulls
  revocation latency to handshake time without leaking the client's
  identity to the responder.

### Pinning

```capnp
interface PinSet {
    # A pin set is a list of (SPKI-hash, algorithm) pairs. Verification
    # succeeds only if at least one cert in the chain has an SPKI hash
    # matching a pin.
    pins        @0 () -> (entries :List(Pin));
    enforce     @1 (chain :CertificateChain) -> (outcome :VerificationOutcome);
    addPin      @2 (pin :Pin) -> ();
    removePin   @3 (pin :Pin) -> ();
    info        @4 () -> (mode :PinMode, expires :Int64);
}

struct Pin {
    spkiHash        @0 :Data;
    hashAlgorithm   @1 :HashAlgorithm;
}

enum PinMode {
    enforce     @0;   # fail closed on mismatch
    reportOnly  @1;   # succeed; emit audit event
}
```

A `PinSet` restricts an already-trusted chain; it does not add trust.
Composition is intersection: trust + CT + OCSP + pins must all pass
for verification to succeed. Pin sets are per-consumer; the web shell
gateway's client-side ACME challenge fetches do not share a pin set
with the fleet mTLS layer.

### Issuance and renewal

ACME is the only supported issuance protocol for v1. Challenge solvers
are caps so the ACME client has no ambient authority over DNS or the
HTTP server. Self-signing and internal-CA use cases are covered by a
separate `CertificateAuthority` cap (future work, see Open Questions).

```capnp
interface AcmeClient {
    # Register or rediscover an account using an account key cap.
    register    @0 (accountKey :PrivateKey, contact :List(Text))
                -> (account :AcmeAccount);

    # Order a certificate for a list of identifiers.
    order       @1 (account :AcmeAccount,
                    identifiers :List(AcmeIdentifier),
                    certKey :PrivateKey,
                    solver :ChallengeSolver)
                -> (chain :CertificateChain);

    # Renew a previously-issued chain when notAfter is near.
    renew       @2 (chain :CertificateChain,
                    certKey :PrivateKey,
                    solver :ChallengeSolver)
                -> (chain :CertificateChain);

    # Revoke a cert.
    revoke      @3 (cert :Certificate, reason :RevocationReason) -> ();

    directory   @4 () -> (url :Text, meta :AcmeDirectoryMeta);
}

interface ChallengeSolver {
    # Publish a challenge token and wait for the ACME server to
    # validate. The solver owns whatever authority is required —
    # DNS record write, HTTP server handler registration, TLS-ALPN
    # responder slot — and nothing more.
    solve       @0 (challenge :AcmeChallenge) -> (ok :Bool);
    cleanup     @1 (challenge :AcmeChallenge) -> ();
    supports    @2 () -> (types :List(AcmeChallengeType));
}

enum AcmeChallengeType {
    http01      @0;
    dns01       @1;
    tlsAlpn01   @2;
}

struct AcmeIdentifier {
    type        @0 :Text;    # "dns", "ip", ...
    value       @1 :Text;
}

interface CertificateStore {
    # Store a certificate chain under a stable handle; used by TLS
    # servers to retrieve the current chain on handshake.
    put         @0 (handle :Text, chain :CertificateChain) -> ();
    get         @1 (handle :Text) -> (chain :CertificateChain);
    list        @2 () -> (handles :List(Text));
    delete      @3 (handle :Text) -> ();
    watch       @4 (handle :Text) -> (subscription :CertSubscription);
}

interface CertSubscription {
    events      @0 () -> (events :List(CertRotationEvent));
    cancel      @1 () -> ();
}

struct CertRotationEvent {
    handle      @0 :Text;
    newChain    @1 :CertificateChain;
    rotatedAt   @2 :Int64;
}
```

The `CertificateStore.watch` subscription is the point at which an
ACME renewal service notifies a TLS server to rotate its chain. The
TLS server does not poll files, no filesystem signaling is involved,
and rotation is atomic from a handshake's perspective.

### TLS configuration

```capnp
interface TlsServerConfig {
    key             @0 () -> (k :PrivateKey);
    chainSource     @1 () -> (store :CertificateStore, handle :Text);
    stapler         @2 () -> (s :OcspStapler);

    # Optional: require client auth against these verifier + trust
    # caps. If unset, the server accepts any client or no client.
    clientVerifier  @3 () -> (v :CertVerifier, trust :TrustStore);

    alpn            @4 () -> (protocols :List(Text));
    minVersion      @5 () -> (v :TlsVersion);
    cipherPolicy    @6 () -> (policy :CipherPolicy);
}

interface TlsClientConfig {
    verifier        @0 () -> (v :CertVerifier);
    trust           @1 () -> (t :TrustStore);
    pins            @2 () -> (p :PinSet);     # null for no pinning
    clientAuth      @3 () -> (k :PrivateKey, chain :CertificateChain);
    alpn            @4 () -> (protocols :List(Text));
    minVersion      @5 () -> (v :TlsVersion);
    serverNameOverride @6 () -> (host :Text);
}

enum TlsVersion {
    tls12           @0;
    tls13           @1;
}

enum CipherPolicy {
    modern          @0;  # TLS 1.3 + AEAD only; Mozilla "modern"
    intermediate    @1;  # TLS 1.2 + 1.3; Mozilla "intermediate"
    legacy          @2;  # Explicit opt-in for ancient peers
}
```

The TLS stack (rustls in the first implementation) consumes a
`TlsServerConfig` or `TlsClientConfig` cap plus a raw `TcpSocket` and
produces a `TlsSocket`. The `TlsSocket` interface lives in
[networking-proposal.md](networking-proposal.md); this proposal only
defines the configuration surface.

## Trust Anchor Bootstrap

The v1 trust anchor bundle is Mozilla's NSS store, synthesized from
the `webpki-roots` crate data embedded in the boot manifest. Rationale:
the bundle is well-curated, auditable (Mozilla's CA Certificate
Program publishes policy and meeting minutes), and already the de
facto default for every Rust TLS stack. capOS does not invent a new
root program.

CT log lists follow the same pattern, drawn from Chrome's published CT
log list.

Update policy:

- Root-store bundles are versioned and signed. `addAnchor` on the
  system `TrustStore` is restricted to the trust-admin service, which
  accepts bundles whose signature chains to a build-time key embedded
  in the boot manifest.
- Deployment overrides (corporate CAs, explicit Mozilla-root removal)
  compose with the Mozilla bundle via `TrustStore.restrict` and
  `addAnchor` on an override store. Overrides are themselves signed
  and manifest-addressable.
- Replacement ships as a manifest update (see
  [storage-and-naming-proposal.md](storage-and-naming-proposal.md)
  Open Question #5 on manifest signing).

The manifest-embedded root store has no background network update
path by design. A compromised root requires a new signed manifest,
which requires the measured-boot chain. Root updates are a deliberate
operational event, not a silent refresh. This is a deliberate
trade-off against the Linux-style `ca-certificates` package that
updates on every apt run.

## Consumers

| Consumer                          | Uses                                                                |
|-----------------------------------|---------------------------------------------------------------------|
| Web text shell gateway            | `TlsServerConfig` + `OcspStapler`; cert from `AcmeClient`           |
| Inter-service mTLS                | `TlsServerConfig` + `TlsClientConfig` with private-PKI `TrustStore` |
| Outbound HTTPS clients (KMS, IMDS)| `TlsClientConfig` with WebPKI-strict verifier                       |
| WebAuthn attestation verification | `CertVerifier.verifySignature` with FIDO MDS `TrustStore`           |
| Code signing verification         | `CertVerifier` with codeSigning trust store + OCSP                  |
| Signed manifest verification      | `CertVerifier.verifySignature` + pinned build-time root             |
| CT mis-issuance monitoring        | `CtMonitor.watchSubject` on capOS-owned domains                     |

## Threat Model

Specific to this subsystem, independent of the crypto/key threat model:

1. **Bogus CA in the trust store.** Compromise of any CA in the
   trust store compromises every cert the verifier accepts.
   Mitigations: restrict the trust store as narrowly as each consumer
   permits (private-PKI services use a private-PKI-only store, not
   WebPKI); require CT for `tlsServerAuth`; enable `CtMonitor` for
   capOS-owned subject patterns.
2. **CT log compromise or collusion.** A log signs a non-existent
   certificate. Mitigations: require SCTs from multiple independent
   logs (`minScts >= 2`); enforce log list freshness (policy rejects
   SCTs from retired or disqualified logs); monitor STH inclusion
   proofs for capOS-issued certs.
3. **OCSP responder compromise.** The responder signs "good" for a
   revoked cert. Mitigations: OCSP response signature chains back to
   a trust anchor via the OCSP-signing EKU; short `nextUpdate`
   windows limit stale "good" responses; fail-closed when
   `requireOcsp` is set.
4. **Stapling stripping.** A MITM strips OCSP staples between a
   compliant server and the client. Mitigations: must-staple
   extension on the server cert forces closed-fail; client-side
   `staplingRequired` policy extends this to all certs.
5. **Name-constraint bypass.** An intermediate CA issues for names
   outside its constrained scope. Mitigations: `permitNameConstraints`
   always on; verifier enforces name constraints before reporting
   success.
6. **Pin brittleness.** A pin prevents legitimate rotation,
   locking out users. Mitigations: short pin expiries, `reportOnly`
   mode for rollout, pins bound to SPKI (not to full certificates).
7. **ACME challenge hijack.** A challenge solver with excessive
   authority forges validation tokens. Mitigations: each solver is a
   scoped cap (one DNS zone, one HTTP path prefix, one ALPN slot);
   solvers are per-consumer, not shared.
8. **Revocation denial-of-service.** An attacker saturates the OCSP
   responder, forcing soft-fail everywhere. Mitigations: OCSP
   stapling (server-side caching takes the responder off the hot
   path); CRL fallback under deployment policy only.
9. **Clock skew attacks.** A client with a wrong clock accepts
   expired certs or rejects valid ones. Mitigations:
   `clockSkewSeconds` has a tight default; consumers requiring
   hard-fail use an attested time source
   ([cloud-metadata-proposal.md](cloud-metadata-proposal.md)).

## Phases

Phases follow the consumers that need this infrastructure.

### Phase 1 — `Certificate`, `CertificateChain`, `TrustStore`, `CertVerifier`

- Add the schemas above to `schema/capos.capnp`.
- Implement a RAM-only trust store seeded from `webpki-roots`.
- Implement a `CertVerifier` using `rustls-webpki` for path building
  and signature verification.
- Host tests: chain verification against known-good and known-bad
  samples, name constraints, algorithm gating.

### Phase 2 — TLS server and client configs

- Add `TlsServerConfig` and `TlsClientConfig` schemas.
- Wire rustls into the networking stack as a `TlsSocket` over
  `TcpSocket`; defined in
  [networking-proposal.md](networking-proposal.md).
- `CertificateStore` with in-memory backing for the web shell gateway.

### Phase 3 — ACME client and challenge solvers

- `AcmeClient` speaking RFC 8555 (Let's Encrypt-compatible).
- `ChallengeSolver` implementations for `http-01` (against the web
  shell gateway's HTTP listener) and `tls-alpn-01`. `dns-01` follows
  once a DNS cap exists.
- `CertificateStore.watch` subscription drives TLS rotation without
  gateway restart.

### Phase 4 — OCSP stapling

- `OcspResponder` + `OcspStapler` services.
- Must-staple enforcement in `CertVerifier`.
- Cached stapled responses refresh in the background.

### Phase 5 — Certificate Transparency (submission + verification)

- SCT verification in `CertVerifier` (both embedded and
  TLS-extension SCTs).
- `CtLog` client for submission; ACME flows submit precertificates
  to required logs before handing the cert to the caller.
- Chrome CT log list bundled and signed like the WebPKI bundle.

### Phase 6 — CT monitoring

- `CtMonitor` service with `watchSubject` subscriptions.
- Observations flow to the audit cap
  ([system-monitoring-proposal.md](system-monitoring-proposal.md)).
- Proof verification: STH signatures, inclusion proofs for
  capOS-issued certs, consistency proofs across STHs.

### Phase 7 — Pinning

- `PinSet` service with enforce and report-only modes.
- Per-consumer pin policy plumbing.
- Audit records on mismatch.

### Phase 8 — CRL fallback and legacy compat

- `CrlStore` implementation for code-signing flows that require CRL.
- Policy knob to enable CRL fallback for OCSP-unreachable cases.

### Phase 9 — Private CA

- `CertificateAuthority` cap for capOS-internal issuance (mTLS
  fleet bootstrapping without an external ACME dependency).
- CA keys live in `KeyVault` with strict seal policy.
- Internal CT log (optional) for mis-issuance detection within a
  private fleet.

## Relationship to Other Proposals

- **[cryptography-and-key-management-proposal.md](cryptography-and-key-management-proposal.md)**
  — supplies the `PrivateKey` / `PublicKey` / `KeyVault` /
  `KeySource` primitives this proposal consumes. A TLS server's key
  cap, an ACME account key, and an internal CA signing key all live
  in a `KeyVault` sealed under a `KeySource`.
- **[networking-proposal.md](networking-proposal.md)** — defines the
  `TcpSocket` this proposal wraps and the `TlsSocket` interface that
  consumes `TlsServerConfig` / `TlsClientConfig`. mTLS between
  services uses this proposal's verifier and trust store.
- **[boot-to-shell-proposal.md](boot-to-shell-proposal.md)** — web
  text shell gateway consumes `TlsServerConfig`; ACME via this
  proposal provides the cert. WebAuthn attestation verification uses
  `CertVerifier.verifySignature`.
- **[cloud-metadata-proposal.md](cloud-metadata-proposal.md)** —
  `InstanceIdentity` is often expressed as a signed JWT or X.509
  certificate; verifying attestation statements uses `CertVerifier`.
- **[storage-and-naming-proposal.md](storage-and-naming-proposal.md)**
  — Open Question #5 (manifest trust, secure boot) is the source of
  the build-time key that signs root-store and CT-log bundles.
- **[system-monitoring-proposal.md](system-monitoring-proposal.md)**
  — every `verifyChain`, `addAnchor`, OCSP query, CT observation, and
  pin mismatch flows through the audit cap.
- **[security-and-verification-proposal.md](security-and-verification-proposal.md)**
  — the certificate parser, path builder, and policy engine are top
  targets for fuzzing and property testing. rustls and
  `rustls-webpki` are the first-layer implementation; capOS-specific
  policy glue (CT, stapling, pinning) gets its own tier of tooling.
- **[user-identity-and-policy-proposal.md](user-identity-and-policy-proposal.md)**
  — client-auth certs and per-user mTLS identity consume
  `TlsClientConfig` with a per-session `PrivateKey` cap.

## Open Questions

1. **Canonical default policy.** Should `webPkiStrict` require `minScts
   >= 2` from day one, or is that too aggressive before CT log list
   curation ships? Chrome requires 2; Apple requires varying counts by
   cert lifetime. Probably match Chrome initially and revisit.
2. **CRL scope.** Is CRL support worth the footprint at all, or should
   capOS ship OCSP-only and refuse to verify against CRL-only CAs?
   Leaning "CRL for code signing only", not for TLS.
3. **Private CA surface.** A `CertificateAuthority` cap with `issue`,
   `revoke`, and `listIssued` methods is straightforward, but the
   policy for issuance (SAN constraints, lifetime caps) deserves its
   own schema pass. Deferred to Phase 9.
4. **Trust-store delta signing.** Signing every bundle replacement is
   expensive. A delta format (add/remove anchors with signed manifest
   patches) would be lighter; worth it only once bundle churn becomes
   a real operational cost.
5. **OCSP nonce support.** Nonces prevent replay but most responders
   do not honor them. Ship without and revisit if a deployment needs
   replay-resistance.
6. **`webpki-roots` crate churn.** The crate publishes a new version
   on Mozilla NSS changes, which is frequent. capOS needs a clean
   bump story — probably "new release triggers a trust-store bundle
   rebuild", automated in CI.
7. **Stapling cache persistence.** Must the `OcspStapler` cache
   survive reboot? Surviving reboot avoids a refresh storm at
   startup but risks serving very stale responses. Probably: cache is
   per-boot, with a short pre-refresh window before `nextUpdate`.
8. **Client-cert private key reuse.** If a client uses one mTLS
   identity across many outbound connections, does each
   `TlsClientConfig` hold its own `PrivateKey` cap (wasteful) or
   share one (safe, since the cap's `sign` method is the only surface)?
   Probably share by default; make duplication explicit if needed.
9. **Integration with `CredentialStore`.** Some WebAuthn authenticators
   return attestation certs that must be chain-verified against FIDO
   MDS. The verification uses `CertVerifier`; the MDS trust store is
   a `TrustStore` maintained separately from the WebPKI bundle. How
   does MDS update cadence fit the no-background-update policy?
   Probably: MDS updates ride manifest updates, same as root bundles.
10. **GOST trust chains.** The
    [formal-mac-mic-proposal.md](formal-mac-mic-proposal.md) GOST
    track implies GOST-signed certificate chains. The `CertVerifier`
    algorithm enum is already open-ended; the work is algorithm
    implementation, not schema evolution.
