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

nordisk/dwarves

crates/dwarves-slsa/tests/verify.rs

//! Verify a signed release bundle end to end.

mod common;

use base64::engine::general_purpose::STANDARD as B64;
use base64::Engine as _;
use dwarves_slsa::dsse::{self, Envelope, Signature};
use dwarves_slsa::statement::{self, Subject, PAYLOAD_TYPE_INTOTO};
use dwarves_slsa::subjects::sha256_hex;
use dwarves_slsa::verify::{verify_bundle, Expect};

fn bundle_of(k: &common::TestKey, st: &serde_json::Value, payload_type: &str) -> String {
    let payload = statement::payload(st);
    let sig = common::sign(k, &dsse::pae(payload_type, &payload));
    Envelope {
        payload_type: payload_type.into(),
        payload: B64.encode(&payload),
        signatures: vec![Signature {
            keyid: dsse::keyid(&k.spki),
            sig: B64.encode(sig),
        }],
    }
    .to_line()
}

fn release(files: &[(&str, &[u8])]) -> serde_json::Value {
    let subjects: Vec<Subject> = files
        .iter()
        .map(|(n, b)| Subject::sha256(*n, sha256_hex(b)))
        .collect();
    statement::statement(&subjects, &common::provenance()).unwrap()
}

const A: &[u8] = b"crate a bytes";
const B: &[u8] = b"crate b bytes";

#[test]
fn a_signed_release_verifies_and_its_artifacts_match() {
    let k = common::key();
    let st = release(&[("crates/a-1.0.0.crate", A), ("crates/b-1.0.0.crate", B)]);
    let v = verify_bundle(
        &bundle_of(&k, &st, PAYLOAD_TYPE_INTOTO),
        &k.spki,
        &Expect::default(),
    )
    .unwrap();
    assert_eq!(v.summary.release, "holger-2.0.1");
    assert_eq!(v.keyid, dsse::keyid(&k.spki));
    assert_eq!(
        v.check_artifact("a-1.0.0.crate", A).unwrap().name,
        "crates/a-1.0.0.crate"
    );
    assert!(v.subject_for(B).is_some());
}

#[test]
fn an_artifact_under_another_name_or_changed_is_refused() {
    let k = common::key();
    let st = release(&[("crates/a-1.0.0.crate", A), ("crates/b-1.0.0.crate", B)]);
    let v = verify_bundle(
        &bundle_of(&k, &st, PAYLOAD_TYPE_INTOTO),
        &k.spki,
        &Expect::default(),
    )
    .unwrap();
    assert!(
        v.check_artifact("b-1.0.0.crate", A).is_err(),
        "a's bytes as b"
    );
    assert!(v.check_artifact("a-1.0.0.crate", b"changed").is_err());
    assert!(v.subject_for(b"changed").is_none());
}

#[test]
fn the_out_directory_must_be_exactly_the_subjects() {
    let k = common::key();
    let st = release(&[("crates/a-1.0.0.crate", A), ("bin/x", B)]);
    let v = verify_bundle(
        &bundle_of(&k, &st, PAYLOAD_TYPE_INTOTO),
        &k.spki,
        &Expect::default(),
    )
    .unwrap();
    let d = tempfile::tempdir().unwrap();
    std::fs::create_dir_all(d.path().join("crates")).unwrap();
    std::fs::create_dir_all(d.path().join("bin")).unwrap();
    std::fs::write(d.path().join("crates/a-1.0.0.crate"), A).unwrap();
    assert!(v.check_dir(d.path()).is_err(), "bin/x missing");
    std::fs::write(d.path().join("bin/x"), B).unwrap();
    assert_eq!(v.check_dir(d.path()).unwrap(), 2);
    std::fs::write(d.path().join("bin/extra"), B).unwrap();
    assert!(v.check_dir(d.path()).is_err(), "an extra file");
    std::fs::remove_file(d.path().join("bin/extra")).unwrap();
    std::fs::write(d.path().join("bin/x"), b"changed").unwrap();
    assert!(v.check_dir(d.path()).is_err(), "a changed file");
}

#[test]
fn another_key_is_refused() {
    let k = common::key();
    let st = release(&[("a", A)]);
    assert!(verify_bundle(
        &bundle_of(&k, &st, PAYLOAD_TYPE_INTOTO),
        &common::key().spki,
        &Expect::default()
    )
    .is_err());
}

#[test]
fn another_builder_or_build_type_is_refused() {
    let k = common::key();
    let st = release(&[("a", A)]);
    let b = bundle_of(&k, &st, PAYLOAD_TYPE_INTOTO);
    let e = verify_bundle(
        &b,
        &k.spki,
        &Expect {
            builder_id: "https://example.com/other".into(),
            build_type: None,
            runner_account: None,
        },
    )
    .unwrap_err();
    assert!(e.0.contains("builder"), "{e}");
    let e = verify_bundle(
        &b,
        &k.spki,
        &Expect {
            build_type: Some("https://example.com/bt".into()),
            ..Expect::default()
        },
    )
    .unwrap_err();
    assert!(e.0.contains("build type"), "{e}");
}

#[test]
fn another_payload_type_or_two_envelopes_are_refused() {
    let k = common::key();
    let st = release(&[("a", A)]);
    assert!(verify_bundle(
        &bundle_of(&k, &st, "application/json"),
        &k.spki,
        &Expect::default()
    )
    .is_err());
    let one = bundle_of(&k, &st, PAYLOAD_TYPE_INTOTO);
    let e = verify_bundle(&format!("{one}{one}"), &k.spki, &Expect::default()).unwrap_err();
    assert!(e.0.contains("one envelope"), "{e}");
}

#[test]
fn a_slsa_v02_statement_is_refused_even_signed() {
    let k = common::key();
    let mut st = release(&[("a", A)]);
    st["predicateType"] = "https://slsa.dev/provenance/v0.2".into();
    assert!(verify_bundle(
        &bundle_of(&k, &st, PAYLOAD_TYPE_INTOTO),
        &k.spki,
        &Expect::default()
    )
    .is_err());
}