plugin/host/src/verify.rs
//! **Is this module the one we signed?**
//!
//! The gate — a detached CMS signature at `foo.wasm.p7s` over the BLAKE3-256 of
//! the module bytes, chained to DER trusted roots, verified BEFORE compilation
//! and fail-closed in a build without the verifier — is `guest_host::verify`
//! (edda) since 2026-09-22, shared with monetize's product host. It still calls
//! the constellation's ONE spec-exact CMS verifier (`znippy_common::sign`); no
//! crypto was written in the move.
//!
//! What stays here is holger's: the frozen `holger-plugin-6..9` code on each of
//! the four refusals ([`verify_named`]), and the rule `registry.rs` states — a
//! module that fails verification is refused **by name** at boot, never dropped
//! from the catalog quietly.
use std::path::Path;
pub use guest_host::verify::{ModuleTrust, TrustError, SIGNATURE_EXT};
/// [`ModuleTrust::verify`] with holger's frozen code on the refusal.
///
/// Returns the verified signer's common name, or `None` when no verification
/// was required. There is no third answer.
pub fn verify_named(
trust: &ModuleTrust,
module: &Path,
bytes: &[u8],
) -> Result<Option<String>, String> {
trust
.verify(module, bytes)
.map_err(|e| crate::named::trust_refusal(&e))
}
#[cfg(test)]
mod tests {
use super::*;
use holger_errcode as ec;
use std::path::PathBuf;
#[test]
fn a_signature_sits_beside_its_module_under_the_full_file_name() {
let p = ModuleTrust::signature_path(Path::new("/var/lib/holger/plugins/foo.wasm"));
assert_eq!(p, PathBuf::from("/var/lib/holger/plugins/foo.wasm.p7s"));
}
#[test]
fn the_default_policy_requires_nothing_and_reports_no_signer() {
let t = ModuleTrust::default();
assert!(!t.is_required());
assert_eq!(
verify_named(&t, Path::new("x.wasm"), b"\0asm").unwrap(),
None
);
}
/// Requiring trust with no roots is a coded configuration error.
#[test]
fn requiring_trust_with_no_roots_is_refused_by_name() {
let t = ModuleTrust::required([]);
let err = verify_named(&t, Path::new("x.wasm"), b"\0asm")
.expect_err("an empty root set verified");
assert!(
err.contains(ec::PLUGIN_TRUST_ROOTS_UNREADABLE.code)
|| err.contains(ec::PLUGIN_VERIFICATION_UNAVAILABLE.code),
"{err}"
);
}
/// A build without the verifier must REFUSE configured roots, never ignore them.
///
/// Reads guest-host's OWN build, not only this crate's feature: in a workspace
/// build another crate (monetize-front, monetize-server) turns
/// `guest-host/verify-modules` on, the verifier is then present, and the same
/// call reads the root instead (holger-plugin-8 for a missing one).
///
/// HOLGER-ROOT-1: needs guest-host 0.2.1 (`VERIFIER_COMPILED`, unpublished), so it runs
/// only with `guest-host-verifier-probe` until that publish.
#[cfg(all(not(feature = "verify-modules"), feature = "guest-host-verifier-probe"))]
#[test]
fn a_build_without_a_verifier_refuses_rather_than_skips() {
let t = ModuleTrust::required([PathBuf::from("/etc/holger/root.der")]);
let err =
verify_named(&t, Path::new("x.wasm"), b"\0asm").expect_err("unverified load allowed");
if guest_host::verify::VERIFIER_COMPILED {
// The verifier is here after all, so the named root is READ, and a missing
// one is still a refusal by name, never a pass.
assert!(
err.contains(ec::PLUGIN_TRUST_ROOTS_UNREADABLE.code),
"{err}"
);
return;
}
assert!(
err.contains(ec::PLUGIN_VERIFICATION_UNAVAILABLE.code),
"{err}"
);
assert!(
err.contains("verify-modules"),
"the refusal does not say how to fix it: {err}"
);
}
fn scratch(name: &str) -> PathBuf {
let dir = std::env::temp_dir().join(format!(
"holger-plugin-verify-{name}-{}-{:?}",
std::process::id(),
std::thread::current().id()
));
let _ = std::fs::remove_dir_all(&dir);
std::fs::create_dir_all(&dir).unwrap();
dir
}
/// An unsigned module in a trust-required directory is `holger-plugin-6`.
#[cfg(feature = "verify-modules")]
#[test]
fn an_unsigned_module_is_refused_by_name_not_skipped() {
let dir = scratch("unsigned");
let module = dir.join("unsigned.wasm");
std::fs::write(&module, b"\0asm\x01\0\0\0").unwrap();
let (_, ca_der) = znippy_common::sign::dev::mint_ca("holger-plugin-test-ca").unwrap();
let root = dir.join("root.der");
std::fs::write(&root, &ca_der).unwrap();
let err = verify_named(
&ModuleTrust::required([root]),
&module,
&std::fs::read(&module).unwrap(),
)
.expect_err("an unsigned module verified");
assert!(err.contains(ec::PLUGIN_MODULE_UNSIGNED.code), "{err}");
assert!(
err.contains("unsigned.wasm.p7s"),
"the refusal does not name the sidecar: {err}"
);
let _ = std::fs::remove_dir_all(&dir);
}
/// A signature that is not over THESE bytes is `holger-plugin-7`.
#[cfg(feature = "verify-modules")]
#[test]
fn a_signature_over_other_bytes_does_not_verify() {
let dir = scratch("bad");
let module = dir.join("tampered.wasm");
std::fs::write(&module, b"\0asm\x01\0\0\0the real module").unwrap();
std::fs::write(
ModuleTrust::signature_path(&module),
b"not a CMS SignedData",
)
.unwrap();
let (_, ca_der) = znippy_common::sign::dev::mint_ca("holger-plugin-test-ca").unwrap();
let root = dir.join("root.der");
std::fs::write(&root, &ca_der).unwrap();
let err = verify_named(
&ModuleTrust::required([root]),
&module,
&std::fs::read(&module).unwrap(),
)
.expect_err("a bogus signature verified");
assert!(err.contains(ec::PLUGIN_SIGNATURE_INVALID.code), "{err}");
let _ = std::fs::remove_dir_all(&dir);
}
}