-
Notifications
You must be signed in to change notification settings - Fork 45
fix: Additional cert validation and sanity checks #187
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 2 commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,6 +1,7 @@ | ||
| import * as tar from 'tar'; | ||
| import path from 'path'; | ||
| import fs from 'fs/promises'; | ||
| import { X509Certificate } from 'crypto'; | ||
| import { Verifier, toTrustMaterial, toSignedEntity } from '@sigstore/verify'; | ||
| import { bundleFromJSON } from '@sigstore/bundle'; | ||
| import trustedRoot from './config/trusted-root.json'; | ||
|
|
@@ -135,6 +136,16 @@ export async function buildAndValidateSignature(options: TaskOriginVerifyOptions | |
| const runtimeIntermediate = bundleCerts[1] ? { rawBytes: bundleCerts[1].rawBytes } : null; | ||
| const rootCert = bundleCerts[3] ? { rawBytes: bundleCerts[3].rawBytes } : null; | ||
|
|
||
| // The pinning below assumes the bundle carries the full chain | ||
| // [leaf, runtime intermediate, static intermediate, root]. If the runtime | ||
| // intermediate or root is missing, that assumption is broken and we cannot build a | ||
| // chain that anchors to a configured CA, so reject rather than silently continuing. | ||
| if (!runtimeIntermediate || !rootCert) { | ||
| throw new Error( | ||
| 'Invalid signature bundle: certificate chain must contain a runtime intermediate and root' | ||
| ); | ||
| } | ||
|
|
||
| // Prepare trust material with: | ||
| // 1. Date strings converted to Date objects (required for filtering) | ||
| // 2. Runtime intermediate CA injected into certificate chain | ||
|
awilliams1-cb marked this conversation as resolved.
Outdated
|
||
|
|
@@ -150,10 +161,27 @@ export async function buildAndValidateSignature(options: TaskOriginVerifyOptions | |
| rawBytes: Buffer.from(cert.rawBytes, 'base64'), | ||
| })); | ||
|
|
||
| // Build the certificate chain: base certs + runtime intermediate + root | ||
| // Build the certificate chain: base certs + runtime intermediate + root. | ||
| // The runtime intermediate and root come from an external source, so they are | ||
| // only injected after being pinned to the statically-configured intermediate. | ||
| const staticInter = new X509Certificate(baseCerts[0].rawBytes); | ||
| const certChain = [...baseCerts]; | ||
| if (runtimeIntermediate) certChain.push(runtimeIntermediate); | ||
| if (rootCert) certChain.push(rootCert); | ||
|
|
||
| // Pin: the root must actually have signed our pinned static intermediate, so | ||
| // only the genuine root is trusted. A cert that is not part of the chain (e.g. | ||
| // a self-minted root) cannot have produced that signature and is rejected. | ||
| // A failed pin falls through to a base-only chain so other configured CAs can | ||
| // still be tried; verification rejects only if no CA yields a trusted path. | ||
| const root = new X509Certificate(new Uint8Array(rootCert.rawBytes)); | ||
| if (root.ca && staticInter.verify(root.publicKey)) { | ||
| // Pin: the runtime intermediate must be issued by our pinned static | ||
| // intermediate; this also rejects a self-signed intermediate injected here. | ||
| const runtime = new X509Certificate(new Uint8Array(runtimeIntermediate.rawBytes)); | ||
| if (runtime.ca && runtime.verify(staticInter.publicKey)) { | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Could we fail closed if this runtime pin does not verify? Otherwise the root may still be trusted and verification can fall back to shorter static/root-issued paths.
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Our |
||
| certChain.push(runtimeIntermediate); | ||
| } | ||
| certChain.push(rootCert); | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Could we confirm the intended trust model here? Since Sigstore treats each cert in
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The certs being added are being validated against the trusted intermediate, so if the root cert doesn't link to the trusted intermediate, or static intermediate isn't linked to the trusted intermediate, then they aren't added as a trust anchor. So there is no circumvention of the trusted intermediate with the addition of the hierarchy verification. Now if the verified root or any verified intermediate (including the ones that are already in this repo) were to directly issue the leaf, then yes that would count. Our verification only ensures that a given signature originates from the cert chain that we build using the trusted roots stored in this repo. |
||
| } | ||
|
|
||
| // eslint-disable-next-line @typescript-eslint/no-explicit-any | ||
| const normalized: any = { | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.