jobs/runner/src/guests.rs
//! **Heimdall's verify-bundle guests, as Holger pins and loads them** (lane HOL-HEIMDALL-02,
//! the pins with feature `heimdall` or `heimdall-wasm`, the loading with `heimdall-wasm`).
//!
//! The released components (heimdall-guests-0.2.1) are vendored in this repository under
//! [`DIR`] (git LFS) with their `.wasm.blake3` and `.wasm.p7s` and the release's SLSA
//! statement; the medium lays them from there. As heimdall guests/verify-bundle/README.md
//! says, the BLAKE3 pins are Holger's OWN ([`RELEASE`], compiled in), not only the sidecars
//! beside the files, and the only trust root is the korp root (the one Holger trusts for its
//! wasm plugins). Holger runs the SPLIT pair: the verifier, and its decoder loaded first.
//! Any refusal is a load error by name; Holger never falls back to parsing a bundle itself.
use std::path::Path;
#[cfg(feature = "heimdall-wasm")]
use std::path::PathBuf;
/// Where the vendored release lives in this repository (and on the medium).
pub const DIR: &str = "guests/heimdall/0.2.1";
/// One pinned component.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Pin {
pub role: &'static str,
pub file: &'static str,
pub sha256: &'static str,
pub blake3: &'static str,
}
/// A pinned release of the guests.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Release {
pub release: &'static str,
pub tag: &'static str,
pub commit: &'static str,
pub attestation: &'static str,
pub verifier: Pin,
pub decoder: Pin,
pub single: Pin,
}
/// **heimdall-guests-0.2.1**: tag heimdall-guests-v0.2.1 at d2f92d15, built reproducibly from
/// heimdall 1336bd6, SLSA provenance heimdall 7edc32b, signed with the owner's YubiKey.
pub const RELEASE: Release = Release {
release: "heimdall-guests-0.2.1",
tag: "heimdall-guests-v0.2.1",
commit: "d2f92d154bc3314d7acddcb01ff9339f30b1162a",
attestation: "heimdall-guests-0.2.1.intoto.jsonl",
verifier: Pin {
role: "verifier",
file: "heimdall-verify-bundle-verifier.wasm",
sha256: "9d2b008265be177e24f5303bb1b2af32716a2c0ffce6ec4185d2f5a7d6d97e32",
blake3: "b49330e0eedd47231b3e481d828e3eaba726b8ad03dc01c02b6e7aa983366f2f",
},
decoder: Pin {
role: "decoder",
file: "heimdall-verify-bundle-decoder.wasm",
sha256: "8fb7028920beb1f30804481bf7851a288b4f984619394486978edb04d0449b03",
blake3: "2a1960512f8b941eccb428fa5d897cf24a11af84423c9312f1a4360bb4b5e4df",
},
single: Pin {
role: "single",
file: "heimdall-verify-bundle.wasm",
sha256: "531157fe62223bf21e3946c292f17dedd5abb48ff709ce8b01ea34a0ed84956b",
blake3: "75481d12e5546b12d2670179a7f6ba8de76251e88ffd8d50de4a238daa94df05",
},
};
impl Release {
pub fn pins(&self) -> [Pin; 3] {
[self.verifier, self.decoder, self.single]
}
}
/// The files of `pin` in `dir` match it: the module's sha256 and BLAKE3, and its
/// `.wasm.blake3` sidecar says the same BLAKE3. A mismatch is named.
pub fn check_files(dir: &Path, pin: &Pin) -> Result<(), String> {
use sha2::Digest as _;
let p = dir.join(pin.file);
let bytes = std::fs::read(&p).map_err(|e| format!("{}: {e}", p.display()))?;
let sha = hex::encode(sha2::Sha256::digest(&bytes));
if sha != pin.sha256 {
return Err(format!(
"{}: sha256 {sha}, the pin is {}",
pin.file, pin.sha256
));
}
let b3 = blake3::hash(&bytes).to_hex().to_string();
if b3 != pin.blake3 {
return Err(format!(
"{}: blake3 {b3}, the pin is {}",
pin.file, pin.blake3
));
}
let side = dir.join(format!("{}.blake3", pin.file));
let s = std::fs::read_to_string(&side).map_err(|e| format!("{}: {e}", side.display()))?;
if s.split_whitespace().next() != Some(pin.blake3) {
return Err(format!(
"{}.blake3 does not say the pin {}",
pin.file, pin.blake3
));
}
Ok(())
}
/// Every file of `release` in `dir` is the pinned one (the server checks the laid guests at
/// boot, lane HOL-HEIMDALL-04).
pub fn check_release(dir: &Path, release: &Release) -> Result<(), String> {
for pin in release.pins() {
check_files(dir, &pin)?;
}
Ok(())
}
/// The name of the trust root's DER file a verify-bundle job carries in its input.
pub const TRUST_ROOT_FILE: &str = "korp-root-ca.der";
/// A trust root file as DER: a PEM `CERTIFICATE` is decoded, anything else is taken as DER.
pub fn der_of(bytes: &[u8]) -> Result<Vec<u8>, String> {
let Ok(text) = std::str::from_utf8(bytes) else {
return Ok(bytes.to_vec());
};
if !text.contains("-----BEGIN CERTIFICATE-----") {
return Ok(bytes.to_vec());
}
let b64: String = text
.lines()
.skip_while(|l| !l.starts_with("-----BEGIN CERTIFICATE-----"))
.skip(1)
.take_while(|l| !l.starts_with("-----END"))
.collect();
decode_b64(b64.trim()).ok_or_else(|| "the trust root PEM does not decode".to_string())
}
fn decode_b64(s: &str) -> Option<Vec<u8>> {
let val = |c: u8| -> Option<u32> {
Some(match c {
b'A'..=b'Z' => c - b'A',
b'a'..=b'z' => c - b'a' + 26,
b'0'..=b'9' => c - b'0' + 52,
b'+' => 62,
b'/' => 63,
_ => return None,
} as u32)
};
let bytes: Vec<u8> = s.bytes().filter(|b| !b.is_ascii_whitespace()).collect();
if !bytes.len().is_multiple_of(4) {
return None;
}
let mut out = Vec::with_capacity(bytes.len() / 4 * 3);
for q in bytes.chunks(4) {
let pad = q.iter().rev().take_while(|&&c| c == b'=').count();
let mut n = 0u32;
for (i, &c) in q.iter().enumerate() {
n <<= 6;
if i < 4 - pad {
n |= val(c)?;
}
}
let b = n.to_be_bytes();
out.extend_from_slice(&b[1..4 - pad]);
}
Some(out)
}
/// **Load the split verify-bundle guest** from `dir`: every file against its pin first,
/// then Heimdall's host verifies each module's `.wasm.p7s` against `trust_roots` (DER; the
/// korp root) and holds it to Holger's own BLAKE3 pin, the decoder first.
#[cfg(feature = "heimdall-wasm")]
pub fn load_split(
dir: &Path,
release: &Release,
trust_roots: &[PathBuf],
limits: &heimdall_run::RunLimits,
) -> Result<heimdall_run::wasm::WasmGuest, String> {
check_files(dir, &release.verifier)?;
check_files(dir, &release.decoder)?;
heimdall_run::wasm::load_split_guest(
&dir.join(release.verifier.file),
Some(release.verifier.blake3),
&dir.join(release.decoder.file),
Some(release.decoder.blake3),
trust_roots,
limits,
)
.map_err(|e| format!("the verify-bundle guest {}: {e}", release.release))
}
/// The split guest as a Heimdall job's kind (`WasmJobRunner`): the verifier and its
/// decoder, each by path and Holger's BLAKE3 pin.
#[cfg(feature = "heimdall-wasm")]
pub fn split_kind(dir: &Path, release: &Release) -> heimdall_core::JobKind {
heimdall_core::JobKind::wasm_split(
heimdall_core::GuestRef {
path: dir.join(release.verifier.file),
blake3: release.verifier.blake3.into(),
},
heimdall_core::GuestRef {
path: dir.join(release.decoder.file),
blake3: release.decoder.blake3.into(),
},
)
}