guests/verify-bundle/tests/common/guests.rs
// SPDX-License-Identifier: AGPL-3.0-only
//! The guest as Heimdall runs it: built for wasm32-unknown-unknown, turned
//! into a component against heimdall-wasm's WIT, signed like a Holger plugin
//! (`.wasm.p7s` + `.wasm.blake3`) and loaded by the real host (no WASI).
//! Heimdall is a defensive sandbox.
//!
//! The builds themselves are tools/guest_build.rs, the one recipe the
//! release tools (examples/dist.rs, examples/sign.rs) use too.
#![allow(dead_code)]
use std::path::{Path, PathBuf};
use std::sync::OnceLock;
use guest_host::verify::sign::{dev, sign_module};
use heimdall_wasm::{Decoder, Guest, Host, HostConfig};
/// The release tools (and through them the one build recipe), loaded once.
#[path = "../../tools/release.rs"]
pub mod release;
#[allow(unused_imports)]
pub use release::guest_build::{componentize, Build, RELEASE};
fn build(b: Build) -> Vec<u8> {
release::guest_build::build_core(b, Path::new(env!("CARGO_TARGET_TMPDIR")))
}
/// The core module of one build, built once per test binary.
pub fn core_module(b: Build) -> &'static [u8] {
static M: [OnceLock<Vec<u8>>; 4] = [
OnceLock::new(),
OnceLock::new(),
OnceLock::new(),
OnceLock::new(),
];
M[b as usize].get_or_init(|| build(b))
}
/// The component of one build.
pub fn component(b: Build) -> &'static [u8] {
static C: [OnceLock<Vec<u8>>; 4] = [
OnceLock::new(),
OnceLock::new(),
OnceLock::new(),
OnceLock::new(),
];
C[b as usize].get_or_init(|| componentize(core_module(b)))
}
/// The lying decoder in `mode` (8 ASCII bytes, see src/wasm.rs `evil`).
pub fn evil_component(mode: &[u8; 8]) -> Vec<u8> {
const MARKER: &[u8] = b"HEIMDALL-EVIL-MODE=";
let mut core = core_module(Build::EvilDecoder).to_vec();
let at = core
.windows(MARKER.len())
.position(|w| w == MARKER)
.expect("the evil decoder carries its mode marker")
+ MARKER.len();
core[at..at + 8].copy_from_slice(mode);
componentize(&core)
}
/// The names a core module imports, as `module::name`.
pub fn imports(core: &[u8]) -> Vec<String> {
let mut out = Vec::new();
for payload in wasmparser::Parser::new(0).parse_all(core) {
if let wasmparser::Payload::ImportSection(r) = payload.unwrap() {
for imp in r.into_imports() {
let imp = imp.unwrap();
out.push(format!("{}::{}", imp.module, imp.name));
}
}
}
out
}
/// A signing CA, a release signer and a host that trusts the CA; components
/// are written, signed and pinned into the same directory.
pub struct Lab {
pub dir: tempfile::TempDir,
pub host: Host,
signer: guest_host::verify::sign::Ed25519Signer,
}
impl Lab {
pub fn new() -> Lab {
let dir = tempfile::tempdir().unwrap();
let (ca_key, ca_der) = dev::mint_ca("heimdall-guest-test-ca").unwrap();
let root = dir.path().join("root.der");
std::fs::write(&root, &ca_der).unwrap();
let signer = dev::new_ed25519_signer(&ca_key, &ca_der, "heimdall-guest-release").unwrap();
let host = Host::new(HostConfig::new([root])).unwrap();
Lab { dir, host, signer }
}
/// Write `bytes` as `<name>.wasm`, signed and pinned; returns its path.
pub fn signed(&self, name: &str, bytes: &[u8]) -> PathBuf {
let module = self.dir.path().join(format!("{name}.wasm"));
std::fs::write(&module, bytes).unwrap();
std::fs::write(
self.dir.path().join(format!("{name}.wasm.p7s")),
sign_module(bytes, &self.signer).unwrap(),
)
.unwrap();
std::fs::write(
self.dir.path().join(format!("{name}.wasm.blake3")),
format!("{}\n", blake3::hash(bytes).to_hex()),
)
.unwrap();
module
}
pub fn decoder_from(&self, name: &str, bytes: &[u8]) -> Decoder {
let p = self.signed(name, bytes);
self.host
.load_decoder(&p)
.unwrap_or_else(|e| panic!("the host refused the decoder {name}: {e}"))
}
pub fn decoder(&self) -> Decoder {
self.decoder_from("decoder", component(Build::Decoder))
}
/// The split guest: the verifier with `decoder` behind it.
pub fn split_with(&self, decoder: Decoder) -> Guest {
let p = self.signed("verifier", component(Build::Verifier));
self.host
.load_split(&p, decoder)
.unwrap_or_else(|e| panic!("the host refused the split guest: {e}"))
}
pub fn split(&self) -> Guest {
self.split_with(self.decoder())
}
/// The single-instance guest.
pub fn job(&self) -> Guest {
let p = self.signed("verify-bundle", component(Build::Job));
self.host
.load(&p)
.unwrap_or_else(|e| panic!("the host refused the guest: {e}"))
}
}
/// The two guests every wasm test runs, loaded once per test binary.
pub struct Loaded {
pub lab: Lab,
pub job: Guest,
pub split: Guest,
}
pub fn loaded() -> &'static Loaded {
static L: OnceLock<Loaded> = OnceLock::new();
L.get_or_init(|| {
let lab = Lab::new();
let job = lab.job();
let split = lab.split();
Loaded { lab, job, split }
})
}