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

nordisk/heimdall

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