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/release_dist.rs

// SPDX-License-Identifier: AGPL-3.0-only
//! The release components of the verify-bundle guest: reproducible bytes
//! (no build path inside, the same module from another target directory),
//! `dist` writes exactly the components the tests load plus their blake3
//! pins, and `sign` signs them the way Holger's plugins are signed — a CMS
//! by the plugin-signing certificate under the korp root — and refuses a
//! readable key, a wrong pin, a second signing and a certificate from another
//! root, leaving no signature behind. Heimdall is a defensive sandbox.

mod common;
use common::guests::{component, core_module, release, Build, RELEASE};
use common::{ca, leaf};
use release::guest_build::build_core_in;

use std::ffi::OsStr;
use std::path::{Path, PathBuf};
use std::process::Command;

fn contains(hay: &[u8], needle: &[u8]) -> bool {
    hay.windows(needle.len()).any(|w| w == needle)
}

#[test]
fn the_release_modules_carry_no_build_path() {
    let src = env!("CARGO_MANIFEST_DIR").as_bytes();
    let tgt = env!("CARGO_TARGET_TMPDIR").as_bytes();
    for b in RELEASE {
        let m = core_module(b);
        assert!(
            !contains(m, src),
            "the {b:?} module names its checkout path, so another checkout builds other bytes"
        );
        assert!(
            !contains(m, tgt),
            "the {b:?} module names its target directory"
        );
        let home = cargo_home();
        assert!(
            !contains(m, home.to_str().unwrap().as_bytes()),
            "the {b:?} module names CARGO_HOME ({}), so another cargo home builds other bytes",
            home.display()
        );
    }
}

/// The cargo home the tests' own builds use.
fn cargo_home() -> PathBuf {
    std::env::var_os("CARGO_HOME")
        .map(PathBuf::from)
        .unwrap_or_else(|| PathBuf::from(std::env::var_os("HOME").unwrap()).join(".cargo"))
}

/// `from` copied to `to`, without target directories and .git.
fn copy_tree(from: &Path, to: &Path) {
    std::fs::create_dir_all(to).unwrap();
    for e in std::fs::read_dir(from).unwrap() {
        let e = e.unwrap();
        let name = e.file_name();
        if name == "target" || name == ".git" {
            continue;
        }
        let ty = e.file_type().unwrap();
        if ty.is_dir() {
            copy_tree(&e.path(), &to.join(&name));
        } else if ty.is_file() {
            std::fs::copy(e.path(), to.join(&name)).unwrap();
        }
    }
}

/// The heimdall checkout copied to `at`; returns the guest crate inside it.
fn checkout_at(at: &Path) -> PathBuf {
    let repo = Path::new(env!("CARGO_MANIFEST_DIR")).join("../..");
    copy_tree(&repo, at);
    at.join("guests/verify-bundle")
}

#[test]
fn dist_builds_the_same_modules_from_any_checkout_cargo_home_vendored_or_not() {
    // Two other checkouts at other paths, each with its own CARGO_HOME:
    // (a) the registry's sources under another cargo home path (the same
    // downloads, linked), (b) every dependency vendored (`cargo vendor`,
    // unversioned directory names) and the registry replaced by it. Both
    // must build the bytes the tests' own checkout builds, module for module.
    let base = Path::new(env!("CARGO_TARGET_TMPDIR")).join("remap");
    let _ = std::fs::remove_dir_all(&base);
    let real_home = cargo_home();

    let a = checkout_at(&base.join("a/heimdall"));
    let home_a = base.join("home-a/cargo");
    std::fs::create_dir_all(&home_a).unwrap();
    std::os::unix::fs::symlink(real_home.join("registry"), home_a.join("registry")).unwrap();

    let b = checkout_at(&base.join("bb/elsewhere/deeper/hd"));
    let home_b = base.join("home-b");
    std::fs::create_dir_all(&home_b).unwrap();
    let vendor = base.join("vendored-sources");
    let st = Command::new(std::env::var("CARGO").unwrap_or_else(|_| "cargo".into()))
        .args(["vendor", "--locked", "--manifest-path"])
        .arg(b.join("Cargo.toml"))
        .arg(&vendor)
        .stdout(std::process::Stdio::null())
        .status()
        .unwrap();
    assert!(st.success(), "cargo vendor failed");
    std::fs::write(
        home_b.join("config.toml"),
        format!(
            "[source.crates-io]\nreplace-with = \"vendored\"\n[source.vendored]\ndirectory = \"{}\"\n",
            vendor.display()
        ),
    )
    .unwrap();

    for (dir, home, tgt) in [(&a, &home_a, "ta"), (&b, &home_b, "tb")] {
        let env: [(&str, &OsStr); 2] = [
            ("CARGO_HOME", home.as_os_str()),
            ("CARGO_NET_OFFLINE", OsStr::new("true")),
        ];
        for build in RELEASE {
            let m = build_core_in(dir, &env, build, &base.join(tgt));
            assert!(
                m == core_module(build),
                "the {build:?} module from {} with CARGO_HOME {} is not the tests' own",
                dir.display(),
                home.display()
            );
        }
    }
    let _ = std::fs::remove_dir_all(&base);
}

#[test]
fn another_target_dir_builds_the_same_job_module() {
    // The job build is the one with C in it (OpenZL), where paths leak first.
    let other = tempfile::tempdir().unwrap();
    let again = Build::Job;
    let bytes = {
        let out = other.path().join("dist");
        let done = release::dist(&out, &other.path().join("t")).unwrap();
        assert_eq!(done.len(), 3);
        std::fs::read(out.join(again.release_name().unwrap())).unwrap()
    };
    assert_eq!(
        bytes,
        component(Build::Job),
        "a build in another target directory gave other bytes"
    );
}

#[test]
fn dist_writes_the_components_the_tests_load_with_their_blake3() {
    let out = tempfile::tempdir().unwrap();
    // The same target root as the tests' own builds: nothing is rebuilt.
    let done = release::dist(out.path(), Path::new(env!("CARGO_TARGET_TMPDIR"))).unwrap();
    let names: Vec<&str> = done.iter().map(|a| a.name).collect();
    assert_eq!(
        names,
        [
            "heimdall-verify-bundle.wasm",
            "heimdall-verify-bundle-verifier.wasm",
            "heimdall-verify-bundle-decoder.wasm"
        ]
    );
    for (a, b) in done.iter().zip(RELEASE) {
        let bytes = std::fs::read(&a.path).unwrap();
        assert_eq!(
            bytes,
            component(b),
            "{} is not the component the tests load",
            a.name
        );
        assert_eq!(a.blake3, blake3::hash(&bytes).to_hex().to_string());
        assert_eq!(a.sha256.len(), 64);
        assert_eq!(
            std::fs::read_to_string(out.path().join(format!("{}.blake3", a.name))).unwrap(),
            format!("{}\n", a.blake3)
        );
        assert_ne!(
            bytes[4..8],
            [1, 0, 0, 0],
            "{} is a core module, not a component",
            a.name
        );
    }
}

/// A korp-shaped PKI in `dir`: root CA, plugin-signing leaf + its PKCS#8
/// key (mode `mode`), as `dwarves sign-cert issue --purpose plugin` lays it.
fn material(dir: &Path, mode: u32) {
    let root = ca("korp root (test)");
    let plugin = leaf("nordisk plugin signing", &root);
    std::fs::write(dir.join(release::ROOT_CA_FILE), &root.der).unwrap();
    std::fs::write(dir.join(release::PLUGIN_CERT_FILE), &plugin.cert_der).unwrap();
    let key = dir.join(release::PLUGIN_KEY_FILE);
    std::fs::write(&key, &plugin.key_pkcs8).unwrap();
    use std::os::unix::fs::PermissionsExt as _;
    std::fs::set_permissions(&key, std::fs::Permissions::from_mode(mode)).unwrap();
}

fn dist_copy() -> tempfile::TempDir {
    let out = tempfile::tempdir().unwrap();
    release::dist(out.path(), Path::new(env!("CARGO_TARGET_TMPDIR"))).unwrap();
    out
}

fn p7s(dist: &Path) -> Vec<PathBuf> {
    let mut v: Vec<PathBuf> = std::fs::read_dir(dist)
        .unwrap()
        .map(|e| e.unwrap().path())
        .filter(|p| p.extension().is_some_and(|e| e == "p7s"))
        .collect();
    v.sort();
    v
}

#[test]
fn sign_writes_three_signatures_that_the_root_verifies_and_the_host_loads() {
    let m = tempfile::tempdir().unwrap();
    material(m.path(), 0o600);
    let d = dist_copy();
    let signed = release::sign(m.path(), d.path()).unwrap();
    assert_eq!(signed.len(), 3);
    assert!(signed.iter().all(|s| s.signer == "nordisk plugin signing"));
    assert_eq!(p7s(d.path()).len(), 3);

    // Holger's side: the korp root is the host's only trust root.
    let host = heimdall_wasm::Host::new(heimdall_wasm::HostConfig::new([m
        .path()
        .join(release::ROOT_CA_FILE)]))
    .unwrap();
    let job = d.path().join("heimdall-verify-bundle.wasm");
    host.load_pinned(&job, &signed[0].blake3).unwrap();
    // Another root does not load it.
    let other = tempfile::tempdir().unwrap();
    material(other.path(), 0o600);
    let stranger = heimdall_wasm::Host::new(heimdall_wasm::HostConfig::new([other
        .path()
        .join(release::ROOT_CA_FILE)]))
    .unwrap();
    assert!(
        stranger.load(&job).is_err(),
        "a host with another root loaded the guest"
    );
}

#[test]
fn sign_refuses_a_key_others_can_read() {
    let m = tempfile::tempdir().unwrap();
    material(m.path(), 0o644);
    let d = dist_copy();
    let e = release::sign(m.path(), d.path()).unwrap_err();
    assert!(e.contains("0600"), "{e}");
    assert!(p7s(d.path()).is_empty());
}

#[test]
fn sign_refuses_a_module_that_is_not_its_pin_and_leaves_no_signature() {
    let m = tempfile::tempdir().unwrap();
    material(m.path(), 0o600);
    let d = dist_copy();
    // The last module changed after its pin was written.
    let dec = d.path().join("heimdall-verify-bundle-decoder.wasm");
    let mut bytes = std::fs::read(&dec).unwrap();
    let n = bytes.len() - 1;
    bytes[n] ^= 1;
    std::fs::write(&dec, bytes).unwrap();
    let e = release::sign(m.path(), d.path()).unwrap_err();
    assert!(e.contains("not the pinned"), "{e}");
    assert!(
        p7s(d.path()).is_empty(),
        "a half-signed release was left behind"
    );
}

#[test]
fn sign_refuses_to_sign_twice() {
    let m = tempfile::tempdir().unwrap();
    material(m.path(), 0o600);
    let d = dist_copy();
    release::sign(m.path(), d.path()).unwrap();
    let before: Vec<Vec<u8>> = p7s(d.path())
        .iter()
        .map(|p| std::fs::read(p).unwrap())
        .collect();
    let e = release::sign(m.path(), d.path()).unwrap_err();
    assert!(e.contains("signed once"), "{e}");
    let after: Vec<Vec<u8>> = p7s(d.path())
        .iter()
        .map(|p| std::fs::read(p).unwrap())
        .collect();
    assert_eq!(before, after, "the first signatures were touched");
}

#[test]
fn sign_refuses_a_certificate_from_another_root_and_leaves_no_signature() {
    let m = tempfile::tempdir().unwrap();
    material(m.path(), 0o600);
    // The certificate and key are from another PKI than korp-root-ca.der.
    let other = tempfile::tempdir().unwrap();
    material(other.path(), 0o600);
    for f in [release::PLUGIN_CERT_FILE, release::PLUGIN_KEY_FILE] {
        std::fs::copy(other.path().join(f), m.path().join(f)).unwrap();
    }
    let d = dist_copy();
    let e = release::sign(m.path(), d.path()).unwrap_err();
    assert!(e.contains("does not verify"), "{e}");
    assert!(p7s(d.path()).is_empty());
}