1use std::collections::HashMap;
2use ed25519_dalek::{VerifyingKey, Verifier as DalekVerifier, Signature as DalekSignature};
3
4use crate::attestation::{
5 pae,
6 artifact_id_from_pae, digest_from_pae, ArtifactId,
7 Ed25519Signer, Signer,
8 Envelope,
9};
10
11#[derive(Debug)]
13pub struct VerifyResult {
14 pub artifact_id: ArtifactId,
18
19 pub digest: String,
21
22 pub verified_key_ids: Vec<String>,
24
25 pub payload_type: String,
27}
28
29#[derive(Debug)]
31pub enum VerifyError {
32 PayloadDecode(String),
34 UnknownKey(String),
36 InvalidSignature(String),
38 NoValidSignature,
40 MalformedSignature(String),
42}
43
44impl std::fmt::Display for VerifyError {
45 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
46 match self {
47 Self::PayloadDecode(e) => write!(f, "payload decode: {}", e),
48 Self::UnknownKey(id) => write!(f, "unknown key: {}", id),
49 Self::InvalidSignature(id) => write!(f, "invalid signature for key: {}", id),
50 Self::NoValidSignature => write!(f, "no valid signature from any trusted key"),
51 Self::MalformedSignature(e) => write!(f, "malformed signature bytes: {}", e),
52 }
53 }
54}
55
56impl std::error::Error for VerifyError {}
57
58#[derive(Clone)]
63pub struct Verifier {
64 keys: HashMap<String, VerifyingKey>,
66}
67
68impl Verifier {
69 pub fn new(keys: HashMap<String, VerifyingKey>) -> Self {
71 Self { keys }
72 }
73
74 pub fn from_signer(signer: &Ed25519Signer) -> Self {
77 let mut keys = HashMap::new();
78 keys.insert(signer.key_id().to_string(), signer.verifying_key());
79 Self { keys }
80 }
81
82 pub fn add_key(&mut self, key_id: impl Into<String>, pub_key: VerifyingKey) {
84 self.keys.insert(key_id.into(), pub_key);
85 }
86
87 pub fn verify(&self, envelope: &Envelope) -> Result<VerifyResult, VerifyError> {
96 if envelope.signatures.is_empty() {
101 return Err(VerifyError::NoValidSignature);
102 }
103
104 let pae_bytes = self.reconstruct_pae(envelope)?;
105 let mut verified = Vec::new();
106
107 for sig in &envelope.signatures {
108 let pub_key = self.keys.get(&sig.keyid)
109 .ok_or_else(|| VerifyError::UnknownKey(sig.keyid.clone()))?;
110
111 let raw_sig = self.decode_sig(sig)?;
112 self.verify_sig(pub_key, &pae_bytes, &raw_sig, &sig.keyid)?;
113 verified.push(sig.keyid.clone());
114 }
115
116 Ok(self.build_result(pae_bytes, verified, &envelope.payload_type))
117 }
118
119 pub fn verify_any(&self, envelope: &Envelope) -> Result<VerifyResult, VerifyError> {
125 let pae_bytes = self.reconstruct_pae(envelope)?;
126 let mut verified = Vec::new();
127
128 for sig in &envelope.signatures {
129 let pub_key = match self.keys.get(&sig.keyid) {
130 Some(k) => k,
131 None => continue, };
133 let raw_sig = match self.decode_sig(sig) {
134 Ok(b) => b,
135 Err(_) => continue, };
137 if self.verify_sig(pub_key, &pae_bytes, &raw_sig, &sig.keyid).is_ok() {
138 verified.push(sig.keyid.clone());
139 }
140 }
141
142 if verified.is_empty() {
143 return Err(VerifyError::NoValidSignature);
144 }
145
146 Ok(self.build_result(pae_bytes, verified, &envelope.payload_type))
147 }
148
149 fn reconstruct_pae(&self, envelope: &Envelope) -> Result<Vec<u8>, VerifyError> {
152 let payload_bytes = base64::Engine::decode(
153 &base64::engine::general_purpose::URL_SAFE_NO_PAD,
154 &envelope.payload,
155 ).map_err(|e| VerifyError::PayloadDecode(e.to_string()))?;
156
157 Ok(pae(&envelope.payload_type, &payload_bytes))
158 }
159
160 fn decode_sig(&self, sig: &crate::attestation::Signature) -> Result<Vec<u8>, VerifyError> {
161 base64::Engine::decode(
162 &base64::engine::general_purpose::URL_SAFE_NO_PAD,
163 &sig.sig,
164 ).map_err(|e| VerifyError::MalformedSignature(e.to_string()))
165 }
166
167 fn verify_sig(
168 &self,
169 pub_key: &VerifyingKey,
170 pae: &[u8],
171 raw_sig: &[u8],
172 key_id: &str,
173 ) -> Result<(), VerifyError> {
174 let sig_bytes: [u8; 64] = raw_sig.try_into()
175 .map_err(|_| VerifyError::MalformedSignature(
176 format!("signature for {} is {} bytes, expected 64", key_id, raw_sig.len())
177 ))?;
178
179 let dalek_sig = DalekSignature::from_bytes(&sig_bytes);
180
181 pub_key.verify_strict(pae, &dalek_sig)
182 .map_err(|_| VerifyError::InvalidSignature(key_id.to_string()))
183 }
184
185 fn build_result(
186 &self,
187 pae_bytes: Vec<u8>,
188 verified: Vec<String>,
189 payload_type: &str,
190 ) -> VerifyResult {
191 VerifyResult {
192 artifact_id: artifact_id_from_pae(&pae_bytes),
193 digest: digest_from_pae(&pae_bytes),
194 verified_key_ids: verified,
195 payload_type: payload_type.to_string(),
196 }
197 }
198}
199
200pub fn verify_with_key(
202 envelope: &Envelope,
203 key_id: &str,
204 pub_key: VerifyingKey,
205) -> Result<VerifyResult, VerifyError> {
206 let mut keys = HashMap::new();
207 keys.insert(key_id.to_string(), pub_key);
208 let v = Verifier::new(keys);
209 v.verify_any(envelope)
210}
211
212#[cfg(test)]
213mod tests {
214 use super::*;
215 use crate::attestation::{sign, Ed25519Signer};
216 use base64::{engine::general_purpose::URL_SAFE_NO_PAD, Engine};
217 use serde::{Deserialize, Serialize};
218
219 #[derive(Debug, Serialize, Deserialize)]
220 struct TestStmt { actor: String, action: String }
221
222 const PT: &str = "application/vnd.treeship.action.v1+json";
223
224 fn stmt() -> TestStmt {
225 TestStmt { actor: "agent://researcher".into(), action: "tool.call".into() }
226 }
227
228 fn make_signer() -> Ed25519Signer {
229 Ed25519Signer::generate("key_test_01").unwrap()
230 }
231
232 #[test]
235 fn verify_roundtrip() {
236 let signer = make_signer();
237 let verifier = Verifier::from_signer(&signer);
238 let signed = sign(PT, &stmt(), &signer).unwrap();
239 let result = verifier.verify(&signed.envelope).unwrap();
240
241 assert_eq!(result.artifact_id, signed.artifact_id);
242 assert_eq!(result.digest, signed.digest);
243 assert_eq!(result.verified_key_ids, vec!["key_test_01"]);
244 assert_eq!(result.payload_type, PT);
245 }
246
247 #[test]
248 fn verify_any_roundtrip() {
249 let signer = make_signer();
250 let verifier = Verifier::from_signer(&signer);
251 let signed = sign(PT, &stmt(), &signer).unwrap();
252 verifier.verify_any(&signed.envelope).unwrap();
253 }
254
255 #[test]
258 fn tampered_payload_fails() {
259 let signer = make_signer();
260 let verifier = Verifier::from_signer(&signer);
261 let signed = sign(PT, &stmt(), &signer).unwrap();
262
263 let malicious = TestStmt { actor: "agent://attacker".into(), action: "steal".into() };
267 let malicious_bytes = serde_json::to_vec(&malicious).unwrap();
268
269 let mut tampered = signed.envelope.clone();
270 tampered.payload = URL_SAFE_NO_PAD.encode(malicious_bytes);
271
272 let err = verifier.verify(&tampered).unwrap_err();
273 assert!(
274 matches!(err, VerifyError::InvalidSignature(_)),
275 "Expected InvalidSignature, got: {}", err
276 );
277 }
278
279 #[test]
280 fn tampered_payload_type_fails() {
281 let signer = make_signer();
282 let verifier = Verifier::from_signer(&signer);
283 let signed = sign("application/vnd.treeship.action.v1+json", &stmt(), &signer).unwrap();
284
285 let mut tampered = signed.envelope.clone();
288 tampered.payload_type = "application/vnd.treeship.approval.v1+json".into();
289
290 assert!(
291 verifier.verify(&tampered).is_err(),
292 "verify must fail when payloadType is tampered"
293 );
294 }
295
296 #[test]
299 fn wrong_key_fails() {
300 let signer = make_signer();
301 let wrong = Ed25519Signer::generate("key_test_01").unwrap();
304 let verifier = Verifier::from_signer(&wrong);
305
306 let signed = sign(PT, &stmt(), &signer).unwrap();
307 assert!(
308 verifier.verify(&signed.envelope).is_err(),
309 "verify with wrong public key must fail"
310 );
311 }
312
313 #[test]
314 fn unknown_key_fails() {
315 let signer = make_signer();
316 let verifier = Verifier::new(HashMap::new()); let signed = sign(PT, &stmt(), &signer).unwrap();
319 assert!(
320 verifier.verify(&signed.envelope).is_err(),
321 "verify with no trusted keys must fail"
322 );
323 }
324
325 #[test]
326 fn verify_any_skips_unknown_keys() {
327 let signer = make_signer();
328 let verifier = Verifier::from_signer(&signer);
330
331 let signed = sign(PT, &stmt(), &signer).unwrap();
333 let result = verifier.verify_any(&signed.envelope).unwrap();
334 assert_eq!(result.verified_key_ids.len(), 1);
335 }
336
337 #[test]
338 fn verify_rejects_empty_signature_envelope() {
339 let signer = make_signer();
344 let verifier = Verifier::from_signer(&signer);
345 let signed = sign(PT, &stmt(), &signer).unwrap();
346
347 let mut unsigned = signed.envelope.clone();
349 unsigned.signatures.clear();
350
351 let err = verifier.verify(&unsigned).unwrap_err();
352 assert!(
353 matches!(err, VerifyError::NoValidSignature),
354 "expected NoValidSignature for zero-signature envelope, got: {err}"
355 );
356
357 assert!(matches!(
360 verifier.verify_any(&unsigned).unwrap_err(),
361 VerifyError::NoValidSignature
362 ));
363 }
364
365 #[test]
366 fn verify_any_all_unknown_fails() {
367 let signer = make_signer();
368 let verifier = Verifier::new(HashMap::new());
369 let signed = sign(PT, &stmt(), &signer).unwrap();
370 assert!(matches!(
371 verifier.verify_any(&signed.envelope).unwrap_err(),
372 VerifyError::NoValidSignature
373 ));
374 }
375
376 #[test]
379 fn artifact_id_matches_sign() {
380 let signer = make_signer();
381 let verifier = Verifier::from_signer(&signer);
382 let signed = sign(PT, &stmt(), &signer).unwrap();
383 let verified = verifier.verify(&signed.envelope).unwrap();
384
385 assert_eq!(
388 signed.artifact_id, verified.artifact_id,
389 "ID from sign and verify must match"
390 );
391 }
392
393 #[test]
396 fn multi_key_verifier() {
397 let s1 = Ed25519Signer::generate("key_1").unwrap();
398 let s2 = Ed25519Signer::generate("key_2").unwrap();
399
400 let mut verifier = Verifier::from_signer(&s1);
401 verifier.add_key("key_2", s2.verifying_key());
402
403 let signed = sign(PT, &stmt(), &s1).unwrap();
405 let result = verifier.verify(&signed.envelope).unwrap();
406 assert_eq!(result.verified_key_ids, vec!["key_1"]);
407
408 let signed2 = sign(PT, &stmt(), &s2).unwrap();
410 let result2 = verifier.verify(&signed2.envelope).unwrap();
411 assert_eq!(result2.verified_key_ids, vec!["key_2"]);
412 }
413
414 #[test]
417 fn json_marshal_unmarshal() {
418 let signer = make_signer();
419 let verifier = Verifier::from_signer(&signer);
420 let signed = sign(PT, &stmt(), &signer).unwrap();
421
422 let json = signed.envelope.to_json().unwrap();
423 let restored = Envelope::from_json(&json).unwrap();
424
425 let result = verifier.verify(&restored).unwrap();
426 assert_eq!(result.artifact_id, signed.artifact_id);
427 }
428
429 #[test]
430 fn verifier_uses_strict_ed25519_rejecting_small_order_keys() {
431 use ed25519_dalek::VerifyingKey;
438 let small_order = [
442 0x00u8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
443 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
444 ];
445 if let Ok(vk) = VerifyingKey::from_bytes(&small_order) {
449 let zero_sig = ed25519_dalek::Signature::from_bytes(&[0u8; 64]);
450 assert!(
451 vk.verify_strict(b"anything", &zero_sig).is_err(),
452 "strict verification must reject a small-order key"
453 );
454 }
455 let signer = make_signer();
458 let env = sign(PT, &stmt(), &signer).unwrap().envelope;
459 let mut v = Verifier::new(std::collections::HashMap::new());
460 v.add_key(signer.key_id().to_string(), signer.verifying_key());
461 assert!(v.verify_any(&env).is_ok(), "a real signature must still verify strictly");
462 }
463
464}