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

nordisk/holger

provenance/tests/pgp_build_feature.rs

//! **OpenPGP verification is opt-in, and it fails closed when it is not built** (T9b).
//!
//! rpgp (T9's `.asc` verifier) cost the default `cargo install holger` about 96 crates,
//! so it sits behind holger-provenance's `pgp` feature (`maven-pgp` in server-lib and the
//! CLI; the CLI's `online` and `full` carry it). A build without it must never ignore a pin it cannot check:
//!
//! * a trust bundle that pins `pgp-keys/<name>` is refused by name at load;
//! * a floor key of `alg: openpgp` is refused by name at load;
//! * a source's `require(pgp_keys: [...])` is refused by name at load;
//! * an `.asc` is "unverified", never a pass.
//!
//! The file takes no feature, so it is never dark: each test checks the side this build is
//! on. `cargo xtask suite-check` runs it without `pgp` (`trust:pgp-not-built`); the
//! workspace run and `trust:maven-pgp` run it with `pgp`.

use holger_provenance::pgp::{PgpKey, BUILT, NOT_BUILT_TAG};
use holger_provenance::{
    check_floor, check_tightens, effective, verify_artifact, KeyAlg, ProvenanceFloor,
    ProvenanceGate, TrustRoot, TrustedKey, Verdict,
};

const FX: &str = concat!(env!("CARGO_MANIFEST_DIR"), "/tests/fixtures/maven");
const POVIRK: &str = "povirk-BDB5FA4FE719D787FB3D3197F6D4A1D411E9D1AE.asc";
const GUAVA: &str = "guava-33.3.1-jre.pom";

fn read(f: &str) -> Vec<u8> {
    std::fs::read(format!("{FX}/{f}")).unwrap_or_else(|e| panic!("{f}: {e}"))
}

/// The refusal names the missing build feature, in the words an operator searches for.
fn refused_by_name(e: &holger_provenance::ProvenanceError) {
    let s = e.tagged();
    assert!(
        s.contains(&format!("[{NOT_BUILT_TAG}]")),
        "the refusal names {NOT_BUILT_TAG}: {s}"
    );
    assert!(
        s.contains("`maven-pgp`"),
        "the refusal names the feature to build: {s}"
    );
}

fn gate_pgp(keys: &[&str]) -> ProvenanceGate {
    ProvenanceGate::Require {
        identity: None,
        identities: vec![],
        builders: vec![],
        keys: vec![],
        apk_keys: vec![],
        pgp_keys: keys.iter().map(|s| s.to_string()).collect(),
        slsa_min: None,
    }
}

fn openpgp_floor() -> ProvenanceFloor {
    ProvenanceFloor {
        keys: vec![TrustedKey {
            id: "guava-release".into(),
            alg: KeyAlg::Openpgp,
            public_key: String::from_utf8(read(POVIRK)).unwrap(),
        }],
        ..Default::default()
    }
}

#[test]
fn the_feature_says_whether_it_is_built() {
    assert_eq!(BUILT, cfg!(feature = "pgp"));
    assert_eq!(NOT_BUILT_TAG, "provenance-pgp-not-built");
}

#[test]
fn a_trust_bundle_that_pins_a_pgp_key_is_refused_by_name_without_the_feature() {
    let pinned = TrustRoot::default().with_pgp_keys(&[(POVIRK.to_string(), read(POVIRK))]);
    let direct = PgpKey::from_armored(POVIRK, &read(POVIRK));
    if BUILT {
        assert_eq!(pinned.unwrap().pgp_keys.len(), 1);
        assert!(direct.is_ok());
    } else {
        let e = pinned.unwrap_err();
        assert_eq!(
            e.code_str(),
            "holger-provenance-10",
            "a trust-root refusal: {e}"
        );
        refused_by_name(&e);
        refused_by_name(&direct.unwrap_err());
    }
}

#[test]
fn a_floor_key_of_alg_openpgp_is_refused_by_name_without_the_feature() {
    let floor = openpgp_floor();
    let gate = ProvenanceGate::Require {
        identity: None,
        identities: vec![],
        builders: vec![],
        keys: vec!["guava-release".into()],
        apk_keys: vec![],
        pgp_keys: vec![],
        slsa_min: None,
    };
    if BUILT {
        check_floor(&floor).unwrap();
        check_tightens(&floor, &gate, "central").unwrap();
    } else {
        refused_by_name(&check_floor(&floor).unwrap_err());
        refused_by_name(&check_tightens(&floor, &gate, "central").unwrap_err());
    }
}

#[test]
fn a_source_that_requires_pgp_keys_is_refused_by_name_without_the_feature() {
    let r = check_tightens(&ProvenanceFloor::default(), &gate_pgp(&[POVIRK]), "central");
    if BUILT {
        r.unwrap();
    } else {
        let e = r.unwrap_err();
        assert_eq!(
            e.code_str(),
            "holger-provenance-11",
            "a policy refusal: {e}"
        );
        assert!(
            e.to_string().contains("\"central\""),
            "it names the source: {e}"
        );
        refused_by_name(&e);
    }
    // A gate with no pgp pin is untouched either way.
    check_tightens(
        &ProvenanceFloor::default(),
        &ProvenanceGate::Prefer,
        "central",
    )
    .unwrap();
}

#[test]
fn an_asc_without_the_feature_is_unverified_never_a_pass() {
    let require = effective(
        &ProvenanceFloor {
            require_signed: true,
            require_provenance: true,
            ..Default::default()
        },
        &ProvenanceGate::Off,
    );
    let materials = [(format!("{GUAVA}.asc"), read(&format!("{GUAVA}.asc")))];
    let r = verify_artifact(&read(GUAVA), &materials, &require, None);
    assert_eq!(
        (r.verdict, r.code.as_deref()),
        (Verdict::Block, Some("holger-provenance-4")),
        "{r:#?}"
    );
    let c = &r.candidates[0];
    assert!(!c.verified);
    let why = c.error.as_deref().unwrap_or_default();
    assert!(why.contains("unverified"), "{why}");
    if !BUILT {
        assert!(why.contains(&format!("[{NOT_BUILT_TAG}]")), "{why}");
    }
    // Under prefer it is a warning, never a pass.
    let prefer = effective(&ProvenanceFloor::default(), &ProvenanceGate::Prefer);
    let r = verify_artifact(&read(GUAVA), &materials, &prefer, None);
    assert_eq!(r.verdict, Verdict::Warn, "{r:#?}");
}