Your browser sucks, JavaScript disabled! Gunnar's console needs JavaScript and WebAssembly. Git over SSH on port 2222 does not.

nordisk/holger

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);
    }
}