Trust architecture and PKI
How the platform establishes cryptographic trust, and where your keys stay yours.
This page covers the trust model and the key flow, and nothing else. The question it answers is the one a security reviewer asks first: what holds the private keys, what can reach them, and what happens when something in the chain is wrong.
One key, and everything else derived from it
A deployment persists exactly one secret of cryptographic consequence: 32 bytes of root key material. The Root CA, the Fulcio intermediate CA, the timestamp authority leaf, the JWT signing key, and the session secrets are all derived from it in memory at startup using HKDF-SHA256 (RFC 5869), each with its own domain-separated label.
Nothing else is stored. There is no keystore to back up, and no file on a running node holds a usable CA private key.
The derivation is deterministic. The same root material produces byte-identical public keys on every replica and across every restart, which is what lets a signing certificate minted on one replica verify against the CA on another without any shared state between them. Certificate DER bytes do vary per boot, because ECDSA signing is hedged, so the issued bundle is persisted and keyed by root-key fingerprint. Trust is evaluated on public keys, and those are stable.
| Key | Role | Lifetime | Constraint |
|---|---|---|---|
| Root CA | Self-signed trust anchor | 20 years | MaxPathLen=1 |
| Fulcio CA | Issues short-lived signing leaves | 5 years | MaxPathLen=0, EKU code signing |
| TSA leaf | RFC 3161 timestamps | 3 years | EKU id-kp-timeStamping |
All keys are ECDSA P-256 with SHA-256. Nothing negotiates an algorithm at runtime, so there is no downgrade path to misconfigure.
Where the root key lives
Two sources, chosen by deployment mode. The derivation tree above is identical in both.
On AWS
The root comes from a KMS HMAC key. At boot the service calls kms:GenerateMac over a fixed label and gets back 32 deterministic bytes. The KMS key material never leaves the HSM, and the key ARN is read from SSM Parameter Store rather than being baked into configuration. The keys are provisioned by a bootstrap step; the infrastructure stack imports them and never creates them, so no deployment can quietly mint a new root.
Standalone
The root is an ECDSA P-256 PEM file on disk at a configured path, mode 0600. The operator generates it with openssl. The server never generates one on its own: if the path is set and the file is missing, boot fails with instructions rather than silently creating a new root, because silent regeneration would invalidate every derived credential on a restart after a volume failure.
The tradeoff, stated plainly. The HMAC-derived root has a property worth knowing before you find it. Any principal holding
kms:GenerateMacon that key can recover the 32-byte root offline and derive every downstream key. The mitigations are that workload identity (IRSA) scopes the permission to a single service account, CloudTrail records every call, the recovered material grants platform credentials only and no cloud or tenant data access, and rotating the KMS key invalidates all derived material at once.
Running on AWS
Every API replica calls the same KMS key at boot and receives the same 32 bytes, so each one derives the same chain independently. Replicas hold no key material between them and coordinate nothing to agree on trust: a certificate minted on one replica verifies against the CA on any other. Adding a replica requires no key distribution step, and losing one loses nothing. The KMS key material itself never leaves the HSM.
Running standalone
The same binary, with every dependency embedded: the CA, the timestamp authority, the evidence store, identity, workflows, and the web UI. Storage is SQLite in WAL mode and a local blob directory. There is no outbound network dependency at all.
This is the mode that matters for a restricted or disconnected environment, where a dependency on a public certificate authority or a hosted timestamp authority would be a blocker on its own. Neither is in the path.
How a client learns what to trust
Clients do not ship pinned roots. They fetch one unauthenticated document and take both the endpoints and the trust anchors from it:
GET ${PLATFORM}/.well-known/judge-configuration
The response carries the Fulcio and TSA endpoints, the evidence store URL, and a trust_bundle_pem that inlines the Fulcio CA together with the Root CA. One fetch establishes where to sign and what to trust. A separate URL serves the TSA certificate chain, which is what a verifier needs to validate a timestamp, and to validate a signature after the leaf that made it has expired.
The practical effect is that rotating a chain does not require redistributing anything to clients. They read the current bundle on the next verification.
Signing and uploading are different acts
These get conflated, and separating them is deliberate.
| Act | What it proves | Authentication |
|---|---|---|
| Sign | This identity produced this evidence at this time | None. An ambient workload OIDC token is sufficient. |
| Upload | This evidence belongs to this tenant and product | A session that carries the tenant binding. |
Signing is keyless and needs no login. The build job exchanges an ambient OIDC token for a certificate valid for roughly ten minutes, signs, gets the signature timestamped, and throws the private key away. Uploading is what binds evidence to an owner, so that is where a session is required.
- 1Build jobRequests an ambient workload token from the OIDC provider — a short-lived JWT carrying the job’s identity in its claims.
- 2Fulcio CAExchanges that token for a signing certificate: a leaf valid for about ten minutes, chaining to the Fulcio CA and then the Root CA.
- 3Build jobBuilds a DSSE envelope over the in-toto predicate and signs it with the ephemeral private key.
- 4TSATimestamps the signature (RFC 3161), which keeps it verifiable after the short-lived certificate expires.
- 5Build jobDiscards the private key.
- 6Evidence storeReceives the signed envelope — the upload, not the signing, is the session-authenticated step.
No long-lived signing credential is ever issued, so there is nothing to steal, and nothing to find in a build image six months from now. The RFC 3161 timestamp is what keeps the signature verifiable after the certificate that made it has expired.
Verifying
Verification runs the same trust chain in reverse. The verifier fetches the discovery document for the trust bundle and the TSA chain URL, retrieves the attestations for the artifact digest from the evidence store, chains each signing leaf to the Fulcio CA and then to the Root CA, and validates the RFC 3161 timestamp against the TSA chain.
Then it evaluates the policy, which is where the real decision lives: which steps are required, which identities are allowed to sign each one, and what rules the evidence has to satisfy. A signature that is cryptographically valid but produced by the wrong identity, or missing a required step, fails. The result is reported per step, with reasons.
Where your keys stay yours
The platform's own PKI signs attestations and timestamps. It does not need to be the thing that signs your artifacts. The attestation signer is pluggable, and the choice is yours per pipeline:
| Signer | Key custody |
|---|---|
| Fulcio (default) | Ephemeral key plus OIDC identity. Nothing persists. |
| AWS KMS | Asymmetric KMS key. Private key stays in the HSM. |
| Azure Key Vault / GCP KMS | Same model on the respective cloud HSM. |
| HashiCorp Vault Transit | Vault holds and uses the key. |
| SPIFFE | SVID from the Workload API, for mesh environments. |
| File | PEM on disk. Intended for disconnected environments. |
A PKCS #11 signer, for direct integration with a customer-operated HSM, is planned and not yet shipped.
Every signature can be countersigned
Two signatures, and they do different jobs. Your key produces the authoritative signature. The platform countersigns with Fulcio for identity and timestamps with the TSA.
The countersignature is not redundancy. A Fulcio leaf's subject alternative name carries the identity of the pipeline that produced the artifact, and because that key is ephemeral and issued against an ambient workload token rather than a stored secret, a build cannot mint provenance claiming to be a different builder. That is what evidences the builder identity requirement for SLSA Build Level 3. The RFC 3161 timestamp establishes when, and keeps the whole envelope verifiable after the ephemeral certificate expires.
Where this may not be sufficient on its own. An ephemeral, software-held key satisfies SLSA. It does not satisfy control families that require cryptographic operations be performed in a validated module under your control, which is where NIST 800-53 SC-12 and SC-13 land, along with any FIPS 140-3 obligation flowing from them. If your control set carries those requirements, the Fulcio countersignature alone will not meet them. That is precisely why the authoritative signature is yours and comes from your own key custody. The two together give you an artifact that is attributable to a pipeline, bound to a moment in time, and signed by a key held to your own assurance standard. Neither signature alone does all three.
Note that key custody is two decisions, not one: the key that signs attestations and the key that signs policies may reasonably differ, and the policy signing key is the more sensitive of the two, since it defines what counts as acceptable evidence. If the platform operator held your policy signing key, it could redefine the policy that gates your pipeline — so you hold it.
What provenance does not give you
Worth being direct about, because overstating it is how these conversations go wrong. Provenance records a build's inputs, command, environment, and output digest, signed at build time and bound to the artifact digest, so substitution after the build is detectable. It does not detect a builder that was already compromised when it ran. A compromised builder can emit accurate-looking attestations about a malicious build.
What raises that bar is the policy, not the signature: requiring attestations from several independent steps so no single compromised step satisfies the policy alone, constraining which identities may sign each step, requiring ephemeral build environments, and verifying that materials resolve to approved sources.
A certificate authority's identity is its key, not its name
Operational warning. Every deployment derives its common names from the same code, so two entirely separate platforms produce CAs with identical subject strings and completely different keys. Common name equality means nothing. Only the key identifies a trust anchor: the SPKI fingerprint, or the subject and authority key identifiers.
Any policy that embeds trust roots should derive them from the discovery document of the same platform the artifacts were signed against, rather than having them copied in by hand. Hand-copied roots are how a policy ends up trusting one environment while the artifacts were signed by another, and the failure surfaces as zero valid verifiers and an ECDSA verification error against a certificate whose name looks correct.
Key rotation
There are two rotations, and they are deliberately different in cost. Both fall out of the derivation design rather than being bolted on.
Rotating the CA chain without touching the root
Each PKI derivation label carries a version prefix. Incrementing a single constant changes the labels, which changes the derived keys, which produces a new Root CA, Fulcio CA, and TSA leaf. The root key material is untouched, and no KMS or HSM operation is involved.
Because the old certificates are still derivable from the same root at the previous version, the platform can serve both during a dual-trust window. Attestations signed under the old chain keep verifying while newly issued leaves chain to the new one. That is what makes routine rotation survivable rather than a flag day, and it is the reason cryptoperiods can be set conservatively in the first place: Root CA 20 years, Fulcio CA 5, TSA leaf 3, per NIST SP 800-57 Part 1.
One asymmetry worth knowing. The JWT signing label carries no version prefix, for backward compatibility. A version bump rotates the CA chain but leaves the JWT signing key unchanged. Rotating that key requires changing the root key material itself. It is a deliberate exception, not an oversight, and it means "rotate the PKI" and "rotate all credentials" are two different operations.
Rotating the root
A new KMS HMAC key is provisioned, the ARN is updated, and the deployment rolls. Everything derived changes simultaneously: the entire CA chain, the JWT signing key, session secrets, and the internal service token. Existing tokens stop verifying and existing sessions are invalidated, so replicas have to move together rather than gradually.
The upside of that bluntness is containment. If the root is suspected compromised, one operation invalidates every credential derived from it, and there is no inventory of scattered keys to chase. The blast radius is knowable because the derivation tree is the complete list of what exists.
| Scenario | Action | Blast radius |
|---|---|---|
| Scheduled CA rotation | Increment the version constant and deploy. | CA chain only. Dual-trust window keeps prior attestations verifiable. |
| Rotate all credentials | New KMS key, update the ARN, coordinated rollout. | Everything derived. Sessions and service tokens invalidated. |
| Suspected root compromise | Root rotation. One operation. | Total, and intentionally so. No key sprawl to hunt down. |
| Lost root, standalone | Restore from the offline encrypted backup. | Unrecoverable without that backup. This is by design. |