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

nordisk/holger

provenance/tests/maven_pgp.rs

//! Real Maven Central OpenPGP signatures, verified OFFLINE against pinned keys.
//!
//! Fetched once on the online side (2026-10-24) from `https://repo1.maven.org/maven2`:
//!
//! * `maven/guava-33.3.1-jre.pom` and its `.asc`
//!   (`com/google/guava/guava/33.3.1-jre/`), an RSA 2048 signature over SHA-512 by
//!   `BDB5FA4FE719D787FB3D3197F6D4A1D411E9D1AE`; the key file is that key from
//!   `keys.openpgp.org`;
//! * `maven/slf4j-api-2.0.16.pom` and its `.asc` (`org/slf4j/slf4j-api/2.0.16/`), an
//!   ECDSA P-521 signature by `60200AC4AE761F1614D6C46766D68DAA073BE985`; the key
//!   file is from `keys.openpgp.org`.
//!
//! Both keys were exported with `gpg --export-options export-minimal`, then their
//! User ID packets and the certifications over them were stripped, so no e-mail
//! address is committed: the primary key, the subkey and its binding signature
//! remain. GnuPG 2.4 said "Good signature" for both pairs before they were committed.
//! Both POMs name no e-mail address either.
//!
//! The verifier is opt-in (T9b): this file needs holger-provenance's `pgp` feature. It
//! runs in the workspace run, in `cargo xtask suite-check` (`trust:maven-pgp`) and as the
//! `maven_pgp` arm of `.nornir/testmatrix-arms.json`. What a build WITHOUT the feature
//! does with the same pins is `tests/pgp_build_feature.rs`.
#![cfg(feature = "pgp")]

use holger_provenance::pgp::{verify_detached, PgpKey};
use holger_provenance::{
    check_tightens, effective, verify_artifact, EffectivePolicy, KeyAlg, ProvenanceFloor,
    ProvenanceGate, SignerInfo, TrustRoot, TrustedKey, Verdict,
};

const FX: &str = concat!(env!("CARGO_MANIFEST_DIR"), "/tests/fixtures/maven");
const POVIRK: &str = "povirk-BDB5FA4FE719D787FB3D3197F6D4A1D411E9D1AE.asc";
const CEKI: &str = "ceki-60200AC4AE761F1614D6C46766D68DAA073BE985.asc";
const POVIRK_FPR: &str = "bdb5fa4fe719d787fb3d3197f6d4a1d411e9d1ae";
const CEKI_FPR: &str = "60200ac4ae761f1614d6c46766d68daa073be985";
const GUAVA: &str = "guava-33.3.1-jre.pom";
const SLF4J: &str = "slf4j-api-2.0.16.pom";

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

fn key(f: &str) -> PgpKey {
    PgpKey::from_armored(f, &read(f)).unwrap()
}

/// A trust root that pins only these PGP keys (by their file names).
fn trust(keys: &[&str]) -> TrustRoot {
    let k: Vec<(String, Vec<u8>)> = keys.iter().map(|f| (f.to_string(), read(f))).collect();
    TrustRoot::default().with_pgp_keys(&k).unwrap()
}

fn require() -> EffectivePolicy {
    effective(
        &ProvenanceFloor {
            require_signed: true,
            require_provenance: true,
            ..Default::default()
        },
        &ProvenanceGate::Off,
    )
}

fn run(
    artifact: &str,
    asc: &str,
    policy: &EffectivePolicy,
    trust: &TrustRoot,
) -> holger_provenance::ProvenanceReport {
    verify_artifact(
        &read(artifact),
        &[(asc.to_string(), read(asc))],
        policy,
        Some(trust),
    )
}

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,
    }
}

#[test]
fn the_pinned_keys_load_with_their_fingerprints() {
    let g = key(POVIRK);
    assert_eq!(g.name, POVIRK);
    assert_eq!(g.fingerprint, POVIRK_FPR);
    assert_eq!(key(CEKI).fingerprint, CEKI_FPR);
    assert!(PgpKey::from_armored(
        "junk",
        b"-----BEGIN PGP PUBLIC KEY BLOCK-----\n\nnope\n-----END PGP PUBLIC KEY BLOCK-----\n"
    )
    .is_err());
    // A signature is not a key.
    assert!(PgpKey::from_armored("sig", &read(&format!("{GUAVA}.asc"))).is_err());
}

#[test]
fn guava_rsa_sha512_verifies_with_the_pinned_key() {
    let v = verify_detached(
        &read(GUAVA),
        &read(&format!("{GUAVA}.asc")),
        &[key(CEKI), key(POVIRK)],
    )
    .unwrap();
    assert_eq!(v.key, POVIRK);
    assert_eq!(v.fingerprint, POVIRK_FPR);
    assert!(v.alg.starts_with("rsa"), "{}", v.alg);
    let r = run(
        GUAVA,
        &format!("{GUAVA}.asc"),
        &require(),
        &trust(&[POVIRK]),
    );
    assert_eq!(r.verdict, Verdict::Pass, "{r:#?}");
    let c = r.verified_provenance().next().unwrap();
    assert_eq!(c.origin, format!("pgp-signature:{GUAVA}.asc"));
    assert_eq!(
        c.builder_id.as_deref(),
        Some(format!("pgp-key:{POVIRK}").as_str())
    );
    assert!(matches!(&c.signer, Some(SignerInfo::Key { id, .. }) if id == POVIRK));
}

#[test]
fn slf4j_ecdsa_p521_verifies_with_the_pinned_key() {
    let v = verify_detached(&read(SLF4J), &read(&format!("{SLF4J}.asc")), &[key(CEKI)]).unwrap();
    assert_eq!(v.fingerprint, CEKI_FPR);
    assert!(v.alg.starts_with("ecdsa"), "{}", v.alg);
    let r = run(
        SLF4J,
        &format!("{SLF4J}.asc"),
        &require(),
        &trust(&[CEKI, POVIRK]),
    );
    assert_eq!(r.verdict, Verdict::Pass, "{r:#?}");
}

#[test]
fn an_unknown_key_is_unverified_never_a_pass() {
    // The signer's key is not pinned: refused under require, warned under prefer.
    let e =
        verify_detached(&read(GUAVA), &read(&format!("{GUAVA}.asc")), &[key(CEKI)]).unwrap_err();
    assert_eq!(e.code_str(), "holger-provenance-4", "{e}");
    assert!(
        e.to_string().contains("unverified") && e.to_string().contains(&POVIRK_FPR.to_uppercase()),
        "{e}"
    );
    let r = run(GUAVA, &format!("{GUAVA}.asc"), &require(), &trust(&[CEKI]));
    assert_eq!(r.verdict, Verdict::Block, "{r:#?}");
    assert_eq!(r.code.as_deref(), Some("holger-provenance-4"));
    let prefer = effective(&ProvenanceFloor::default(), &ProvenanceGate::Prefer);
    let r = run(GUAVA, &format!("{GUAVA}.asc"), &prefer, &trust(&[CEKI]));
    assert_eq!(r.verdict, Verdict::Warn, "{r:#?}");
    assert!(r.candidates.iter().all(|c| !c.verified));
    // No PGP key pinned at all, and no trust root at all.
    let r = run(
        GUAVA,
        &format!("{GUAVA}.asc"),
        &require(),
        &TrustRoot::default(),
    );
    assert_eq!(
        (r.verdict, r.code.as_deref()),
        (Verdict::Block, Some("holger-provenance-4")),
        "{r:#?}"
    );
    let r = verify_artifact(
        &read(GUAVA),
        &[(format!("{GUAVA}.asc"), read(&format!("{GUAVA}.asc")))],
        &require(),
        None,
    );
    assert_eq!(
        (r.verdict, r.code.as_deref()),
        (Verdict::Block, Some("holger-provenance-4")),
        "{r:#?}"
    );
}

#[test]
fn a_changed_byte_or_another_file_fails_the_signature() {
    let mut pom = read(GUAVA);
    let at = pom.len() / 2;
    pom[at] ^= 1;
    let e = verify_detached(&pom, &read(&format!("{GUAVA}.asc")), &[key(POVIRK)]).unwrap_err();
    assert_eq!(e.code_str(), "holger-provenance-3", "{e}");
    // slf4j's signature against guava's bytes, both keys pinned.
    let r = run(
        GUAVA,
        &format!("{SLF4J}.asc"),
        &require(),
        &trust(&[CEKI, POVIRK]),
    );
    assert_eq!(
        (r.verdict, r.code.as_deref()),
        (Verdict::Block, Some("holger-provenance-3")),
        "{r:#?}"
    );
}

#[test]
fn require_pgp_keys_narrows_the_pinned_keys_per_source() {
    let floor = ProvenanceFloor::default();
    let gate = gate_pgp(&[CEKI]);
    check_tightens(&floor, &gate, "central").unwrap();
    let policy = effective(&floor, &gate);
    assert_eq!(policy.pgp_keys, vec![CEKI.to_string()]);
    // Both keys pinned in the bundle, but this source allows only Ceki's.
    let r = run(
        GUAVA,
        &format!("{GUAVA}.asc"),
        &policy,
        &trust(&[CEKI, POVIRK]),
    );
    assert_eq!(
        (r.verdict, r.code.as_deref()),
        (Verdict::Block, Some("holger-provenance-4")),
        "{r:#?}"
    );
    let r = run(
        SLF4J,
        &format!("{SLF4J}.asc"),
        &policy,
        &trust(&[CEKI, POVIRK]),
    );
    assert_eq!(r.verdict, Verdict::Pass, "{r:#?}");
    // A source that demands an SLSA level cannot be satisfied by a bare signature.
    let mut slsa = gate_pgp(&[POVIRK]);
    if let ProvenanceGate::Require { slsa_min, .. } = &mut slsa {
        *slsa_min = Some(1);
    }
    let r = run(
        GUAVA,
        &format!("{GUAVA}.asc"),
        &effective(&floor, &slsa),
        &trust(&[POVIRK]),
    );
    assert_eq!(
        (r.verdict, r.code.as_deref()),
        (Verdict::Block, Some("holger-provenance-7")),
        "{r:#?}"
    );
}

#[test]
fn the_ron_writes_require_pgp_keys() {
    let gate: ProvenanceGate =
        ron::from_str(&format!(r#"require(pgp_keys: ["{POVIRK}"])"#)).unwrap();
    assert_eq!(gate, gate_pgp(&[POVIRK]));
    assert!(
        ron::from_str::<ProvenanceGate>(r#"require(pgp_key: ["x"])"#).is_err(),
        "a typo is refused by name"
    );
}

#[test]
fn a_floor_key_of_alg_openpgp_is_a_pinned_pgp_key() {
    let armored = String::from_utf8(read(POVIRK)).unwrap();
    let floor = ProvenanceFloor {
        keys: vec![TrustedKey {
            id: "guava-release".into(),
            alg: KeyAlg::Openpgp,
            public_key: armored,
        }],
        ..Default::default()
    };
    let gate = ProvenanceGate::Require {
        identity: None,
        identities: vec![],
        builders: vec![],
        keys: vec!["guava-release".into()],
        apk_keys: vec![],
        pgp_keys: vec![],
        slsa_min: None,
    };
    check_tightens(&floor, &gate, "central").unwrap();
    let r = run(
        GUAVA,
        &format!("{GUAVA}.asc"),
        &effective(&floor, &gate),
        &TrustRoot::default(),
    );
    assert_eq!(r.verdict, Verdict::Pass, "{r:#?}");
    assert!(
        matches!(&r.candidates[0].signer, Some(SignerInfo::Key { id, .. }) if id == "guava-release")
    );
    // A floor key that is not an OpenPGP key refuses the policy by name.
    let bad = ProvenanceFloor {
        keys: vec![TrustedKey {
            id: "x".into(),
            alg: KeyAlg::Openpgp,
            public_key: "not a key".into(),
        }],
        ..Default::default()
    };
    let e = check_tightens(&bad, &ProvenanceGate::Off, "central").unwrap_err();
    assert_eq!(e.code_str(), "holger-provenance-11", "{e}");
}