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

nordisk/holger

plugin/host/tests/the_forge_signed_guests_load.rs

//! **The modules Build Thing built and signed load in this host, answer, and are
//! refused by name the moment their provenance is wrong.**
//!
//! `an_unsigned_module_stops_the_boot.rs` proves the trust gate over a WAT probe
//! signed by a test CA. That leaves the half that matters on a box unmeasured:
//! do the REAL guests — `dwarves buildthing ./holger --suite MVP --variant
//! plugin-*`, built in the pinned toolchain container and signed by the forge
//! (`sign = { purpose = "plugin" }`) — carry a `.p7s` this host accepts, a
//! `.blake3` that is the digest the host computes, and do they answer once
//! loaded?
//!
//! Where the modules are read from: `HOLGER_SIGNED_PLUGIN_DIR`, else the forge's
//! own output path `<repo>/target/wasm32-unknown-unknown/release`. A module
//! there with NO `.p7s` beside it is a host build (`cargo xtask
//! build-wasm-plugins`), not the forge's, and fails with the command that makes
//! the right one — it never skips.
//!
//! The owner's trust root is not in this repository, so the one test that needs
//! it reads `HOLGER_PLUGIN_TRUST_ROOT` (a DER certificate — the root the
//! plugin-signing certificate is issued under) and **skips BY NAME** without
//! it. Every refusal test runs unconditionally: a stranger's root, a stripped
//! signature and a signature moved onto another module need no owner key.
//!
//! The forge needs only the OPEN tree beside holger (no monetize-impl, no
//! holger-ui: the sold row's edge is lazy and holger-ui is the front row's own
//! sibling). On dwarf the forge CLI is `~/.local/bin/dwarves`, built from
//! nordisk/dwarves main in the toolchain container with
//! `cargo build --release -p dwarves --bin dwarves --no-default-features --features
//! cli,build-image,wasm-stage,runs-on-container,container-oci,sign-step`
//! (`sign-step` is the signing; the default `embed-nornir` needs libgraphblas).
//!
//! ```text
//! dwarves buildthing ./holger --suite MVP --variant plugin-allocation   # and the other two
//! HOLGER_PLUGIN_TRUST_ROOT=~/.dwarves/root/korp-root-ca.der \
//!   cargo test -p holger-plugin-host --features wasm-plugins,verify-modules,meter-guests \
//!   --test the_forge_signed_guests_load -- --nocapture
//! ```

use std::path::{Path, PathBuf};
use std::sync::Arc;

use holger_plugin_abi::v2::Capability;
use holger_plugin_host::backend::SharedStore;
use holger_plugin_host::meter::MeterRuntime;
use holger_plugin_host::verify::ModuleTrust;
use holger_plugin_host::{
    DirStore, HandlerBackend, MeterLoadError, NoServices, PluginPolicy, WasmHandler, WasmMeter,
};
use traits::{ArtifactId, RepositoryBackendTrait};
use znippy_common::sign::{dev, ArchiveSigner};

/// `(variant, module file, what the loaded guest must call itself)`.
const HANDLERS: &[(&str, &str, &str)] = &[
    (
        "plugin-skidbladnir",
        "holger_handler_skidbladnir_wasm.wasm",
        "skidbladnir",
    ),
    (
        "plugin-rust-toolchain",
        "holger_handler_rust_toolchain_wasm.wasm",
        "rust-toolchain",
    ),
];
const METER: (&str, &str) = ("plugin-allocation", "holger_plugin_allocation_wasm.wasm");
const METER_GRANT: &[Capability] = &[Capability::UsageRead, Capability::StoreList];

fn all_modules() -> Vec<(&'static str, &'static str)> {
    let mut v: Vec<_> = HANDLERS.iter().map(|(var, m, _)| (*var, *m)).collect();
    v.push(METER);
    v
}

fn signed_dir() -> PathBuf {
    std::env::var("HOLGER_SIGNED_PLUGIN_DIR")
        .map(PathBuf::from)
        .unwrap_or_else(|_| {
            PathBuf::from(env!("CARGO_MANIFEST_DIR"))
                .join("../../target/wasm32-unknown-unknown/release")
        })
}

/// The forge's module, with both sidecars, or a panic carrying the build command.
fn forge_module(variant: &str, file: &str) -> PathBuf {
    let p = signed_dir().join(file);
    let fix = format!(
        "Build it through Build Thing (signed, in the pinned container):\n    \
         dwarves buildthing ./holger --suite MVP --variant {variant}"
    );
    assert!(
        p.is_file(),
        "{} is missing. This test loads the REAL signed module; it does not skip.\n{fix}",
        p.display()
    );
    let sig = ModuleTrust::signature_path(&p);
    assert!(
        sig.is_file(),
        "{} has no {} beside it — that is a host build, not the forge's.\n{fix}",
        p.display(),
        sig.display()
    );
    assert!(
        p.with_file_name(format!("{file}.blake3")).is_file(),
        "{} has no .blake3 beside it.\n{fix}",
        p.display()
    );
    p
}

fn scratch(name: &str) -> PathBuf {
    let p = std::env::temp_dir().join(format!(
        "holger-forge-signed-{name}-{}-{:?}",
        std::process::id(),
        std::thread::current().id()
    ));
    let _ = std::fs::remove_dir_all(&p);
    std::fs::create_dir_all(&p).expect("scratch dir");
    p
}

/// Copy a module (and, when asked, its `.p7s`) into a fresh directory.
fn staged(name: &str, module: &Path, with_signature: bool) -> PathBuf {
    let dir = scratch(name);
    let dst = dir.join(module.file_name().unwrap());
    std::fs::copy(module, &dst).expect("copy module");
    if with_signature {
        std::fs::copy(
            ModuleTrust::signature_path(module),
            ModuleTrust::signature_path(&dst),
        )
        .expect("copy signature");
    }
    dst
}

fn store(name: &str) -> SharedStore {
    Arc::new(DirStore::new(scratch(name)))
}

/// Load a module of either kind under `policy`: the refusal string, or the
/// verified signer and digest.
fn load(
    file: &str,
    path: &Path,
    policy: &PluginPolicy,
) -> Result<(Option<String>, String), String> {
    if file == METER.1 {
        WasmMeter::load_with(
            path,
            store("meter"),
            Arc::new(NoServices),
            policy,
            METER_GRANT,
        )
        .map(|m| (m.signer().map(str::to_string), m.digest_hex().to_string()))
        .map_err(|e| match e {
            MeterLoadError::Refused { module, reason } => format!("{module}: {reason}"),
            other => format!("{other:?}"),
        })
    } else {
        WasmHandler::load_with(path, store("handler"), policy)
            .map(|h| (h.signer().map(str::to_string), h.digest_hex().to_string()))
    }
}

/// A policy that requires trust in a freshly minted stranger's root.
fn strangers_policy(dir: &Path, der: &[u8]) -> PluginPolicy {
    let root = dir.join("stranger-root.der");
    std::fs::write(&root, der).expect("write root");
    PluginPolicy::new().with_trust(ModuleTrust::required([root]))
}

/// **THE positive half**: under the owner's root every forge-signed guest
/// verifies, the host's digest is the forge's `.blake3`, and the guest answers.
#[test]
fn every_forge_signed_guest_verifies_under_the_owner_root_and_answers() {
    let Ok(root) = std::env::var("HOLGER_PLUGIN_TRUST_ROOT") else {
        println!(
            "SKIPPED BY NAME [forge-signed-guests-load]: set HOLGER_PLUGIN_TRUST_ROOT to the DER \
             root the plugin-signing certificate is issued under to measure this"
        );
        return;
    };
    let policy = PluginPolicy::new().with_trust(ModuleTrust::required([PathBuf::from(&root)]));

    for (variant, file) in all_modules() {
        let module = forge_module(variant, file);
        let (signer, digest) = load(file, &module, &policy)
            .unwrap_or_else(|e| panic!("{variant}: the forge-signed module was refused: {e}"));
        let signer = signer
            .unwrap_or_else(|| panic!("{variant}: trust was required and no signer was reported"));
        let sidecar = std::fs::read_to_string(module.with_file_name(format!("{file}.blake3")))
            .expect("read .blake3");
        assert_eq!(
            digest,
            sidecar.trim(),
            "{variant}: the host's digest is not the forge's .blake3"
        );
        println!("MEASURED: {variant} {file} verified, signer '{signer}', blake3 {digest}");
    }

    // …and each one ANSWERS once loaded — a call into the guest, not only a load.
    for (variant, file, name) in HANDLERS {
        let module = forge_module(variant, file);
        let h = WasmHandler::load_with(&module, store("answer"), &policy).expect("loads");
        assert_eq!(
            h.manifest().handler,
            *name,
            "{variant}: the guest calls itself something else"
        );
        let backend = HandlerBackend::new("bundles", h.into_runtime()).expect("wraps as a backend");
        let id = ArtifactId {
            namespace: None,
            name: "nothing-here".into(),
            version: "0".into(),
        };
        assert_eq!(
            backend
                .fetch(&id)
                .expect("the guest answered fetch with an error"),
            None
        );
        println!(
            "MEASURED: {variant} answered manifest ({name}) and fetch (None on an empty store)"
        );
    }
    let module = forge_module(METER.0, METER.1);
    let m = WasmMeter::load_with(
        &module,
        store("answer-meter"),
        Arc::new(NoServices),
        &policy,
        METER_GRANT,
    )
    .unwrap_or_else(|e| panic!("{}: {e:?}", METER.0));
    let declared = m.meters().expect("the meter guest answered `meters`");
    assert!(
        !declared.is_empty(),
        "the open meter guest declares its storage meters"
    );
    println!(
        "MEASURED: {} answered `meters` with {} declaration(s)",
        METER.0,
        declared.len()
    );
}

/// The forge's signature is a perfectly valid signature — over a root this box
/// never trusted. Refused under `holger-plugin-7`, naming the module.
#[test]
fn a_forge_signed_guest_under_a_strangers_root_is_refused_by_name() {
    for (variant, file) in all_modules() {
        let module = staged(
            &format!("stranger-{variant}"),
            &forge_module(variant, file),
            true,
        );
        let (_, der) = dev::mint_ca("a stranger's CA").expect("mint CA");
        let policy = strangers_policy(module.parent().unwrap(), &der);
        let err = load(file, &module, &policy)
            .expect_err("a signature chaining to an untrusted root passed");
        assert!(
            err.contains(holger_errcode::PLUGIN_SIGNATURE_INVALID.code),
            "{variant}: {err}"
        );
        assert!(
            err.contains(file),
            "{variant}: the refusal does not name the module: {err}"
        );
    }
}

/// The same bytes with the `.p7s` stripped: `holger-plugin-6`, naming the
/// sidecar the operator must supply — never a module quietly loaded.
#[test]
fn a_forge_built_guest_without_its_signature_is_refused_by_name() {
    for (variant, file) in all_modules() {
        let module = staged(
            &format!("unsigned-{variant}"),
            &forge_module(variant, file),
            false,
        );
        let (_, der) = dev::mint_ca("a stranger's CA").expect("mint CA");
        let policy = strangers_policy(module.parent().unwrap(), &der);
        let err = load(file, &module, &policy).expect_err("an unsigned module loaded");
        assert!(
            err.contains(holger_errcode::PLUGIN_MODULE_UNSIGNED.code),
            "{variant}: {err}"
        );
        assert!(
            err.contains(&format!("{file}.p7s")),
            "{variant}: the refusal does not name the sidecar: {err}"
        );
    }
}

/// A signature by a TRUSTED key, but over another module's bytes — moved onto
/// this one. `holger-plugin-7`: the signature is checked, not counted.
#[test]
fn a_trusted_signature_over_another_guest_is_refused_by_name() {
    let mods = all_modules();
    for (i, (variant, file)) in mods.iter().enumerate() {
        let (other_variant, other_file) = mods[(i + 1) % mods.len()];
        let other_bytes =
            std::fs::read(forge_module(other_variant, other_file)).expect("read the other module");
        let module = staged(
            &format!("moved-{variant}"),
            &forge_module(variant, file),
            false,
        );
        let (key, der) = dev::mint_ca("a stranger's CA").expect("mint CA");
        let policy = strangers_policy(module.parent().unwrap(), &der);
        // The policy's own root signs — but the OTHER module's digest.
        let signer = dev::new_p256_signer(&key, &der, "trusted publisher").expect("signer");
        let digest: [u8; 32] = *znippy_common::blake3::hash(&other_bytes).as_bytes();
        std::fs::write(
            ModuleTrust::signature_path(&module),
            signer.sign_digest(&digest).expect("sign"),
        )
        .expect("write the moved signature");
        let err = load(file, &module, &policy)
            .expect_err("a signature over another module's bytes passed");
        assert!(
            err.contains(holger_errcode::PLUGIN_SIGNATURE_INVALID.code),
            "{variant}: {err}"
        );
        assert!(
            err.contains(file),
            "{variant}: the refusal does not name the module: {err}"
        );
    }
}