use std::path::{Path, PathBuf};
use scrollcase_consumer::trust::TrustAnchors;
use scrollcase_consumer::verify::inspect_release_document;
fn fixture(name: &str) -> PathBuf {
Path::new(env!("CARGO_MANIFEST_DIR"))
.join("tests/fixtures")
.join(name)
}
fn mutated(directory: &Path, mutate: impl Fn(&mut serde_json::Value)) -> PathBuf {
let mut document: serde_json::Value =
serde_json::from_slice(&std::fs::read(fixture("signed-release.json")).unwrap()).unwrap();
mutate(&mut document);
let path = directory.join(format!("release-{}.json", uuid_like()));
std::fs::write(&path, serde_json::to_vec_pretty(&document).unwrap()).unwrap();
path
}
fn uuid_like() -> u128 {
std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap()
.as_nanos()
}
fn scratch() -> PathBuf {
let directory = std::env::temp_dir().join(format!("scrollcase-release-{}", uuid_like()));
std::fs::create_dir_all(&directory).unwrap();
directory
}
#[test]
fn a_genuine_signed_release_is_accepted_and_fully_interpreted() {
let inspected =
inspect_release_document(&fixture("signed-release.json"), TrustAnchors::KeyFile(&fixture("trusted-key.json")))
.expect("the fixture release must verify");
assert_eq!(inspected.release.schema_version, 2);
assert!(inspected.release.kind.ends_with(".release"));
assert_eq!(
inspected.release.python_entry_point,
inspected.adapter.python.entry_point
);
assert_eq!(inspected.signed.signatures.len(), 1);
}
#[test]
fn nothing_survives_an_edit_to_the_signed_bytes() {
let directory = scratch();
let altered_payload = mutated(&directory, |document| {
document["payloadBase64"] = serde_json::json!("eyJzY2hlbWFWZXJzaW9uIjoyfQ==");
});
let error = inspect_release_document(&altered_payload, TrustAnchors::KeyFile(&fixture("trusted-key.json"))).unwrap_err();
assert!(
error.message().contains("Signed payload SHA-256 mismatch"),
"{error}"
);
let restated = mutated(&directory, |document| {
use base64::engine::general_purpose::STANDARD as BASE64;
use base64::Engine as _;
let payload = br#"{"schemaVersion":2,"kind":"scrollcase.box.release"}"#;
document["payloadBase64"] = serde_json::json!(BASE64.encode(payload));
document["payloadSha256"] = serde_json::json!(sha256_hex(payload));
});
let error = inspect_release_document(&restated, TrustAnchors::KeyFile(&fixture("trusted-key.json"))).unwrap_err();
assert!(error.message().contains("no valid signature"), "{error}");
let foreign_key = mutated(&directory, |document| {
document["signatures"][0]["keyId"] = serde_json::json!("someone-elses-key");
});
let error = inspect_release_document(&foreign_key, TrustAnchors::KeyFile(&fixture("trusted-key.json"))).unwrap_err();
assert!(error.message().contains("no valid signature"), "{error}");
let legacy = mutated(&directory, |document| {
document["schemaVersion"] = serde_json::json!(1);
});
let error = inspect_release_document(&legacy, TrustAnchors::KeyFile(&fixture("trusted-key.json"))).unwrap_err();
assert!(
error.message().contains("Unsupported schemaVersion 1"),
"{error}"
);
std::fs::remove_dir_all(directory).unwrap();
}
fn sha256_hex(bytes: &[u8]) -> String {
use sha2::{Digest, Sha256};
use std::fmt::Write as _;
Sha256::digest(bytes)
.iter()
.fold(String::with_capacity(64), |mut hex, byte| {
let _ = write!(hex, "{byte:02x}");
hex
})
}