provenance/tests/minted.rs
//! The verifier's refusals, one by one, on attestations minted in the test.
mod common;
use common::*;
use holger_provenance::{
check_tightens, effective, verify_artifact, BuilderRule, EffectivePolicy, Identity,
ProvenanceFloor, ProvenanceGate, TrustRoot, Verdict,
};
const WHEEL: &[u8] = b"PK\x03\x04 holger-demo-1.0.0-py3-none-any.whl bytes";
const BUILDER: &str = "https://git.gunnar.rs/nordisk/holger/.forgejo/workflows/release.yml";
fn require(slsa_min: u8) -> ProvenanceGate {
ProvenanceGate::Require {
identity: None,
identities: vec![],
builders: vec![],
keys: vec![],
apk_keys: vec![],
pgp_keys: vec![],
slsa_min: Some(slsa_min),
}
}
fn keyed(key: &Key, builders: Vec<BuilderRule>) -> ProvenanceFloor {
ProvenanceFloor {
keys: vec![key.trusted()],
builders,
..Default::default()
}
}
fn slsa_envelope(key: &Key, bytes: &[u8], builder: &str) -> Vec<u8> {
key.envelope(&statement(
"holger-demo-1.0.0-py3-none-any.whl",
bytes,
"https://slsa.dev/provenance/v1",
slsa(builder),
))
}
fn run(
policy: &EffectivePolicy,
artifact: &[u8],
material: Vec<(&str, Vec<u8>)>,
trust: Option<&TrustRoot>,
) -> holger_provenance::ProvenanceReport {
let m: Vec<(String, Vec<u8>)> = material
.into_iter()
.map(|(n, b)| (n.to_string(), b))
.collect();
verify_artifact(artifact, &m, policy, trust)
}
fn code(r: &holger_provenance::ProvenanceReport) -> &str {
r.code.as_deref().unwrap_or("")
}
#[test]
fn a_valid_key_signed_slsa_envelope_passes_under_require() {
for key in [Key::ed25519("demo-release"), Key::p256("demo-release")] {
let policy = effective(&keyed(&key, vec![BUILDER.into()]), &require(2));
let r = run(
&policy,
WHEEL,
vec![("demo.intoto.jsonl", slsa_envelope(&key, WHEEL, BUILDER))],
None,
);
assert_eq!(r.verdict, Verdict::Pass, "{r:#?}");
let c = r.verified_provenance().next().unwrap();
let s = c.slsa.as_ref().unwrap();
assert_eq!(
(s.builder_id.as_str(), s.build_type.as_str()),
(BUILDER, "https://holger.rs/demo-build/v1")
);
assert_eq!(s.invocation_id, "demo-run-1");
assert_eq!(s.resolved_dependencies.len(), 1);
assert_eq!(s.external_parameters["ref"], "refs/heads/main");
assert_eq!(c.slsa_level, Some(2));
}
}
#[test]
fn a_wrong_digest_is_refused() {
let key = Key::ed25519("demo-release");
let policy = effective(&keyed(&key, vec![]), &require(0));
let r = run(
&policy,
WHEEL,
vec![("demo.json", slsa_envelope(&key, b"other bytes", BUILDER))],
None,
);
assert_eq!(
(r.verdict, code(&r)),
(Verdict::Block, "holger-provenance-2"),
"{}",
r.detail
);
}
#[test]
fn a_wrong_signer_is_refused() {
let allowed = Key::ed25519("demo-release");
let intruder = Key::ed25519("demo-release");
let policy = effective(&keyed(&allowed, vec![]), &require(0));
let r = run(
&policy,
WHEEL,
vec![("demo.json", slsa_envelope(&intruder, WHEEL, BUILDER))],
None,
);
assert_eq!(
(r.verdict, code(&r)),
(Verdict::Block, "holger-provenance-3"),
"{}",
r.detail
);
// A keyid the policy does not name is an untrusted signer.
let other = Key::ed25519("somebody-else");
let r = run(
&policy,
WHEEL,
vec![("demo.json", slsa_envelope(&other, WHEEL, BUILDER))],
None,
);
assert_eq!(
(r.verdict, code(&r)),
(Verdict::Block, "holger-provenance-4"),
"{}",
r.detail
);
}
#[test]
fn a_builder_not_allowed_is_refused() {
let key = Key::p256("demo-release");
let policy = effective(&keyed(&key, vec![BUILDER.into()]), &require(0));
let r = run(
&policy,
WHEEL,
vec![(
"demo.json",
slsa_envelope(&key, WHEEL, "https://evil.example/builder"),
)],
None,
);
assert_eq!(
(r.verdict, code(&r)),
(Verdict::Block, "holger-provenance-6"),
"{}",
r.detail
);
}
#[test]
fn a_too_low_slsa_level_is_refused() {
let key = Key::ed25519("demo-release");
let floor = keyed(
&key,
vec![BuilderRule {
id: BUILDER.into(),
level: Some(1),
}],
);
let r = run(
&effective(&floor, &require(2)),
WHEEL,
vec![("demo.json", slsa_envelope(&key, WHEEL, BUILDER))],
None,
);
assert_eq!(
(r.verdict, code(&r)),
(Verdict::Block, "holger-provenance-7"),
"{}",
r.detail
);
// The same builder at level 3 passes a minimum of 3.
let floor = keyed(
&key,
vec![BuilderRule {
id: BUILDER.into(),
level: Some(3),
}],
);
let r = run(
&effective(&floor, &require(3)),
WHEEL,
vec![("demo.json", slsa_envelope(&key, WHEEL, BUILDER))],
None,
);
assert_eq!(r.verdict, Verdict::Pass, "{}", r.detail);
}
#[test]
fn a_tampered_payload_fails_the_byte_exact_pae() {
let key = Key::ed25519("demo-release");
let policy = effective(&keyed(&key, vec![]), &require(0));
let env: serde_json::Value =
serde_json::from_slice(&slsa_envelope(&key, WHEEL, BUILDER)).unwrap();
// Change one byte of the payload (the builder id) and keep the signature.
let payload = holger_provenance::crypto::b64("p", env["payload"].as_str().unwrap()).unwrap();
let tampered = String::from_utf8(payload)
.unwrap()
.replace("release.yml", "release.ymL");
let mut env2 = env.clone();
env2["payload"] = b64(tampered.as_bytes()).into();
let r = run(
&policy,
WHEEL,
vec![("demo.json", serde_json::to_vec(&env2).unwrap())],
None,
);
assert_eq!(
(r.verdict, code(&r)),
(Verdict::Block, "holger-provenance-3"),
"{}",
r.detail
);
// A payload-type confusion: same bytes, another type. PAE binds the type.
let mut env3 = env.clone();
env3["payloadType"] = "application/json".into();
let r = run(
&policy,
WHEEL,
vec![("demo.json", serde_json::to_vec(&env3).unwrap())],
None,
);
assert_eq!(r.verdict, Verdict::Block, "{}", r.detail);
// The PAE is DSSE v1's, byte for byte (the spec's own vector).
assert_eq!(
znippy_common::sign::dsse_pae("http://example.com/HelloWorld", b"hello world"),
b"DSSEv1 29 http://example.com/HelloWorld 11 hello world".to_vec()
);
}
#[test]
fn no_attestation_is_refused_under_require_and_warned_under_prefer() {
let key = Key::ed25519("demo-release");
let r = run(
&effective(&keyed(&key, vec![]), &require(0)),
WHEEL,
vec![],
None,
);
assert_eq!(
(r.verdict, code(&r)),
(Verdict::Block, "holger-provenance-1")
);
let r = run(
&effective(&keyed(&key, vec![]), &ProvenanceGate::Prefer),
WHEEL,
vec![],
None,
);
assert_eq!(
(r.verdict, code(&r)),
(Verdict::Warn, "holger-provenance-1")
);
let r = run(
&effective(&keyed(&key, vec![]), &ProvenanceGate::Off),
WHEEL,
vec![],
None,
);
assert_eq!(r.verdict, Verdict::Pass);
}
#[test]
fn require_signed_in_the_floor_refuses_unsigned_bytes_from_any_source() {
let key = Key::ed25519("demo-release");
let floor = ProvenanceFloor {
require_signed: true,
..keyed(&key, vec![])
};
for gate in [ProvenanceGate::Off, ProvenanceGate::Prefer] {
let r = run(&effective(&floor, &gate), WHEEL, vec![], None);
assert_eq!(
(r.verdict, code(&r)),
(Verdict::Block, "holger-provenance-13"),
"{gate:?}"
);
}
// Signed, but no provenance, under `off`: the floor's signature is there, so it passes.
let signed_only = key.envelope(&statement(
"x",
WHEEL,
"https://spdx.dev/Document",
serde_json::json!({}),
));
let r = run(
&effective(&floor, &ProvenanceGate::Off),
WHEEL,
vec![("sbom.json", signed_only)],
None,
);
assert_eq!(r.verdict, Verdict::Pass, "{}", r.detail);
}
#[test]
fn a_source_may_only_tighten_the_floor() {
let floor = ProvenanceFloor {
require_provenance: true,
slsa_min: 2,
identities: vec![Identity::regex(
"https://issuer.enforce.dev",
"https://issuer\\.enforce\\.dev/.+",
)],
..Default::default()
};
let e = check_tightens(&floor, &ProvenanceGate::Prefer, "pypi").unwrap_err();
assert_eq!(e.code_str(), "holger-provenance-11", "{e}");
let e = check_tightens(&floor, &require(1), "pypi").unwrap_err();
assert!(e.to_string().contains("below the floor"), "{e}");
let gate = ProvenanceGate::Require {
identity: Some(Identity::exact(
"https://token.actions.githubusercontent.com",
"https://github.com/x/y",
)),
identities: vec![],
builders: vec![],
keys: vec![],
apk_keys: vec![],
pgp_keys: vec![],
slsa_min: Some(3),
};
let e = check_tightens(&floor, &gate, "pypi").unwrap_err();
assert!(e.to_string().contains("not one the floor"), "{e}");
let ok = ProvenanceGate::Require {
identity: Some(Identity::exact(
"https://issuer.enforce.dev",
"https://issuer.enforce.dev/3fab4fe4edb624d",
)),
identities: vec![],
builders: vec![],
keys: vec![],
apk_keys: vec![],
pgp_keys: vec![],
slsa_min: Some(3),
};
check_tightens(&floor, &ok, "chainguard").unwrap();
let eff = effective(&floor, &ProvenanceGate::Off);
assert_eq!(
eff.provenance,
holger_provenance::Mode::Require,
"the floor stands whatever the gate says"
);
assert_eq!(effective(&floor, &ok).slsa_min, 3);
}
/// The RON shape agreed with lane G0+G2: `rules:` carries the floor's fields;
/// a source's `gates.provenance` is `require(…) | prefer | off`.
#[test]
fn the_ron_shape_parses() {
let floor: ProvenanceFloor = ron::from_str(
r#"(
require_signed: true,
require_provenance: false,
slsa_min: 1,
identities: [
(issuer: "https://token.actions.githubusercontent.com",
subject: "https://github.com/chainguard-images/images/.github/workflows/release.yaml@refs/heads/main"),
(issuer: "https://issuer.enforce.dev", subject_regex: "https://issuer\\.enforce\\.dev/.+"),
],
builders: ["https://github.com/chainguard-dev/terraform-provider-apko", (id: "https://github.com/actions/runner/github-hosted", level: 3)],
keys: [(id: "demo-release", alg: ed25519, public_key: "MCowBQYDK2VwAyEAGb9ECWmEzf6FQbrBZ9w7lshQhqowtrbLDFw4rXAxZuE=")],
trust_root: Some("/var/lib/holger/sigstore-trust.holger.znippy"),
)"#,
)
.unwrap();
assert_eq!(floor.builders[1].level, Some(3));
assert_eq!(floor.keys[0].alg, holger_provenance::KeyAlg::Ed25519);
let chainguard: ProvenanceGate = ron::from_str(
r#"require(identity: (issuer: "https://token.actions.githubusercontent.com",
subject: "https://github.com/chainguard-images/images/.github/workflows/release.yaml@refs/heads/main"),
slsa_min: 2)"#,
)
.unwrap();
assert!(matches!(
&chainguard,
ProvenanceGate::Require {
identity: Some(_),
slsa_min: Some(2),
..
}
));
check_tightens(&floor, &chainguard, "chainguard").unwrap();
let pypi: ProvenanceGate = ron::from_str("prefer").unwrap();
assert_eq!(pypi, ProvenanceGate::Prefer);
let off: ProvenanceGate = ron::from_str("off").unwrap();
assert_eq!(off, ProvenanceGate::Off);
let keyed: ProvenanceGate = ron::from_str(r#"require(keys: ["demo-release"], builders: ["https://github.com/actions/runner/github-hosted"])"#).unwrap();
check_tightens(&floor, &keyed, "demo").unwrap();
assert_eq!(effective(&floor, &keyed).keys.len(), 1);
}
// ── keyless, against the mini Sigstore ───────────────────────────────────────
const ENFORCE: &str = "https://issuer.enforce.dev";
const CG_LIB_ID: &str = "https://issuer.enforce.dev/3fab4fe4edb624d0/9a1b2c3d4e5f6071";
fn keyless_policy(identity: Identity, slsa_min: u8) -> EffectivePolicy {
effective(
&ProvenanceFloor::default(),
&ProvenanceGate::Require {
identity: Some(identity),
identities: vec![],
builders: vec![],
keys: vec![],
apk_keys: vec![],
pgp_keys: vec![],
slsa_min: Some(slsa_min),
},
)
}
#[test]
fn a_chainguard_libraries_npm_bundle_minted_like_theirs_verifies_offline() {
let s = MiniSigstore::new();
let trust = TrustRoot::from_trusted_root_json(&s.trusted_root_json()).unwrap();
let tgz = b"\x1f\x8b demo npm tarball";
let st = statement(
"pkg:npm/lodash@4.17.21",
tgz,
"https://slsa.dev/provenance/v1",
slsa("https://chainguard.dev/factory"),
);
let bundle = s.bundle(&st, ENFORCE, CG_LIB_ID, SIGNED_AT);
let r = run(
&keyless_policy(
Identity::regex(ENFORCE, "https://issuer\\.enforce\\.dev/.+"),
2,
),
tgz,
vec![("lodash-provenance-4.17.21.sigstore", bundle.clone())],
Some(&trust),
);
assert_eq!(r.verdict, Verdict::Pass, "{r:#?}");
let c = &r.candidates[0];
assert!(c.tlog[0].set_verified && c.tlog[0].inclusion_verified);
assert!(
c.checked.iter().any(|x| x.contains("SCT")),
"{:?}",
c.checked
);
// The same bundle under GitHub's issuer is an identity refusal.
let gh = Identity::exact("https://token.actions.githubusercontent.com", CG_LIB_ID);
let r = run(
&keyless_policy(gh, 2),
tgz,
vec![("b.sigstore", bundle)],
Some(&trust),
);
assert_eq!(code(&r), "holger-provenance-5", "{}", r.detail);
}
#[test]
fn a_chainguard_python_pep740_minted_like_theirs_verifies_offline() {
let s = MiniSigstore::new();
let trust = TrustRoot::from_trusted_root_json(&s.trusted_root_json()).unwrap();
let st = statement(
"flask-3.0.1-py3-none-any.whl",
WHEEL,
"https://docs.pypi.org/attestations/publish/v1",
serde_json::Value::Null,
);
let r = run(
&keyless_policy(
Identity::regex(ENFORCE, "https://issuer\\.enforce\\.dev/.+"),
2,
),
WHEEL,
vec![("w.provenance.json", s.pep740(&st, ENFORCE, CG_LIB_ID))],
Some(&trust),
);
assert_eq!(r.verdict, Verdict::Pass, "{r:#?}");
assert_eq!(r.candidates[0].builder_id.as_deref(), Some(CG_LIB_ID));
}
#[test]
fn keyless_refusals_name_the_broken_link() {
let s = MiniSigstore::new();
let trust = TrustRoot::from_trusted_root_json(&s.trusted_root_json()).unwrap();
let id = Identity::exact(ENFORCE, CG_LIB_ID);
let st = statement(
"x",
WHEEL,
"https://slsa.dev/provenance/v1",
slsa("https://chainguard.dev/factory"),
);
// Logged a day after the certificate expired.
let late = s.bundle(&st, ENFORCE, CG_LIB_ID, SIGNED_AT + 2 * 86_400);
let r = run(
&keyless_policy(id.clone(), 0),
WHEEL,
vec![("late", late)],
Some(&trust),
);
assert_eq!(code(&r), "holger-provenance-12", "{}", r.detail);
// A changed SET.
let mut b: serde_json::Value =
serde_json::from_slice(&s.bundle(&st, ENFORCE, CG_LIB_ID, SIGNED_AT)).unwrap();
let e = &mut b["verificationMaterial"]["tlogEntries"][0];
e["integratedTime"] = (SIGNED_AT + 1).to_string().into();
let r = run(
&keyless_policy(id.clone(), 0),
WHEEL,
vec![("set", serde_json::to_vec(&b).unwrap())],
Some(&trust),
);
assert_eq!(code(&r), "holger-provenance-8", "{}", r.detail);
// Another Sigstore's root does not vouch for this one's certificates.
let other = MiniSigstore::new();
let foreign = TrustRoot::from_trusted_root_json(&other.trusted_root_json()).unwrap();
let good = s.bundle(&st, ENFORCE, CG_LIB_ID, SIGNED_AT);
let r = run(
&keyless_policy(id, 0),
WHEEL,
vec![("good", good)],
Some(&foreign),
);
assert_eq!(
code(&r),
"holger-provenance-8",
"the foreign root does not know this log: {}",
r.detail
);
}
#[test]
fn the_trust_root_crosses_the_airgap_as_a_signed_bundle() {
use holger_provenance::trust::{build_trust_bundle, BundleSigner, TrustBundleOptions};
use holger_update_bundle::policy::{
ApprovedSigner, CertificateValidity, ExpiredPosture, TrustMode, TrustPolicy,
};
use holger_update_bundle::schema::POLICY_SCHEMA;
let mint = |cn: &str| {
let key = rcgen::KeyPair::generate_for(&rcgen::PKCS_ED25519).unwrap();
let mut p = rcgen::CertificateParams::new(vec![]).unwrap();
p.distinguished_name.push(rcgen::DnType::CommonName, cn);
let der = p.self_signed(&key).unwrap().der().to_vec();
(
holger_provenance::crypto::sha256_hex(&der),
BundleSigner {
cert_der: der,
key_pkcs8: key.serialize_der(),
},
)
};
let (fr, reviewer) = mint("trust-reviewer");
let (fo, owner) = mint("trust-owner");
let (fl, release) = mint("trust-release");
let policy = TrustPolicy {
schema_version: POLICY_SCHEMA.to_string(),
trust_mode: Some(TrustMode::PinnedSigners),
trusted_roots: vec![],
certificate_validity: CertificateValidity::Enforce,
approved_signers: vec![
ApprovedSigner {
fingerprint: fr,
subject: None,
roles: vec!["security-reviewer".into()],
},
ApprovedSigner {
fingerprint: fo,
subject: None,
roles: vec!["system-owner".into()],
},
],
required_approval_roles: vec!["security-reviewer".into(), "system-owner".into()],
minimum_distinct_approvers: 2,
trusted_release_signers: vec![fl],
release_signer_must_be_distinct: true,
maximum_bundle_age_secs: None,
require_expiry: true,
on_expired_metadata: Some(ExpiredPosture::Refuse),
require_sbom: false,
require_vex: false,
require_scan: false,
allowed_products: vec![holger_provenance::TRUST_PRODUCT.into()],
allowed_models: vec![],
};
let dir = tempfile::tempdir().unwrap();
let now = chrono::Utc::now();
let real = std::fs::read(concat!(
env!("CARGO_MANIFEST_DIR"),
"/tests/fixtures/sigstore/trusted_root.json"
))
.unwrap();
// A Maven Central signer's OpenPGP key rides in it too (T9), where the OpenPGP
// verifier is built (`pgp`, T9b). Where it is not, the same key is refused by name,
// both when a bundle is made and when one is read (`with_pgp_keys`, which
// `from_bundle` runs over every `pgp-keys/<name>` payload).
let pgp_keys = vec![(
"povirk-BDB5FA4FE719D787FB3D3197F6D4A1D411E9D1AE.asc".to_string(),
std::fs::read(concat!(
env!("CARGO_MANIFEST_DIR"),
"/tests/fixtures/maven/povirk-BDB5FA4FE719D787FB3D3197F6D4A1D411E9D1AE.asc"
))
.unwrap(),
)];
let pgp_keys = if holger_provenance::pgp::BUILT {
pgp_keys
} else {
let e = TrustRoot::default().with_pgp_keys(&pgp_keys).unwrap_err();
assert!(e.tagged().contains("[provenance-pgp-not-built]"), "{e}");
Vec::new()
};
let out = build_trust_bundle(
&real,
TrustBundleOptions {
sequence: 1,
created_at: now,
expires_at: Some(now + chrono::Duration::days(90)),
supplier: "holger-sigstore-trust".into(),
producer: "test".into(),
source_uri: Some("https://tuf-repo-cdn.sigstore.dev".into()),
policy: policy.clone(),
approvals: vec![
("security-reviewer".into(), reviewer),
("system-owner".into(), owner),
],
release,
out: dir.path().join("sigstore-trust.holger.znippy"),
// Wolfi's APK signing key rides in the same bundle (lane G3).
apk_keys: vec![(
"wolfi-signing.rsa.pub".into(),
std::fs::read(concat!(
env!("CARGO_MANIFEST_DIR"),
"/tests/fixtures/wolfi/wolfi-signing.rsa.pub"
))
.unwrap(),
)],
pgp_keys,
},
)
.unwrap();
let root = TrustRoot::from_bundle(&out, &policy, &holger_update_bundle::time::HostRtc).unwrap();
assert_eq!(root.digest, holger_provenance::crypto::sha256_hex(&real));
assert!(!root.cas.is_empty() && !root.tlogs.is_empty() && !root.ctlogs.is_empty());
let apk: Vec<&str> = root.apk_keys.iter().map(|k| k.name.as_str()).collect();
assert_eq!(
apk,
["wolfi-signing.rsa.pub"],
"the bundle carries the pinned apk key"
);
let pgp: Vec<(&str, &str)> = root
.pgp_keys
.iter()
.map(|k| (k.name.as_str(), k.fingerprint.as_str()))
.collect();
if holger_provenance::pgp::BUILT {
assert_eq!(
pgp,
[(
"povirk-BDB5FA4FE719D787FB3D3197F6D4A1D411E9D1AE.asc",
"bdb5fa4fe719d787fb3d3197f6d4a1d411e9d1ae"
)],
"the bundle carries the pinned pgp key"
);
} else {
assert!(
pgp.is_empty(),
"no pgp key is pinned without the verifier: {pgp:?}"
);
}
// A policy that pins other signers refuses the bundle by name.
let mut stranger = policy.clone();
stranger.trusted_release_signers = vec!["00".repeat(32)];
let e =
TrustRoot::from_bundle(&out, &stranger, &holger_update_bundle::time::HostRtc).unwrap_err();
assert_eq!(e.code_str(), "holger-provenance-10", "{e}");
}