use anyhow::Context;
use base64::{Engine as _, engine::general_purpose};
use ed25519_dalek::{Signature, Verifier, VerifyingKey};
use serde::Deserialize;
use serde_json::Value;
use std::collections::HashMap;
use std::sync::{Arc, Mutex};
pub trait TrustStore: Send + Sync {
fn get_public_key(&self, signer: &str) -> Option<Vec<u8>>;
}
#[derive(Default, Clone)]
pub struct InMemoryTrustStore {
inner: Arc<Mutex<HashMap<String, Vec<u8>>>>,
}
impl InMemoryTrustStore {
pub fn new() -> Self {
Self {
inner: Arc::new(Mutex::new(HashMap::new())),
}
}
pub fn insert(&self, signer: impl Into<String>, pk: Vec<u8>) {
let mut g = self.inner.lock().unwrap();
g.insert(signer.into(), pk);
}
}
impl TrustStore for InMemoryTrustStore {
fn get_public_key(&self, signer: &str) -> Option<Vec<u8>> {
let g = self.inner.lock().unwrap();
g.get(signer).cloned()
}
}
#[derive(Deserialize)]
struct SignedManifest {
signer: String,
signature: String, }
pub fn verify_manifest(
signed_manifest_json: &str,
canonical_manifest_bytes: &[u8],
trust: &dyn TrustStore,
) -> anyhow::Result<()> {
let sm: SignedManifest =
serde_json::from_str(signed_manifest_json).context("parse signed manifest")?;
let sig_bytes = general_purpose::STANDARD
.decode(&sm.signature)
.context("decode signature")?;
let sig = Signature::try_from(sig_bytes.as_slice()).context("parse signature")?;
let pk_bytes = trust
.get_public_key(&sm.signer)
.ok_or_else(|| anyhow::anyhow!("unknown signer"))?;
let pk_array: [u8; 32] = pk_bytes.try_into().map_err(|_| anyhow::anyhow!("invalid public key length"))?;
let pk = VerifyingKey::from_bytes(&pk_array).context("parse public key")?;
pk.verify(canonical_manifest_bytes, &sig)
.context("signature verification failed")?;
Ok(())
}
pub fn verify_manifest_signature(
manifest_bytes: &[u8],
signature_b64: &str,
public_key_bytes: &[u8],
) -> anyhow::Result<()> {
let sig_bytes = general_purpose::STANDARD
.decode(signature_b64)
.context("decoding base64 signature")?;
let signature = Signature::try_from(sig_bytes.as_slice()).context("parsing signature bytes")?;
let pk_array: [u8; 32] = public_key_bytes.try_into().map_err(|_| anyhow::anyhow!("invalid public key length"))?;
let pubkey = VerifyingKey::from_bytes(&pk_array).context("parsing public key bytes")?;
pubkey
.verify(manifest_bytes, &signature)
.context("signature verification failed")?;
Ok(())
}
pub fn canonical_bytes_from_value(v: &Value) -> anyhow::Result<Vec<u8>> {
let mut value = v.clone();
sort_json_value_keys(&mut value);
let s = serde_json::to_vec(&value)?;
Ok(s)
}
fn sort_json_value_keys(v: &mut Value) {
match v {
Value::Object(map) => {
let mut entries: Vec<_> = map.iter().map(|(k, v)| (k.clone(), v.clone())).collect();
entries.sort_by_key(|(k, _)| k.clone());
map.clear();
for (k, mut val) in entries {
sort_json_value_keys(&mut val);
map.insert(k, val);
}
}
Value::Array(a) => {
for item in a.iter_mut() {
sort_json_value_keys(item);
}
}
_ => {}
}
}
#[cfg(test)]
mod tests {
use super::*;
use ed25519_dalek::{Signer, SigningKey};
#[test]
fn verify_signed_manifest_happy_path() {
let seed = [0x12u8; 32];
let sk = SigningKey::from_bytes(&seed);
let pubkey = sk.verifying_key();
let manifest = b"{\"ops\":[],\"version\":1}";
let sig = sk.sign(manifest);
let signed_json = serde_json::json!({
"signer": "test-signer",
"signature": general_purpose::STANDARD.encode(sig.to_bytes()),
})
.to_string();
let trust = InMemoryTrustStore::new();
trust.insert("test-signer", pubkey.to_bytes().to_vec());
verify_manifest(&signed_json, manifest, &trust as &dyn TrustStore).unwrap();
}
#[test]
fn verify_rejects_tampered_manifest() {
let seed = [0x34u8; 32];
let sk = SigningKey::from_bytes(&seed);
let pubkey = sk.verifying_key();
let manifest = b"{\"ops\":[],\"version\":1}";
let sig = sk.sign(manifest);
let signed_json = serde_json::json!({
"signer": "test-signer",
"signature": general_purpose::STANDARD.encode(sig.to_bytes()),
})
.to_string();
let trust = InMemoryTrustStore::new();
trust.insert("test-signer", pubkey.to_bytes().to_vec());
let tampered = b"{\"ops\":[],\"version\":2}";
let res = verify_manifest(&signed_json, tampered, &trust as &dyn TrustStore);
assert!(res.is_err());
}
#[test]
fn sign_and_verify_manifest_roundtrip() {
let seed = [0x12u8; 32];
let sk = SigningKey::from_bytes(&seed);
let pubkey = sk.verifying_key();
let manifest = serde_json::json!({
"fingerprint_version": 1,
"ops": [ { "name": "relu", "impl": "v1" } ],
"dtype": "f32",
"composer_version": "wgsl-composer-0.1",
"artifact_kind": "generic",
});
let bytes = canonical_bytes_from_value(&manifest).expect("canonicalize");
let sig = sk.sign(&bytes);
let sig_b64 = general_purpose::STANDARD.encode(sig.to_bytes());
let pubkey_bytes = pubkey.to_bytes();
verify_manifest_signature(&bytes, &sig_b64, &pubkey_bytes).expect("verify ok");
let signed_json = serde_json::json!({
"signer": "test-signer",
"signature": sig_b64,
})
.to_string();
let trust = InMemoryTrustStore::new();
trust.insert("test-signer", pubkey_bytes.to_vec());
verify_manifest(&signed_json, &bytes, &trust as &dyn TrustStore)
.expect("verify via truststore");
}
}