Your browser sucks, JavaScript disabled! Gunnar's console needs JavaScript and WebAssembly. Git over SSH on port 2222 does not.

nordisk/holger

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}");
}