1use std::io::Cursor;
16
17use minisign::{PublicKey, SignatureBox};
18use waf_wasm::WasmModule;
19
20pub use waf_wasm::WasmOptions;
22
23pub fn verify_signature(
27 wasm: &[u8],
28 signature: &str,
29 trusted_pubkeys: &[String],
30) -> Result<(), String> {
31 let sig_box =
32 SignatureBox::from_string(signature).map_err(|e| format!("malformed signature: {e}"))?;
33 if trusted_pubkeys.is_empty() {
34 return Err("no trusted_keys configured".to_string());
35 }
36 for key_b64 in trusted_pubkeys {
37 let Ok(pk) = PublicKey::from_base64(key_b64) else {
38 continue; };
40 if minisign::verify(&pk, &sig_box, Cursor::new(wasm), true, false, false).is_ok() {
41 return Ok(());
42 }
43 }
44 Err("signature does not verify against any trusted key".to_string())
45}
46
47pub fn load_verified_plugin(
51 name: &str,
52 wasm: &[u8],
53 signature: &str,
54 trusted_pubkeys: &[String],
55 config: &[u8],
56 opts: &WasmOptions,
57) -> Result<WasmModule, String> {
58 verify_signature(wasm, signature, trusted_pubkeys)?;
59 let (module, _report) = WasmModule::from_bytes(name, wasm, config, opts)
60 .map_err(|e| format!("verified plugin {name:?} failed to load: {e}"))?;
61 Ok(module)
62}
63
64#[cfg(test)]
65mod tests {
66 use super::*;
67 use minisign::KeyPair;
68
69 fn sign(data: &[u8]) -> (String, String) {
71 let kp = KeyPair::generate_unencrypted_keypair().unwrap();
72 let sig_box = minisign::sign(None, &kp.sk, Cursor::new(data), None, None).unwrap();
73 (sig_box.into_string(), kp.pk.to_base64())
74 }
75
76 const WASM: &[u8] = b"\x00asm\x01\x00\x00\x00 pretend this is a plugin";
77
78 #[test]
79 fn valid_signature_verifies() {
80 let (sig, pk) = sign(WASM);
81 assert!(verify_signature(WASM, &sig, &[pk]).is_ok());
82 }
83
84 #[test]
85 fn tampered_bytes_fail() {
86 let (sig, pk) = sign(WASM);
87 let mut tampered = WASM.to_vec();
88 tampered[10] ^= 0xff;
89 assert!(verify_signature(&tampered, &sig, &[pk]).is_err());
90 }
91
92 #[test]
93 fn wrong_key_fails() {
94 let (sig, _pk) = sign(WASM);
95 let (_sig2, other_pk) = sign(WASM); assert!(verify_signature(WASM, &sig, &[other_pk]).is_err());
97 }
98
99 #[test]
100 fn malformed_signature_fails() {
101 assert!(verify_signature(WASM, "not a minisig", &["x".to_string()]).is_err());
102 }
103
104 #[test]
105 fn no_trusted_keys_fails() {
106 let (sig, _pk) = sign(WASM);
107 assert!(verify_signature(WASM, &sig, &[]).is_err());
108 }
109
110 #[test]
111 fn verifies_against_any_of_several_keys() {
112 let (sig, pk) = sign(WASM);
113 let (_s, other) = sign(WASM);
114 assert!(verify_signature(WASM, &sig, &[other, pk]).is_ok());
116 }
117
118 fn opts() -> WasmOptions {
119 WasmOptions::default()
120 }
121
122 #[test]
123 fn load_gate_rejects_bad_signature_before_touching_the_runtime() {
124 let wasm = wat::parse_str("(module)").unwrap();
126 let (sig, _pk) = sign(&wasm);
127 let (_s, other) = sign(&wasm); match load_verified_plugin("p", &wasm, &sig, &[other], b"", &opts()) {
129 Ok(_) => panic!("an untrusted signature must not load"),
130 Err(e) => assert!(e.contains("verify") || e.contains("trusted"), "gate error, got: {e}"),
131 }
132 }
133
134 #[test]
135 fn load_with_valid_signature_reaches_the_runtime() {
136 let wasm = wat::parse_str("(module)").unwrap();
139 let (sig, pk) = sign(&wasm);
140 match load_verified_plugin("p", &wasm, &sig, &[pk], b"", &opts()) {
141 Ok(_) => {} Err(e) => assert!(
143 !e.contains("verify") && !e.contains("trusted"),
144 "must fail (if at all) at the runtime, not the gate: {e}"
145 ),
146 }
147 }
148}