mod support;
use scrollcase_consumer::prepare::{
attach_extracted_box, verify_and_extract_box, verify_extracted_payload, AttachOptions,
EnvironmentReportOptions, PrepareOptions, PreparedStatus,
};
use scrollcase_consumer::trust::{parse_trusted_keys, TrustAnchors};
use serde_json::json;
#[cfg(unix)]
use support::Entry;
fn prepare_options<'a>(
fixture: &'a support::BoxFixture,
destination: &'a std::path::Path,
) -> PrepareOptions<'a> {
PrepareOptions {
trust: TrustAnchors::KeyFile(&fixture.key_path),
archive: Some(&fixture.archive_path),
destination,
environment: EnvironmentReportOptions::default(),
}
}
fn attach_options<'a>(
fixture: &'a support::BoxFixture,
root: &'a std::path::Path,
) -> AttachOptions<'a> {
AttachOptions {
trust: TrustAnchors::KeyFile(&fixture.key_path),
root,
environment: EnvironmentReportOptions::default(),
}
}
#[test]
fn preparing_a_box_yields_a_receipt_describing_what_was_verified() {
let fixture = support::valid("prepare-valid");
let destination = fixture.directory.join("installed");
let prepared =
verify_and_extract_box(&fixture.release_path, &prepare_options(&fixture, &destination))
.expect("a valid box must prepare");
assert_eq!(prepared.status(), PreparedStatus::Prepared);
assert_eq!(prepared.root(), destination);
assert_eq!(prepared.box_id(), "fixture-box");
assert_eq!(prepared.model_id(), "fixture-model");
assert_eq!(prepared.signing_key_ids(), [support::KEY_ID]);
assert!(prepared.required_assets().is_empty());
assert!(prepared.execution().is_some());
assert!(destination.join("box.json").is_file());
assert!(prepared.installed_size_bytes() > 0);
}
#[test]
fn a_destination_that_already_exists_is_never_written_into() {
let fixture = support::valid("prepare-existing");
let destination = fixture.directory.join("installed");
std::fs::create_dir_all(&destination).unwrap();
std::fs::write(destination.join("mine.txt"), b"do not touch\n").unwrap();
let error =
verify_and_extract_box(&fixture.release_path, &prepare_options(&fixture, &destination))
.unwrap_err();
assert!(error.message().contains("Destination already exists"), "{error}");
assert!(!destination.join("box.json").exists());
assert_eq!(
std::fs::read(destination.join("mine.txt")).unwrap(),
b"do not touch\n"
);
}
#[test]
fn an_extracted_size_that_disagrees_with_the_release_leaves_nothing_behind() {
let fixture = support::build(
"prepare-size",
|_| {},
|_| {},
|release| release["installedSizeBytes"] = json!(999_999),
);
let destination = fixture.directory.join("installed");
let error =
verify_and_extract_box(&fixture.release_path, &prepare_options(&fixture, &destination))
.unwrap_err();
assert!(
error
.message()
.contains("Extracted payload size does not match the signed release"),
"{error}"
);
assert!(!destination.exists());
let leftovers: Vec<_> = std::fs::read_dir(&fixture.directory)
.unwrap()
.filter_map(Result::ok)
.filter(|entry| entry.file_name().to_string_lossy().starts_with(".scrollcase-prepare-"))
.collect();
assert!(leftovers.is_empty(), "staging directory was left behind");
}
#[test]
fn a_release_that_declares_the_true_extracted_size_prepares() {
let fixture = support::valid("prepare-size-ok");
let staging = fixture.directory.join("probe");
verify_and_extract_box(&fixture.release_path, &prepare_options(&fixture, &staging)).unwrap();
let measured = scrollcase_consumer::filesystem::payload_size(&staging).unwrap();
let sized = support::build(
"prepare-size-declared",
|_| {},
|_| {},
move |release| release["installedSizeBytes"] = json!(measured),
);
let destination = sized.directory.join("installed");
let prepared =
verify_and_extract_box(&sized.release_path, &prepare_options(&sized, &destination)).unwrap();
assert_eq!(prepared.installed_size_bytes(), measured);
}
#[test]
fn attaching_re_identifies_an_extracted_box_without_its_archive() {
let fixture = support::valid("attach-valid");
let destination = fixture.directory.join("installed");
verify_and_extract_box(&fixture.release_path, &prepare_options(&fixture, &destination)).unwrap();
std::fs::remove_file(&fixture.archive_path).unwrap();
let attached =
attach_extracted_box(&fixture.release_path, &attach_options(&fixture, &destination))
.expect("an extracted box must attach");
assert_eq!(attached.status(), PreparedStatus::Attached);
assert_eq!(attached.box_id(), "fixture-box");
assert_eq!(attached.root(), destination);
}
fn embedded_anchors(fixture: &support::BoxFixture) -> Vec<scrollcase_consumer::trust::TrustedKey> {
parse_trusted_keys(&std::fs::read(&fixture.key_path).unwrap())
.expect("the fixture trust file is one of the two shapes")
}
#[test]
fn anchors_held_in_memory_verify_exactly_what_a_trust_file_verifies() {
let fixture = support::build_with_payload_digest("attach-embedded-anchors", |_| {});
let destination = fixture.directory.join("installed");
verify_and_extract_box(&fixture.release_path, &prepare_options(&fixture, &destination)).unwrap();
let embedded = embedded_anchors(&fixture);
let options = AttachOptions {
trust: TrustAnchors::Keys(&embedded),
root: &destination,
environment: EnvironmentReportOptions::default(),
};
let attached = attach_extracted_box(&fixture.release_path, &options)
.expect("compiled-in anchors must attach exactly as a trust file does");
assert_eq!(attached.status(), PreparedStatus::Attached);
assert_eq!(attached.signing_key_ids(), [support::KEY_ID]);
let verified = verify_extracted_payload(&fixture.release_path, &options)
.expect("compiled-in anchors must verify a payload");
assert_eq!(verified.box_id, "fixture-box");
assert!(verified.entry_count > 0);
}
#[test]
fn rewriting_the_trust_file_cannot_change_what_compiled_in_anchors_accept() {
let fixture = support::valid("attach-anchors-outlive-the-file");
let destination = fixture.directory.join("installed");
verify_and_extract_box(&fixture.release_path, &prepare_options(&fixture, &destination)).unwrap();
let embedded = embedded_anchors(&fixture);
support::write_foreign_key(&fixture.key_path);
let error =
attach_extracted_box(&fixture.release_path, &attach_options(&fixture, &destination))
.unwrap_err();
assert!(error.message().contains("no valid signature"), "{error}");
let attached = attach_extracted_box(
&fixture.release_path,
&AttachOptions {
trust: TrustAnchors::Keys(&embedded),
root: &destination,
environment: EnvironmentReportOptions::default(),
},
)
.expect("an edited trust file must not reach a caller that carries its anchors");
assert_eq!(attached.signing_key_ids(), [support::KEY_ID]);
}
#[test]
fn attaching_refuses_anything_that_is_not_an_extracted_box() {
let fixture = support::valid("attach-not-a-box");
let missing = fixture.directory.join("nowhere");
let error =
attach_extracted_box(&fixture.release_path, &attach_options(&fixture, &missing)).unwrap_err();
assert!(error.message().contains("is not an extracted box directory"), "{error}");
let file = fixture.directory.join("a-file");
std::fs::write(&file, b"not a box\n").unwrap();
let error =
attach_extracted_box(&fixture.release_path, &attach_options(&fixture, &file)).unwrap_err();
assert!(error.message().contains("is not an extracted box directory"), "{error}");
let empty = fixture.directory.join("empty");
std::fs::create_dir_all(&empty).unwrap();
let error =
attach_extracted_box(&fixture.release_path, &attach_options(&fixture, &empty)).unwrap_err();
assert!(error.message().contains("Attached box is missing venv/"), "{error}");
}
#[cfg(unix)]
#[test]
fn attaching_refuses_a_link_standing_in_for_the_box_root() {
let fixture = support::valid("attach-link-root");
let destination = fixture.directory.join("installed");
verify_and_extract_box(&fixture.release_path, &prepare_options(&fixture, &destination)).unwrap();
let link = fixture.directory.join("linked");
std::os::unix::fs::symlink(&destination, &link).unwrap();
let error =
attach_extracted_box(&fixture.release_path, &attach_options(&fixture, &link)).unwrap_err();
assert!(error.message().contains("is not an extracted box directory"), "{error}");
}
#[test]
fn an_on_demand_asset_must_match_its_signed_descriptor_before_a_receipt_exists() {
const ASSET: &[u8] = b"trusted on-demand bytes";
let policy = || {
json!([{
"url": "https://example.invalid/weights.bin",
"relativePath": "weights/model.bin",
"sizeBytes": ASSET.len(),
"sha256": support::sha256_hex(ASSET),
}])
};
let fixture = support::build(
"attach-assets",
|manifest| {
manifest["weights"] = json!("on-demand");
manifest["assets"] = policy();
},
|_| {},
|release| {
release["weights"] = json!("on-demand");
release["assets"] = policy();
},
);
let destination = fixture.directory.join("installed");
let prepared =
verify_and_extract_box(&fixture.release_path, &prepare_options(&fixture, &destination))
.expect("preparing does not require the assets to be present yet");
assert_eq!(prepared.required_assets().len(), 1);
let error =
attach_extracted_box(&fixture.release_path, &attach_options(&fixture, &destination))
.unwrap_err();
assert!(error.message().contains("asset is missing"), "{error}");
std::fs::create_dir_all(destination.join("weights")).unwrap();
std::fs::write(destination.join("weights/model.bin"), b"short").unwrap();
let error =
attach_extracted_box(&fixture.release_path, &attach_options(&fixture, &destination))
.unwrap_err();
assert!(error.message().contains("asset size mismatch"), "{error}");
std::fs::write(destination.join("weights/model.bin"), vec![b'x'; ASSET.len()]).unwrap();
let error =
attach_extracted_box(&fixture.release_path, &attach_options(&fixture, &destination))
.unwrap_err();
assert!(error.message().contains("asset SHA-256 mismatch"), "{error}");
std::fs::write(destination.join("weights/model.bin"), ASSET).unwrap();
let attached =
attach_extracted_box(&fixture.release_path, &attach_options(&fixture, &destination))
.expect("the signed asset must be accepted");
assert_eq!(attached.required_assets().len(), 1);
}
#[test]
fn payload_verification_proves_the_tree_is_the_one_the_release_describes() {
let fixture = support::build_with_payload_digest("payload-valid", |_| {});
let destination = fixture.directory.join("installed");
verify_and_extract_box(&fixture.release_path, &prepare_options(&fixture, &destination)).unwrap();
let verified =
verify_extracted_payload(&fixture.release_path, &attach_options(&fixture, &destination))
.expect("an untouched tree must verify");
assert_eq!(verified.box_id, "fixture-box");
assert_eq!(verified.entry_count, 3);
std::fs::write(destination.join("appeared-later.txt"), b"written by the app\n").unwrap();
assert!(
verify_extracted_payload(&fixture.release_path, &attach_options(&fixture, &destination))
.is_ok()
);
}
#[test]
fn payload_verification_catches_every_way_the_tree_can_stop_matching() {
let fixture = support::build_with_payload_digest("payload-tamper", |_| {});
let destination = fixture.directory.join("installed");
verify_and_extract_box(&fixture.release_path, &prepare_options(&fixture, &destination)).unwrap();
std::fs::write(destination.join("app/main.py"), b"print('replaced')\n").unwrap();
let error =
verify_extracted_payload(&fixture.release_path, &attach_options(&fixture, &destination))
.unwrap_err();
assert!(
error.message().starts_with("Payload does not match the signed release:"),
"{error}"
);
std::fs::remove_file(destination.join("app/main.py")).unwrap();
let error =
verify_extracted_payload(&fixture.release_path, &attach_options(&fixture, &destination))
.unwrap_err();
assert!(error.message().contains("is missing"), "{error}");
let fresh = support::build_with_payload_digest("payload-list", |_| {});
let root = fresh.directory.join("installed");
verify_and_extract_box(&fresh.release_path, &prepare_options(&fresh, &root)).unwrap();
std::fs::write(root.join("payload-digest.v1"), b"sha256-path-list-v1\n").unwrap();
let error =
verify_extracted_payload(&fresh.release_path, &attach_options(&fresh, &root)).unwrap_err();
assert!(
error
.message()
.contains("Payload digest list does not match the signed release"),
"{error}"
);
std::fs::remove_file(root.join("payload-digest.v1")).unwrap();
let error =
verify_extracted_payload(&fresh.release_path, &attach_options(&fresh, &root)).unwrap_err();
assert!(error.message().contains("missing its payload digest list"), "{error}");
}
#[test]
fn a_release_without_a_commitment_cannot_have_its_payload_verified() {
let fixture = support::valid("payload-absent");
let destination = fixture.directory.join("installed");
verify_and_extract_box(&fixture.release_path, &prepare_options(&fixture, &destination)).unwrap();
let error =
verify_extracted_payload(&fixture.release_path, &attach_options(&fixture, &destination))
.unwrap_err();
assert!(
error.message().contains("does not commit to a payload digest"),
"{error}"
);
}
#[cfg(unix)]
#[test]
fn a_link_is_verified_by_its_target_string_not_by_what_it_points_at() {
let fixture = support::build_with_payload_digest("payload-link", |entries| {
entries.push(Entry::File("venv/bin/python3.11", b"#!/bin/sh\n".to_vec(), 0o755));
entries.push(Entry::Link("venv/bin/python3", "python3.11"));
});
let destination = fixture.directory.join("installed");
verify_and_extract_box(&fixture.release_path, &prepare_options(&fixture, &destination)).unwrap();
assert!(
verify_extracted_payload(&fixture.release_path, &attach_options(&fixture, &destination))
.is_ok()
);
let link = destination.join("venv/bin/python3");
std::fs::remove_file(&link).unwrap();
std::os::unix::fs::symlink("python", &link).unwrap();
let error =
verify_extracted_payload(&fixture.release_path, &attach_options(&fixture, &destination))
.unwrap_err();
assert!(
error.message().starts_with("Payload does not match the signed release:"),
"{error}"
);
std::fs::remove_file(&link).unwrap();
std::fs::write(&link, b"#!/bin/sh\n").unwrap();
let error =
verify_extracted_payload(&fixture.release_path, &attach_options(&fixture, &destination))
.unwrap_err();
assert!(error.message().contains("is not a link"), "{error}");
}
#[test]
fn a_receipt_carries_the_environment_the_release_declares() {
let fixture = support::build(
"prepare-environment",
|manifest| {
manifest["environment"] = json!({ "SCROLLCASE_DECLARED": "from-the-release" });
},
|_| {},
|release| {
release["environment"] = json!({ "SCROLLCASE_DECLARED": "from-the-release" });
},
);
let destination = fixture.directory.join("installed");
let prepared =
verify_and_extract_box(&fixture.release_path, &prepare_options(&fixture, &destination))
.unwrap();
let report = prepared.environment_report();
assert_eq!(report.release_variable_count, 1);
let declared = report
.variables
.iter()
.find(|variable| variable.name == "SCROLLCASE_DECLARED")
.expect("a declared variable is always actionable enough to list");
assert_eq!(declared.value, "from-the-release");
}