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