|
| 1 | +//! Browser (wasm) bindings for the Rosalind receipt verifier. |
| 2 | +//! |
| 3 | +//! [`verify`] runs the SAME canonical-JSON + BLAKE3 self-hash check that ships in the |
| 4 | +//! `rosalind verify` CLI (via the `rosalind-receipt` crate) — entirely client-side, no |
| 5 | +//! upload, no server. It returns a small JSON string the page renders. |
| 6 | +
|
| 7 | +use rosalind_receipt::{verify_manifest_str, ReceiptVerdict}; |
| 8 | +use wasm_bindgen::prelude::*; |
| 9 | + |
| 10 | +/// Verify a receipt's JSON text. Returns a JSON object string: |
| 11 | +/// `{"verdict":"verified|tampered|unverifiable|unparseable","detail":"…", |
| 12 | +/// "self_hash_ok":true|false|null,"measurement_hash_ok":…,"schema_version":N|null, |
| 13 | +/// "subcommand":"…"|null}` — the page does `JSON.parse(verify(text))`. |
| 14 | +#[wasm_bindgen] |
| 15 | +pub fn verify(json: &str) -> String { |
| 16 | + let c = verify_manifest_str(json); |
| 17 | + let verdict = match c.verdict { |
| 18 | + ReceiptVerdict::Verified => "verified", |
| 19 | + ReceiptVerdict::Tampered => "tampered", |
| 20 | + ReceiptVerdict::Unverifiable => "unverifiable", |
| 21 | + ReceiptVerdict::Unparseable => "unparseable", |
| 22 | + }; |
| 23 | + let tribool = |b: Option<bool>| match b { |
| 24 | + Some(true) => "true", |
| 25 | + Some(false) => "false", |
| 26 | + None => "null", |
| 27 | + }; |
| 28 | + let opt_u32 = |n: Option<u32>| n.map(|v| v.to_string()).unwrap_or_else(|| "null".to_string()); |
| 29 | + let opt_str = |s: Option<String>| match s { |
| 30 | + Some(v) => format!("\"{}\"", json_escape(&v)), |
| 31 | + None => "null".to_string(), |
| 32 | + }; |
| 33 | + format!( |
| 34 | + "{{\"verdict\":\"{}\",\"detail\":\"{}\",\"self_hash_ok\":{},\"measurement_hash_ok\":{},\"schema_version\":{},\"subcommand\":{}}}", |
| 35 | + verdict, |
| 36 | + json_escape(&c.detail), |
| 37 | + tribool(c.self_hash_ok), |
| 38 | + tribool(c.measurement_hash_ok), |
| 39 | + opt_u32(c.schema_version), |
| 40 | + opt_str(c.subcommand), |
| 41 | + ) |
| 42 | +} |
| 43 | + |
| 44 | +/// Minimal JSON string escaping for the small set of characters in our detail/subcommand. |
| 45 | +fn json_escape(s: &str) -> String { |
| 46 | + let mut out = String::with_capacity(s.len()); |
| 47 | + for ch in s.chars() { |
| 48 | + match ch { |
| 49 | + '"' => out.push_str("\\\""), |
| 50 | + '\\' => out.push_str("\\\\"), |
| 51 | + '\n' => out.push_str("\\n"), |
| 52 | + '\r' => out.push_str("\\r"), |
| 53 | + '\t' => out.push_str("\\t"), |
| 54 | + c if (c as u32) < 0x20 => out.push_str(&format!("\\u{:04x}", c as u32)), |
| 55 | + c => out.push(c), |
| 56 | + } |
| 57 | + } |
| 58 | + out |
| 59 | +} |
0 commit comments