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.
Implementation Status
The schemas and Phase 1-9 ordering below are design beyond the landed Phase 1
subset: vendored WebPKI roots, capos-tls host verifier logic, and the
Certificate / CertificateChain / TrustStore / CertVerifier schema
surface. The remaining near-term work is decomposed into a bounded slice chain
owned by Certificates / TLS and the
Certificates / TLS track in docs/tasks/README.md. The cut lands the lowest-risk real
logic first. The Phase 2 client local proof landed on 2026-06-08: a userspace
TLS 1.3 client completes one handshake over a userspace-served TcpSocket cap
with a vendored embedded-tls state machine while validating the peer chain
with capos-tls. The
key-management proposal now has
the minimal PrivateKey / PublicKey ABI, RAM signing core, and RAM-only
KeyVault custody plus a development-only software KeySource for local
TLS/ACME proofs, but no production custody source yet. Production/public
server-side TLS remains blocked on reviewed custody and a server cert source:
- Phase 1 deps [DONE 2026-06-03]. vendor
rustls-webpki+webpki-rootsas no_std+alloc snapshots with provenance:cloud-tls-vendor-rustls-webpki-roots-no-std-provenance. - Phase 1 [DONE 2026-06-03].
Certificate/CertificateChain/TrustStore/CertVerifierschema + host-tested verify logic over a RAM-only webpki-roots store:cloud-tls-cert-truststore-certverifier-phase1-host-proof. - Phase 2 (client) [DONE 2026-06-08]. One userspace TLS client handshake
over the Phase C userspace
TcpSocketcap, validating the peer chain with the Phase 1 verifier and a vendoredembedded-tlsTLS 1.3 state machine:cloud-tls-client-handshake-over-tcpsocket-local-proof. - Phase 2 (server consumer) – capOS-terminated TLS for the self-hosted Web
UI (the direct-termination successor to the provider-terminated bootstrap
below, not the closeout path for the first public proof), blocked additionally
on a sealed
PrivateKeycap and a server cert source:cloud-tls-self-hosted-webui-terminated-endpoint. - Minimal TLS/ACME key custody [DONE for local proofs]. The TLS server key and ACME
account key need a
PrivateKey/KeyVault/KeySourcesubset. The minimalPrivateKey/PublicKeyABI and RAM signing proof landed 2026-06-04; RAMKeyVaultcustody landed 2026-06-05; development-only softwareKeySourcebootstrap landed 2026-06-05. - Phase 3 (ACME successor chain) [PARTIAL]. The local ACME account/order
core landed on 2026-06-08:
capos-tlssigns ES256 JWS requests through anAcmeAccountPrivateKeycap, submits a CSR signed by a TLS-purpose key cap, and parses a returned local test certificate chain. Remaining Phase 3 work is scopedhttp-01challenge solving,CertificateStore.watchrenewal/rotation, and then a public GCE capOS-terminated direct-termination proof. These are successor tasks after the provider-managed first public proof, not replacements for it:cloud-tls-acme-account-order-local-proof[DONE 2026-06-08],cloud-tls-acme-http01-challenge-solver-local-proof,cloud-tls-acme-renewal-certstore-rotation-local-proof, andcloud-gce-public-webui-letsencrypt-direct-termination-proof.
Phases 4-9 (OCSP, CT, pinning, CRL, private CA) remain undecomposed design.
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
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.
Two adjacent proposals draw their own trust boundaries instead of extending this one:
- OIDC and OAuth2 tokens are not X.509.
OIDC and OAuth2 covers
short-lived bearer tokens (ID tokens, access tokens, DPoP proofs,
client assertions) signed by JWKS-published keys, not by X.509
trust chains. Where an OIDC issuer’s
private_key_jwtclient assertion or workload-identity federation flow does need an X.509 cert, the signing key is aPrivateKeycap from the key proposal and the cert is aCertificatecap from this one. The token capability objects, JWKS verifier, and DPoP machinery live in the OIDC proposal; this proposal only supplies the verifier when an OIDC flow happens to land on an X.509 binding. - SSH host keys are not X.509 certs.
SSH Shell Gateway uses raw SSH
host-key signatures (
SshHostKey.signExchangeHash) and TOFU/authorized-key trust, not WebPKI chains. The host key is a narrow wrapper around aPrivateKeycap from the key proposal, constrained to SSH host-key signing; this proposal’sCertificate,TrustStore,CertVerifier, and ACME flow are not consumed by the SSH transport. SSH and TLS/mTLS are intentional siblings — SSH for raw operator/agent access without a CA, TLS for PKI-integrated services.
Problem
capOS will need certificate and TLS infrastructure for:
- TLS termination in the web text shell gateway (Boot to Shell).
- mTLS between services on a multi-host capability graph
(Networking). TLS wraps the
TcpSocketcap defined there; in Phase A-B that socket state is kernel-resident smoltcp, and TLS sees it through the same cap boundary after Phase C migrates the stack to userspace. - WebAuthn attestation statement verification (Boot to Shell).
- Code signing verification for binaries, boot manifests, update bundles (Storage and Naming Open Question #5).
- Cloud KMS HTTPS API clients
(Cryptography and Key Management
CloudKmsKeySource). - Attestation report verification chains
(Cryptography and Key Management
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
TrustStorecap containing that one anchor, not the full Mozilla root bundle. A service that must not bypass CT receives aCertVerifierwhose policy hasminScts >= 2; no method on that cap lets the caller lower the bar. - Revocation is a cap drop. A compromised anchor is removed from
the
TrustStoreit 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, everyaddAnchor, every OCSP query flows through the audit cap. A service that bypasses revocation shows up in the audit log as a service that stopped callingOcspResponder.status. - Rotation without restart. A TLS server holds a
CertificateStore.watchsubscription; 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
TlsServerConfigis 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
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
interface TrustStore {
# List anchors as WebPKI trust-anchor records. Mozilla/WebPKI roots may not
# be representable as full Certificate caps.
anchors @0 () -> (anchors :List(TrustAnchorInfo));
# 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
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 :TrustAnchorInfo;
sctCount @1 :UInt8;
ocspStatus @2 :OcspStatus;
notAfter @3 :Int64; # min notAfter across the verified path
}
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, longnotAftertolerances, 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.
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
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 = truemeans OCSP-unreachable is a hard verification failure. Default forCertPurpose.tlsClientAuthon services facing untrusted networks; soft-fail otherwise.- A certificate carrying the
id-pe-tlsfeaturemust-staple extension fails verification if no stapled response is present, regardless ofrequireOcsp. VerificationPolicy.staplingRequired = trueextends 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
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).
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
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 consumes a TlsServerConfig or TlsClientConfig cap plus a raw
TcpSocket and produces a TlsSocket. The first landed local client proof uses
embedded-tls directly over a userspace-served TcpSocket; the broader
config-cap service surface remains the Phase 2 TLS-service design. The
TlsSocket draft interface lives in the
“TLS Layering” section of
Networking; this proposal only
defines the configuration surface. While TcpSocket state remains
kernel-resident through Phase A-B of the networking proposal, the
TLS stack itself is a userspace consumer of that cap and does not
move into the kernel — the certificate parser, path builder, and
TLS state machine all run in the userspace TLS service.
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.
addAnchoron the systemTrustStoreis 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.restrictandaddAnchoron an override store. Overrides are themselves signed and manifest-addressable. - Replacement ships as a manifest update (see Storage and Naming 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.
Bootstrap TLS for the First Public GCE Web UI
The schemas above are no longer entirely pre-implementation design: the Phase 1
verifier, Phase 2 client handshake over a userspace TcpSocket, local
key-custody precursors, and the local ACME account/order/finalize core have
landed. The server-side TlsServerConfig / TlsSocket consumer, scoped
http-01, CertificateStore.watch renewal, production key custody, and later
CT/OCSP/pinning surfaces remain future or blocked work. The first time the
self-served capOS Web UI (remote-session-web-ui) is exposed to a public
operator browser on GCE, capOS therefore still does not terminate TLS
itself. The reviewed first ingress terminates HTTPS at the GCP external load
balancer’s Google front end against a provider-managed certificate; capOS serves
only plain HTTP/1.1 on a backend port reachable solely from the load balancer
and health-check source ranges. The full posture (firewall scope, browser
session rules, evidence, teardown) is recorded in the “Public Web UI Ingress
Policy” section of
Cloud Deployment and the on-hold
public Web UI ingress task;
this note records only where TLS terminates and who holds the key.
Bootstrap consequences specific to this proposal:
- No capOS private-key custody in the first proof. The TLS private
key stays on the provider side. No
PrivateKeycap,KeyVault, orKeySourcefrom Cryptography and Key Management is consumed for the first public Web UI endpoint, and no key material is written into the disk image, manifest, or evidence directory. - No capability-native verification on the public hop. Because the
Google front end performs TLS, the
Certificate,TrustStore,CertVerifier,OcspStapler, and ACME flows defined here are not exercised by the first public Web UI proof. Provider-managed certificate lifecycle (issuance, renewal, revocation) is the provider’s, not capOS’s. - Successor path is the direct-termination shape. When this
proposal’s
TlsServerConfigplus anAcmeClient/ChallengeSolver(Phases 2-3) ship over the userspace TLS stack, a direct-external-IP, capOS-terminated ingress becomes a separately reviewed second option. At that point the certificate is aCertificateChaincap, the key is a sealedPrivateKeycap,CertificateStore.watchdrives rotation, and the load-balancer-terminated path becomes one deployment choice rather than the only buildable one. The bootstrap step does not foreclose the capability-native model; it precedes it. - Let’s Encrypt is successor-only until the remaining prerequisites land.
The
Certificates / TLS backlog now names the
landed local key-custody precursor (
PrivateKey/KeyVault/ developmentKeySource), landed TLS client over the userspaceTcpSocket, and landed local ACME account/order/finalize core. Remaining successor prerequisites are the capOS-terminated Web UI TLS endpoint, scopedhttp-01solver,CertificateStore.watchrenewal and rotation, and then the on-hold Let’s Encrypt direct-termination GCE proof. Local ACME proofs use a local Let’s Encrypt-compatible directory. A real GCE or Let’s Encrypt staging/production run additionally needs a controlled public DNS name and explicit billable/public-ingress and CA authorization. Raw key material must not be written to manifests, images, logs, or evidence.
This mirrors the trust-anchor bootstrap above: capOS ships a pragmatic, reviewed interim posture (here, provider-terminated TLS) and migrates to the capability-native model as the implementing subsystems land, rather than blocking the first public proof on the full stack.
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:
- 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; enableCtMonitorfor capOS-owned subject patterns. - 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. - 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
nextUpdatewindows limit stale “good” responses; fail-closed whenrequireOcspis set. - 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
staplingRequiredpolicy extends this to all certs. - Name-constraint bypass. An intermediate CA issues for names
outside its constrained scope. Mitigations:
permitNameConstraintsalways on; verifier enforces name constraints before reporting success. - Pin brittleness. A pin prevents legitimate rotation,
locking out users. Mitigations: short pin expiries,
reportOnlymode for rollout, pins bound to SPKI (not to full certificates). - 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.
- 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.
- Clock skew attacks. A client with a wrong clock accepts
expired certs or rejects valid ones. Mitigations:
clockSkewSecondshas a tight default; consumers requiring hard-fail use an attested time source (Cloud Metadata).
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
CertVerifierusingrustls-webpkifor 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
TlsServerConfigandTlsClientConfigschemas. - Wire a userspace TLS state machine into the networking stack as a
TlsSocketoverTcpSocket; defined in Networking. CertificateStorewith in-memory backing for the web shell gateway.
Phase 3 — ACME client and challenge solvers
AcmeClientcore speaking the local RFC 8555 account/order/finalize flow has landed incapos-tls; served capability wiring and public CA transport remain future.ChallengeSolverimplementations forhttp-01(against the web shell gateway’s HTTP listener) andtls-alpn-01.dns-01follows once a DNS cap exists.CertificateStore.watchsubscription drives TLS rotation without gateway restart.
Phase 4 — OCSP stapling
OcspResponder+OcspStaplerservices.- 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). CtLogclient 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
CtMonitorservice withwatchSubjectsubscriptions.- Observations flow to the audit cap (System Monitoring).
- Proof verification: STH signatures, inclusion proofs for capOS-issued certs, consistency proofs across STHs.
Phase 7 — Pinning
PinSetservice with enforce and report-only modes.- Per-consumer pin policy plumbing.
- Audit records on mismatch.
Phase 8 — CRL fallback and legacy compat
CrlStoreimplementation for code-signing flows that require CRL.- Policy knob to enable CRL fallback for OCSP-unreachable cases.
Phase 9 — Private CA
CertificateAuthoritycap for capOS-internal issuance (mTLS fleet bootstrapping without an external ACME dependency).- CA keys live in
KeyVaultwith strict seal policy. - Internal CT log (optional) for mis-issuance detection within a private fleet.
Relationship to Other Proposals
- Cryptography and Key Management
— supplies the key primitives this proposal consumes. Its minimal
PrivateKey/PublicKeyABI, RAM signing core, RAM-onlyKeyVaulthandle custody, and development-only softwareKeySourcebootstrap exist for the local TLS/ACME precursor. Persistence and production custody remain future. A TLS server’s key cap, an ACME account key, and an internal CA signing key all live in aKeyVaultsealed under aKeySource(typical choices:Tpm2KeySourcefor fleet mTLS identities,PasskeyPrfKeySourceorPassphraseKeySourcefor operator client-auth,CloudKmsKeySourcefor cloud-anchored CAs, development-only software sources for local ACME accounts). TLS certificate keys and ACME account JWS keys remain purpose-separated; the key proposal names that split asKeyPurpose.tlsandKeyPurpose.acmeAccount. - Networking — defines the
TcpSocketthis proposal wraps and the draftTlsSocketinterface that consumesTlsServerConfig/TlsClientConfig. In the proposal’s Phase A-B the socket state is kernel-resident smoltcp; the TLS stack consumes that cap from userspace and does not move into the kernel even before Phase C. mTLS between services uses this proposal’s verifier and trust store on top of that same cap. - OIDC and OAuth2 —
separate trust model (JWKS-signed bearer tokens, not X.509
chains). The two proposals meet only at the corners where OIDC
flows do bind to an X.509 cert:
private_key_jwtclient assertions andtls_client_auth/self_signed_tls_client_authOAuth2 client authentication consume aPrivateKeyfrom the key proposal plus aCertificate/CertificateChainfrom this one; workload-identity federation (RFC 8693) and outbound HTTPS to IdP/JWKS endpoints consume aTlsClientConfigwith awebPkiStrictverifier. Inbound bearer-token verification stays in the OIDC proposal. - SSH Shell Gateway — explicitly a
non-consumer. SSH uses raw host-key signatures and
TOFU/authorized-key trust, not WebPKI; the host key wraps a
PrivateKeyfrom the key proposal directly, not aCertificatefrom this one. SSH and TLS/mTLS coexist as the two operator-facing remote-shell paths: SSH for CA-free operator/agent access, TLS/mTLS (the web text shell gateway plus future Telnet-over-TLS paths) for PKI-integrated environments. - Boot to Shell — web
text shell gateway consumes
TlsServerConfig; ACME via this proposal provides the cert. WebAuthn attestation verification usesCertVerifier.verifySignature. - Cloud Metadata —
InstanceIdentityis often expressed as a signed JWT or X.509 certificate; verifying attestation statements usesCertVerifier. - Storage and Naming — 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
— every
verifyChain,addAnchor, OCSP query, CT observation, and pin mismatch flows through the audit cap. - Security and Verification
— the certificate parser, path builder, and policy engine are top
targets for fuzzing and property testing. The landed client path uses
embedded-tlsplusrustls-webpki-backedcapos-tlsverification; capOS-specific policy glue (CT, stapling, pinning) gets its own tier of tooling. - User Identity and Policy
— client-auth certs and per-user mTLS identity consume
TlsClientConfigwith a per-sessionPrivateKeycap.
Open Questions
- Canonical default policy. Should
webPkiStrictrequire `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.
- 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.
- Private CA surface. A
CertificateAuthoritycap withissue,revoke, andlistIssuedmethods is straightforward, but the policy for issuance (SAN constraints, lifetime caps) deserves its own schema pass. Deferred to Phase 9. - 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.
- OCSP nonce support. Nonces prevent replay but most responders do not honor them. Ship without and revisit if a deployment needs replay-resistance.
webpki-rootscrate 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.- Stapling cache persistence. Must the
OcspStaplercache 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 beforenextUpdate. - Client-cert private key reuse. If a client uses one mTLS
identity across many outbound connections, does each
TlsClientConfighold its ownPrivateKeycap (wasteful) or share one (safe, since the cap’ssignmethod is the only surface)? Probably share by default; make duplication explicit if needed. - Integration with
CredentialStore. Some WebAuthn authenticators return attestation certs that must be chain-verified against FIDO MDS. The verification usesCertVerifier; the MDS trust store is aTrustStoremaintained 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. - GOST trust chains. The
Formal MAC/MIC GOST
track implies GOST-signed certificate chains. The
CertVerifieralgorithm enum is already open-ended; the work is algorithm implementation, not schema evolution.