use std::io::Cursor;
use minisign::{PublicKey, SignatureBox};
use waf_wasm::WasmModule;
pub use waf_wasm::WasmOptions;
pub fn verify_signature(
wasm: &[u8],
signature: &str,
trusted_pubkeys: &[String],
) -> Result<(), String> {
let sig_box =
SignatureBox::from_string(signature).map_err(|e| format!("malformed signature: {e}"))?;
if trusted_pubkeys.is_empty() {
return Err("no trusted_keys configured".to_string());
}
for key_b64 in trusted_pubkeys {
let Ok(pk) = PublicKey::from_base64(key_b64) else {
continue; };
if minisign::verify(&pk, &sig_box, Cursor::new(wasm), true, false, false).is_ok() {
return Ok(());
}
}
Err("signature does not verify against any trusted key".to_string())
}
pub fn load_verified_plugin(
name: &str,
wasm: &[u8],
signature: &str,
trusted_pubkeys: &[String],
config: &[u8],
opts: &WasmOptions,
) -> Result<WasmModule, String> {
verify_signature(wasm, signature, trusted_pubkeys)?;
let (module, _report) = WasmModule::from_bytes(name, wasm, config, opts)
.map_err(|e| format!("verified plugin {name:?} failed to load: {e}"))?;
Ok(module)
}
#[cfg(test)]
mod tests {
use super::*;
use minisign::KeyPair;
fn sign(data: &[u8]) -> (String, String) {
let kp = KeyPair::generate_unencrypted_keypair().unwrap();
let sig_box = minisign::sign(None, &kp.sk, Cursor::new(data), None, None).unwrap();
(sig_box.into_string(), kp.pk.to_base64())
}
const WASM: &[u8] = b"\x00asm\x01\x00\x00\x00 pretend this is a plugin";
#[test]
fn valid_signature_verifies() {
let (sig, pk) = sign(WASM);
assert!(verify_signature(WASM, &sig, &[pk]).is_ok());
}
#[test]
fn tampered_bytes_fail() {
let (sig, pk) = sign(WASM);
let mut tampered = WASM.to_vec();
tampered[10] ^= 0xff;
assert!(verify_signature(&tampered, &sig, &[pk]).is_err());
}
#[test]
fn wrong_key_fails() {
let (sig, _pk) = sign(WASM);
let (_sig2, other_pk) = sign(WASM); assert!(verify_signature(WASM, &sig, &[other_pk]).is_err());
}
#[test]
fn malformed_signature_fails() {
assert!(verify_signature(WASM, "not a minisig", &["x".to_string()]).is_err());
}
#[test]
fn no_trusted_keys_fails() {
let (sig, _pk) = sign(WASM);
assert!(verify_signature(WASM, &sig, &[]).is_err());
}
#[test]
fn verifies_against_any_of_several_keys() {
let (sig, pk) = sign(WASM);
let (_s, other) = sign(WASM);
assert!(verify_signature(WASM, &sig, &[other, pk]).is_ok());
}
fn opts() -> WasmOptions {
WasmOptions::default()
}
#[test]
fn load_gate_rejects_bad_signature_before_touching_the_runtime() {
let wasm = wat::parse_str("(module)").unwrap();
let (sig, _pk) = sign(&wasm);
let (_s, other) = sign(&wasm); match load_verified_plugin("p", &wasm, &sig, &[other], b"", &opts()) {
Ok(_) => panic!("an untrusted signature must not load"),
Err(e) => assert!(e.contains("verify") || e.contains("trusted"), "gate error, got: {e}"),
}
}
#[test]
fn load_with_valid_signature_reaches_the_runtime() {
let wasm = wat::parse_str("(module)").unwrap();
let (sig, pk) = sign(&wasm);
match load_verified_plugin("p", &wasm, &sig, &[pk], b"", &opts()) {
Ok(_) => {} Err(e) => assert!(
!e.contains("verify") && !e.contains("trusted"),
"must fail (if at all) at the runtime, not the gate: {e}"
),
}
}
}