1pub mod access;
5pub mod c2pa_manifest;
6pub mod cawg;
7pub mod cose;
8pub mod did;
9pub mod jumbf;
10pub mod keri;
11pub mod scitt;
12pub mod sigstore;
13pub mod trust;
14pub mod types;
15pub mod vc;
16
17use anyhow::Result;
18use ed25519_dalek::SigningKey;
19use parking_lot::RwLock;
20use sha2::{Digest, Sha256};
21use std::io::{BufRead, Write};
22use std::path::{Path, PathBuf};
23use trust::TrustStore;
24
25use types::{
26 DeletionRecord, ProvenanceRecord, StoreManifest, VerificationDetail, VerificationResult,
27};
28
29pub struct TripleProvenanceParams<'a> {
30 pub fact_id: &'a str,
31 pub content: &'a [u8],
32 pub dimensions: u32,
33 pub subject: &'a str,
34 pub relation: &'a str,
35 pub object: &'a str,
36 pub source_uri: Option<&'a str>,
37}
38
39#[derive(serde::Serialize, serde::Deserialize)]
40struct LogEntry {
41 prev_hash: String,
42 fact_id: String,
43 record: ProvenanceRecord,
44}
45
46pub struct ProvenanceManager {
47 signing_key: SigningKey,
48 issuer_did: String,
49 records: RwLock<fxhash::FxHashMap<String, ProvenanceRecord>>,
50 chain_head: RwLock<[u8; 32]>,
51 logical_clock: std::sync::atomic::AtomicU64,
52 log_path: Option<PathBuf>,
53 revocation_bitstring: RwLock<Vec<u8>>,
54 kel: RwLock<keri::KeyEventLog>,
55 #[cfg(feature = "provenance-scitt")]
56 scitt_endpoint: Option<String>,
57}
58
59impl ProvenanceManager {
60 pub fn new(key_path: &Path, storage_path: Option<&Path>) -> Result<Self> {
61 let signing_key = super::security::SigningManager::new(key_path)?.into_signing_key();
62 let issuer_did = did::did_key_from_ed25519(&signing_key.verifying_key().to_bytes());
63 let log_path = storage_path.map(|p| p.join("provenance_log.jsonl"));
64 let (records, chain_head) = Self::load_log(log_path.as_deref());
65 let clock_init = records
66 .values()
67 .map(|r| r.sequence)
68 .max()
69 .map_or(0, |max| max + 1);
70 let mut kel = match storage_path {
71 Some(p) => keri::KeyEventLog::with_path(&p.join("kel.json")).unwrap_or_default(),
72 None => keri::KeyEventLog::new(),
73 };
74 if kel.events().is_empty() {
75 if let Err(e) = kel.inception(&signing_key, None) {
76 tracing::warn!("KEL inception failed: {e}");
77 }
78 }
79
80 Ok(Self {
81 signing_key,
82 issuer_did,
83 records: RwLock::new(records),
84 chain_head: RwLock::new(chain_head),
85 logical_clock: std::sync::atomic::AtomicU64::new(clock_init),
86 log_path: log_path.clone(),
87 revocation_bitstring: RwLock::new(Self::load_revocation_bitstring(log_path.as_deref())),
88 kel: RwLock::new(kel),
89 #[cfg(feature = "provenance-scitt")]
90 scitt_endpoint: None,
91 })
92 }
93
94 pub fn from_signing_key(signing_key: SigningKey) -> Self {
95 let issuer_did = did::did_key_from_ed25519(&signing_key.verifying_key().to_bytes());
96 let mut kel = keri::KeyEventLog::new();
97 let _ = kel.inception(&signing_key, None);
98 Self {
99 signing_key,
100 issuer_did,
101 records: RwLock::new(fxhash::FxHashMap::default()),
102 chain_head: RwLock::new([0u8; 32]),
103 logical_clock: std::sync::atomic::AtomicU64::new(0),
104 log_path: None,
105 revocation_bitstring: RwLock::new(Vec::new()),
106 kel: RwLock::new(kel),
107 #[cfg(feature = "provenance-scitt")]
108 scitt_endpoint: None,
109 }
110 }
111
112 fn next_sequence(&self) -> u64 {
113 self.logical_clock
114 .fetch_add(1, std::sync::atomic::Ordering::SeqCst)
115 }
116
117 #[cfg(feature = "provenance-scitt")]
118 pub fn with_scitt_endpoint(mut self, endpoint: String) -> Self {
119 self.scitt_endpoint = Some(endpoint);
120 self
121 }
122
123 pub fn issuer_did(&self) -> &str {
124 &self.issuer_did
125 }
126
127 pub fn store_record(&self, fact_id: &str, record: ProvenanceRecord) {
128 if let Some(ref path) = self.log_path {
129 let prev_hash = hex_encode(&*self.chain_head.read());
130 let entry = LogEntry {
131 prev_hash,
132 fact_id: fact_id.to_string(),
133 record: record.clone(),
134 };
135 if let Ok(line) = serde_json::to_string(&entry) {
136 let new_hash: [u8; 32] = Sha256::digest(line.as_bytes()).into();
137 let mut buf = line;
138 buf.push('\n');
139 if let Err(e) = std::fs::OpenOptions::new()
140 .create(true)
141 .append(true)
142 .open(path)
143 .and_then(|mut f| f.write_all(buf.as_bytes()))
144 {
145 tracing::warn!("provenance log write failed: {e}");
146 }
147 *self.chain_head.write() = new_hash;
148 self.write_signed_head(&new_hash, path);
149 }
150 }
151 self.records.write().insert(fact_id.to_string(), record);
152 }
153
154 fn write_signed_head(&self, head: &[u8; 32], log_path: &Path) {
155 let head_path = log_path.with_extension("head");
156 match cose::sign_payload(&self.signing_key, head) {
157 Ok(signed) => {
158 if let Err(e) = std::fs::write(head_path, signed) {
159 tracing::warn!("signed head write failed: {e}");
160 }
161 }
162 Err(e) => tracing::warn!("signed head signing failed: {e}"),
163 }
164 }
165
166 pub fn get_record(&self, fact_id: &str) -> Option<ProvenanceRecord> {
167 self.records.read().get(fact_id).cloned()
168 }
169
170 pub fn record_count(&self) -> usize {
171 self.records.read().len()
172 }
173
174 pub fn record_deletion(&self, fact_id: &str, reason: Option<&str>) -> Result<DeletionRecord> {
175 let timestamp_ms = std::time::SystemTime::now()
176 .duration_since(std::time::UNIX_EPOCH)
177 .unwrap_or_default()
178 .as_millis() as u64;
179
180 let mut claim_payload = Vec::new();
181 let cbor_claim = ciborium::Value::Map(vec![
182 (
183 ciborium::Value::Text("action".to_string()),
184 ciborium::Value::Text("delete".to_string()),
185 ),
186 (
187 ciborium::Value::Text("fact_id".to_string()),
188 ciborium::Value::Text(fact_id.to_string()),
189 ),
190 (
191 ciborium::Value::Text("timestamp_ms".to_string()),
192 ciborium::Value::Integer(timestamp_ms.into()),
193 ),
194 ]);
195 ciborium::into_writer(&cbor_claim, &mut claim_payload)
196 .map_err(|e| anyhow::anyhow!("CBOR encoding failed: {e}"))?;
197
198 let cose_envelope = cose::sign_payload(&self.signing_key, &claim_payload)?;
199
200 let record = DeletionRecord {
201 fact_id: fact_id.to_string(),
202 deleted_by: Some(self.issuer_did.clone()),
203 timestamp_ms,
204 reason: reason.map(String::from),
205 cose_envelope: Some(cose_envelope),
206 };
207
208 self.records.write().remove(fact_id);
209
210 if let Some(ref path) = self.log_path {
211 let prev_hash = hex_encode(&*self.chain_head.read());
212 let entry_json = serde_json::json!({
213 "prev_hash": prev_hash,
214 "action": "delete",
215 "fact_id": fact_id,
216 "deletion": record,
217 });
218 if let Ok(line) = serde_json::to_string(&entry_json) {
219 let new_hash: [u8; 32] = Sha256::digest(line.as_bytes()).into();
220 let mut buf = line;
221 buf.push('\n');
222 if let Err(e) = std::fs::OpenOptions::new()
223 .create(true)
224 .append(true)
225 .open(path)
226 .and_then(|mut f| f.write_all(buf.as_bytes()))
227 {
228 tracing::warn!("deletion log write failed: {e}");
229 }
230 *self.chain_head.write() = new_hash;
231 self.write_signed_head(&new_hash, path);
232 }
233 }
234
235 Ok(record)
236 }
237
238 pub fn rotate_key(&mut self, new_key_path: &Path) -> Result<String> {
239 let old_did = self.issuer_did.clone();
240 let new_signing_key =
241 super::security::SigningManager::new(new_key_path)?.into_signing_key();
242 let new_did = did::did_key_from_ed25519(&new_signing_key.verifying_key().to_bytes());
243
244 let mut rotation_payload = Vec::new();
245 let cbor = ciborium::Value::Map(vec![
246 (
247 ciborium::Value::Text("action".to_string()),
248 ciborium::Value::Text("key_rotation".to_string()),
249 ),
250 (
251 ciborium::Value::Text("old_did".to_string()),
252 ciborium::Value::Text(old_did.clone()),
253 ),
254 (
255 ciborium::Value::Text("new_did".to_string()),
256 ciborium::Value::Text(new_did.clone()),
257 ),
258 ]);
259 ciborium::into_writer(&cbor, &mut rotation_payload)
260 .map_err(|e| anyhow::anyhow!("CBOR encoding failed: {e}"))?;
261 let rotation_envelope = cose::sign_payload(&self.signing_key, &rotation_payload)?;
262
263 if let Some(ref path) = self.log_path {
264 let prev_hash = hex_encode(&*self.chain_head.read());
265 let entry_json = serde_json::json!({
266 "prev_hash": prev_hash,
267 "action": "key_rotation",
268 "old_did": old_did,
269 "new_did": new_did,
270 "cose_envelope": rotation_envelope,
271 });
272 if let Ok(line) = serde_json::to_string(&entry_json) {
273 let new_hash: [u8; 32] = Sha256::digest(line.as_bytes()).into();
274 let mut buf = line;
275 buf.push('\n');
276 if let Err(e) = std::fs::OpenOptions::new()
277 .create(true)
278 .append(true)
279 .open(path)
280 .and_then(|mut f| f.write_all(buf.as_bytes()))
281 {
282 tracing::warn!("key rotation log write failed: {e}");
283 }
284 *self.chain_head.write() = new_hash;
285 self.write_signed_head(&new_hash, path);
286 }
287 }
288
289 self.signing_key = new_signing_key;
290 self.issuer_did = new_did.clone();
291 Ok(new_did)
292 }
293
294 pub fn verify_log_integrity(&self) -> Result<bool> {
295 let Some(ref path) = self.log_path else {
296 return Ok(true);
297 };
298 let Ok(file) = std::fs::File::open(path) else {
299 return Ok(true);
300 };
301 let mut expected_prev = [0u8; 32];
302 let mut computed_head = [0u8; 32];
303 let mut has_entries = false;
304 for line in std::io::BufReader::new(file).lines() {
305 let line = line.map_err(|e| anyhow::anyhow!("log read error: {e}"))?;
306 let obj: serde_json::Value =
307 serde_json::from_str(&line).map_err(|e| anyhow::anyhow!("log parse error: {e}"))?;
308 let prev = obj
309 .get("prev_hash")
310 .and_then(|v| v.as_str())
311 .ok_or_else(|| anyhow::anyhow!("missing prev_hash"))?;
312 let declared = hex_decode(prev)?;
313 if declared != expected_prev {
314 return Ok(false);
315 }
316 computed_head = Sha256::digest(line.as_bytes()).into();
317 expected_prev = computed_head;
318 has_entries = true;
319 }
320 if has_entries {
321 let head_path = path.with_extension("head");
322 if let Ok(signed_bytes) = std::fs::read(&head_path) {
323 let verifying_key = self.signing_key.verifying_key();
324 match cose::verify_and_extract(&verifying_key, &signed_bytes) {
325 Ok(stored_head) => {
326 if stored_head.as_slice() != computed_head {
327 return Ok(false);
328 }
329 }
330 Err(_) => return Ok(false),
331 }
332 }
333 }
334 Ok(true)
335 }
336
337 fn load_log(path: Option<&Path>) -> (fxhash::FxHashMap<String, ProvenanceRecord>, [u8; 32]) {
338 let mut map = fxhash::FxHashMap::default();
339 let mut head = [0u8; 32];
340 let Some(path) = path else {
341 return (map, head);
342 };
343 let Ok(file) = std::fs::File::open(path) else {
344 return (map, head);
345 };
346 for line in std::io::BufReader::new(file).lines() {
347 let Ok(line) = line else { continue };
348 head = Sha256::digest(line.as_bytes()).into();
349 if let Ok(entry) = serde_json::from_str::<LogEntry>(&line) {
350 map.insert(entry.fact_id, entry.record);
351 } else if let Ok(obj) = serde_json::from_str::<serde_json::Value>(&line) {
352 if obj.get("action").and_then(|a| a.as_str()) == Some("delete") {
353 if let Some(fid) = obj.get("fact_id").and_then(|f| f.as_str()) {
354 map.remove(fid);
355 }
356 }
357 }
358 }
359 (map, head)
360 }
361
362 fn load_revocation_bitstring(log_path: Option<&Path>) -> Vec<u8> {
363 let Some(path) = log_path else {
364 return Vec::new();
365 };
366 let bitstring_path = path.with_extension("statuslist");
367 std::fs::read(bitstring_path).unwrap_or_default()
368 }
369
370 pub fn revoke_credential(&self, status_index: u64) -> Result<()> {
371 let byte_idx = (status_index / 8) as usize;
372 let bit_idx = (status_index % 8) as u8;
373 {
374 let mut bits = self.revocation_bitstring.write();
375 if bits.len() <= byte_idx {
376 bits.resize(byte_idx + 1, 0);
377 }
378 bits[byte_idx] |= 1 << bit_idx;
379 }
380 if let Some(ref path) = self.log_path {
381 let bitstring_path = path.with_extension("statuslist");
382 let bits = self.revocation_bitstring.read();
383 std::fs::write(bitstring_path, &*bits)
384 .map_err(|e| anyhow::anyhow!("failed to persist revocation bitstring: {e}"))?;
385 }
386 Ok(())
387 }
388
389 pub fn is_revoked(&self, status_index: u64) -> bool {
390 let byte_idx = (status_index / 8) as usize;
391 let bit_idx = (status_index % 8) as u8;
392 let bits = self.revocation_bitstring.read();
393 byte_idx < bits.len() && (bits[byte_idx] & (1 << bit_idx)) != 0
394 }
395
396 pub fn create_fact_provenance(
398 &self,
399 fact_id: &str,
400 content: &[u8],
401 source_uri: Option<&str>,
402 ) -> Result<ProvenanceRecord> {
403 let content_hash: [u8; 32] = Sha256::digest(content).into();
404 let mut record = ProvenanceRecord::now().with_source(source_uri.unwrap_or(""), content);
405 record.issuer_did = Some(self.issuer_did.clone());
406 record.sequence = self.next_sequence();
407
408 let cose_envelope = cose::sign_fact_claim(
409 &self.signing_key,
410 fact_id,
411 &content_hash,
412 source_uri,
413 record.timestamp_ms,
414 )?;
415 record.cose_envelope = Some(cose_envelope);
416
417 let credential = vc::create_fact_credential(
418 &self.issuer_did,
419 fact_id,
420 &content_hash,
421 0, source_uri,
423 None,
424 record.sequence,
425 );
426 let signed_vc = vc::sign_credential(&self.signing_key, credential)?;
427 record.vc_json = Some(serde_json::to_string(&signed_vc)?);
428
429 #[cfg(feature = "provenance-scitt")]
430 if let Some(ref endpoint) = self.scitt_endpoint {
431 let claim = scitt::FactClaim {
432 fact_id: fact_id.to_string(),
433 content_hash: content_hash.to_vec(),
434 timestamp_ms: record.timestamp_ms,
435 source_uri: source_uri.map(String::from),
436 store_id: None,
437 };
438 let statement = scitt::create_signed_statement(&self.signing_key, &claim)?;
439 match scitt::submit_statement(endpoint, &statement) {
440 Ok(receipt) => record.scitt_receipt = Some(receipt.receipt_bytes),
441 Err(e) => tracing::warn!("SCITT submission failed: {e}"),
442 }
443 }
444
445 self.store_record(fact_id, record.clone());
446 Ok(record)
447 }
448
449 pub fn create_triple_provenance(
451 &self,
452 params: &TripleProvenanceParams<'_>,
453 ) -> Result<ProvenanceRecord> {
454 let content_hash: [u8; 32] = Sha256::digest(params.content).into();
455 let mut record = ProvenanceRecord::now();
456 record.source_uri = params.source_uri.map(String::from);
457 record.source_hash = Some(content_hash);
458 record.issuer_did = Some(self.issuer_did.clone());
459 record.sequence = self.next_sequence();
460
461 let cose_envelope = cose::sign_fact_claim(
462 &self.signing_key,
463 params.fact_id,
464 &content_hash,
465 params.source_uri,
466 record.timestamp_ms,
467 )?;
468 record.cose_envelope = Some(cose_envelope);
469
470 let credential = vc::create_fact_credential(
471 &self.issuer_did,
472 params.fact_id,
473 &content_hash,
474 params.dimensions,
475 params.source_uri,
476 Some((params.subject, params.relation, params.object)),
477 record.sequence,
478 );
479 let signed_vc = vc::sign_credential(&self.signing_key, credential)?;
480 record.vc_json = Some(serde_json::to_string(&signed_vc)?);
481
482 self.store_record(params.fact_id, record.clone());
483 Ok(record)
484 }
485
486 pub fn create_batch_provenance(
487 &self,
488 items: &[(&str, &[u8], Option<&str>)],
489 ) -> Result<Vec<ProvenanceRecord>> {
490 if items.is_empty() {
491 return Ok(Vec::new());
492 }
493 let leaf_hashes: Vec<[u8; 32]> = items
494 .iter()
495 .map(|(_, content, _)| Sha256::digest(content).into())
496 .collect();
497 let merkle_root = Self::merkle_root(&leaf_hashes);
498 let root_envelope = cose::sign_payload(&self.signing_key, &merkle_root)?;
499
500 let mut records = Vec::with_capacity(items.len());
501 for (i, (fact_id, content, source_uri)) in items.iter().enumerate() {
502 let content_hash = leaf_hashes[i];
503 let proof = Self::merkle_proof(&leaf_hashes, i);
504 let mut record = ProvenanceRecord::now().with_source(source_uri.unwrap_or(""), content);
505 record.issuer_did = Some(self.issuer_did.clone());
506 record.sequence = self.next_sequence();
507 record.cose_envelope = Some(root_envelope.clone());
508 record.merkle_proof = Some(proof);
509
510 let credential = vc::create_fact_credential(
511 &self.issuer_did,
512 fact_id,
513 &content_hash,
514 0,
515 *source_uri,
516 None,
517 record.sequence,
518 );
519 let signed_vc = vc::sign_credential(&self.signing_key, credential)?;
520 record.vc_json = Some(serde_json::to_string(&signed_vc)?);
521
522 self.store_record(fact_id, record.clone());
523 records.push(record);
524 }
525 Ok(records)
526 }
527
528 fn merkle_root(leaves: &[[u8; 32]]) -> [u8; 32] {
529 if leaves.is_empty() {
530 return [0u8; 32];
531 }
532 let mut level: Vec<[u8; 32]> = leaves.to_vec();
533 while level.len() > 1 {
534 let mut next = Vec::with_capacity(level.len().div_ceil(2));
535 for pair in level.chunks(2) {
536 if pair.len() == 2 {
537 let mut hasher = Sha256::new();
538 hasher.update(pair[0]);
539 hasher.update(pair[1]);
540 next.push(hasher.finalize().into());
541 } else {
542 next.push(pair[0]);
543 }
544 }
545 level = next;
546 }
547 level[0]
548 }
549
550 fn merkle_proof(leaves: &[[u8; 32]], index: usize) -> Vec<(bool, [u8; 32])> {
551 let mut proof = Vec::new();
552 let mut level: Vec<[u8; 32]> = leaves.to_vec();
553 let mut idx = index;
554 while level.len() > 1 {
555 let sibling_idx = if idx.is_multiple_of(2) {
556 idx + 1
557 } else {
558 idx - 1
559 };
560 if sibling_idx < level.len() {
561 proof.push((idx % 2 == 1, level[sibling_idx]));
562 }
563 let mut next = Vec::with_capacity(level.len().div_ceil(2));
564 for pair in level.chunks(2) {
565 if pair.len() == 2 {
566 let mut hasher = Sha256::new();
567 hasher.update(pair[0]);
568 hasher.update(pair[1]);
569 next.push(hasher.finalize().into());
570 } else {
571 next.push(pair[0]);
572 }
573 }
574 level = next;
575 idx /= 2;
576 }
577 proof
578 }
579
580 pub fn create_store_manifest(
582 &self,
583 store_id: &str,
584 store_data: &[u8],
585 fact_count: usize,
586 dimensions: u32,
587 title: Option<&str>,
588 ) -> Result<StoreManifest> {
589 let store_hash: [u8; 32] = Sha256::digest(store_data).into();
590
591 let params = c2pa_manifest::ManifestParams {
592 store_id,
593 fact_count,
594 dimensions,
595 store_hash: &store_hash,
596 title,
597 ingredients: Vec::new(),
598 };
599
600 let manifest = c2pa_manifest::create_manifest(&self.signing_key, ¶ms)?;
601 let cose_envelope = c2pa_manifest::sign_manifest(&self.signing_key, &manifest)?;
602
603 let manifest_label = format!("urn:hms:manifest:{store_id}");
604 let claim_json = serde_json::to_value(&manifest)?;
605 let assertions: Vec<(&str, serde_json::Value)> = manifest
606 .assertions
607 .iter()
608 .map(|a| (a.label.as_str(), a.data.clone()))
609 .collect();
610 let jumbf_box = jumbf::build_c2pa_manifest_box(
611 &manifest_label,
612 claim_json,
613 cose_envelope.clone(),
614 assertions,
615 );
616 let jumbf_bytes = jumbf_box.encode()?;
617
618 Ok(StoreManifest {
619 manifest_id: manifest_label,
620 issuer_did: Some(self.issuer_did.clone()),
621 created_ms: std::time::SystemTime::now()
622 .duration_since(std::time::UNIX_EPOCH)
623 .unwrap_or_default()
624 .as_millis() as u64,
625 fact_count,
626 store_hash,
627 cose_envelope: Some(cose_envelope),
628 jumbf_manifest: Some(jumbf_bytes),
629 })
630 }
631
632 fn resolve_cose_verifying_key(
633 record: &ProvenanceRecord,
634 fallback: &ed25519_dalek::VerifyingKey,
635 ) -> ed25519_dalek::VerifyingKey {
636 if let Some(ref envelope) = record.cose_envelope {
637 if let Ok(kid) = cose::extract_key_id(envelope) {
638 if kid.len() == 32 {
639 if let Ok(key) = ed25519_dalek::VerifyingKey::from_bytes(
640 kid.as_slice().try_into().unwrap_or(&[0u8; 32]),
641 ) {
642 return key;
643 }
644 }
645 }
646 }
647 if let Some(ref issuer_did) = record.issuer_did {
648 if let Ok(pk_bytes) = did::ed25519_from_did_key(issuer_did) {
649 if let Ok(key) = ed25519_dalek::VerifyingKey::from_bytes(&pk_bytes) {
650 return key;
651 }
652 }
653 }
654 *fallback
655 }
656
657 pub fn verify_fact_provenance(
663 &self,
664 record: &ProvenanceRecord,
665 trust: &TrustStore,
666 ) -> Result<VerificationResult> {
667 let mut result = self.verify_fact_provenance_self_consistency(record)?;
668 let fallback = self.signing_key.verifying_key();
669 let key = Self::resolve_cose_verifying_key(record, &fallback);
670 let trusted = trust.is_trusted(&key);
671 result.details.push(VerificationDetail {
672 check: "trust_anchor".to_string(),
673 passed: trusted,
674 message: if trusted {
675 None
676 } else {
677 Some("signing key is not in the trust store".to_string())
678 },
679 });
680 result.valid = result.details.iter().all(|d| d.passed) && !result.details.is_empty();
681 Ok(result)
682 }
683
684 pub fn verify_fact_provenance_self_consistency(
690 &self,
691 record: &ProvenanceRecord,
692 ) -> Result<VerificationResult> {
693 let mut details = Vec::new();
694 let fallback = self.signing_key.verifying_key();
695 let verifying_key = Self::resolve_cose_verifying_key(record, &fallback);
696
697 if let Some(ref envelope) = record.cose_envelope {
698 match cose::verify_and_extract(&verifying_key, envelope) {
699 Ok(_) => details.push(VerificationDetail {
700 check: "cose_signature".to_string(),
701 passed: true,
702 message: None,
703 }),
704 Err(e) => details.push(VerificationDetail {
705 check: "cose_signature".to_string(),
706 passed: false,
707 message: Some(e.to_string()),
708 }),
709 }
710 }
711
712 if let Some(ref vc_json) = record.vc_json {
713 match serde_json::from_str::<vc::FactCredential>(vc_json) {
714 Ok(ref credential) => {
715 match vc::verify_credential(credential) {
716 Ok(()) => details.push(VerificationDetail {
717 check: "vc_proof".to_string(),
718 passed: true,
719 message: None,
720 }),
721 Err(e) => details.push(VerificationDetail {
722 check: "vc_proof".to_string(),
723 passed: false,
724 message: Some(e.to_string()),
725 }),
726 }
727 let revoked = credential
728 .credential_status
729 .as_ref()
730 .and_then(|s| s.status_list_index.parse::<u64>().ok())
731 .is_some_and(|idx| self.is_revoked(idx));
732 details.push(VerificationDetail {
733 check: "revocation_status".to_string(),
734 passed: !revoked,
735 message: if revoked {
736 Some("credential has been revoked".to_string())
737 } else {
738 None
739 },
740 });
741 }
742 Err(e) => details.push(VerificationDetail {
743 check: "vc_parse".to_string(),
744 passed: false,
745 message: Some(e.to_string()),
746 }),
747 }
748 }
749
750 let all_passed = details.iter().all(|d| d.passed);
751
752 Ok(VerificationResult {
753 valid: all_passed && !details.is_empty(),
754 issuer_did: record.issuer_did.clone(),
755 timestamp_ms: record.timestamp_ms,
756 details,
757 })
758 }
759
760 pub fn signing_key(&self) -> &SigningKey {
761 &self.signing_key
762 }
763
764 pub fn verifying_key(&self) -> ed25519_dalek::VerifyingKey {
766 self.signing_key.verifying_key()
767 }
768
769 pub fn self_trust(&self) -> TrustStore {
772 TrustStore::trusting_key(&self.signing_key.verifying_key())
773 }
774
775 pub fn create_sigstore_bundle(
776 &self,
777 content: &[u8],
778 identity: Option<&str>,
779 ) -> Result<sigstore::SigstoreBundle> {
780 sigstore::create_local_bundle(&self.signing_key, content, identity)
781 }
782
783 pub fn verify_sigstore_bundle(
787 &self,
788 bundle: &sigstore::SigstoreBundle,
789 content: &[u8],
790 trusted_key: &ed25519_dalek::VerifyingKey,
791 ) -> Result<()> {
792 sigstore::verify_bundle_with_key(bundle, content, trusted_key)
793 }
794
795 pub fn create_cawg_assertion(
798 &self,
799 referenced: &[(String, String, Vec<u8>)],
800 display_name: &str,
801 ) -> Result<serde_json::Value> {
802 cawg::create_identity_assertion_ica(&self.signing_key, referenced, display_name)
803 }
804
805 pub fn verify_cawg_assertion(
811 &self,
812 assertion: &serde_json::Value,
813 trust: &TrustStore,
814 ) -> Result<serde_json::Value> {
815 let embedded = cawg::ica_embedded_bytes(assertion)?;
816 let vc = cawg::verify_cawg_ica(&embedded)?;
817 let issuer = vc
818 .get("issuer")
819 .and_then(|v| v.as_str())
820 .ok_or_else(|| anyhow::anyhow!("CAWG assertion VC has no issuer"))?;
821 let key_bytes = did::ed25519_from_did_jwk(issuer)?;
822 let key = ed25519_dalek::VerifyingKey::from_bytes(&key_bytes)
823 .map_err(|e| anyhow::anyhow!("invalid CAWG issuer key: {e}"))?;
824 if !trust.is_trusted(&key) {
825 return Err(anyhow::anyhow!("CAWG issuer is not trusted: {issuer}"));
826 }
827 Ok(vc)
828 }
829
830 pub fn keri_event_log(&self) -> &RwLock<keri::KeyEventLog> {
831 &self.kel
832 }
833
834 pub fn create_keri_interaction(
835 &self,
836 anchors: Vec<serde_json::Value>,
837 ) -> Result<keri::KeyEvent> {
838 self.kel
839 .write()
840 .interaction(&self.signing_key, anchors)
841 .cloned()
842 }
843
844 pub fn verify_keri_log(&self) -> Result<()> {
845 self.kel.read().verify()
846 }
847
848 fn resolve_manifest_verifying_key(
850 manifest: &StoreManifest,
851 fallback: &ed25519_dalek::VerifyingKey,
852 ) -> ed25519_dalek::VerifyingKey {
853 if let Some(ref envelope) = manifest.cose_envelope {
854 if let Ok(kid) = cose::extract_key_id(envelope) {
855 if let Ok(key) = ed25519_dalek::VerifyingKey::from_bytes(
856 kid.as_slice().try_into().unwrap_or(&[0u8; 32]),
857 ) {
858 return key;
859 }
860 }
861 }
862 if let Some(ref issuer_did) = manifest.issuer_did {
863 if let Ok(pk_bytes) = did::ed25519_from_did_key(issuer_did) {
864 if let Ok(key) = ed25519_dalek::VerifyingKey::from_bytes(&pk_bytes) {
865 return key;
866 }
867 }
868 }
869 *fallback
870 }
871
872 pub fn verify_store_manifest(
877 &self,
878 manifest: &StoreManifest,
879 store_data: Option<&[u8]>,
880 trust: &TrustStore,
881 ) -> Result<VerificationResult> {
882 let mut result = self.verify_store_manifest_self_consistency(manifest, store_data)?;
883 let fallback = self.signing_key.verifying_key();
884 let key = Self::resolve_manifest_verifying_key(manifest, &fallback);
885 let trusted = trust.is_trusted(&key);
886 result.details.push(VerificationDetail {
887 check: "trust_anchor".to_string(),
888 passed: trusted,
889 message: if trusted {
890 None
891 } else {
892 Some("signing key is not in the trust store".to_string())
893 },
894 });
895 result.valid = result.details.iter().all(|d| d.passed) && !result.details.is_empty();
896 Ok(result)
897 }
898
899 pub fn verify_store_manifest_self_consistency(
902 &self,
903 manifest: &StoreManifest,
904 store_data: Option<&[u8]>,
905 ) -> Result<VerificationResult> {
906 let mut details = Vec::new();
907 let fallback = self.signing_key.verifying_key();
908 let verifying_key = Self::resolve_manifest_verifying_key(manifest, &fallback);
909
910 if let Some(ref envelope) = manifest.cose_envelope {
911 match cose::verify_and_extract(&verifying_key, envelope) {
912 Ok(payload) => {
913 details.push(VerificationDetail {
914 check: "manifest_cose_signature".to_string(),
915 passed: true,
916 message: None,
917 });
918
919 if let Ok(hms_manifest) =
920 serde_json::from_slice::<c2pa_manifest::HmsManifest>(&payload)
921 {
922 if let Some(data) = store_data {
923 let hash_ok = c2pa_manifest::validate_store_hash(&hms_manifest, data)
924 .unwrap_or(false);
925 details.push(VerificationDetail {
926 check: "store_hash".to_string(),
927 passed: hash_ok,
928 message: if hash_ok {
929 None
930 } else {
931 Some("store hash mismatch".to_string())
932 },
933 });
934 }
935 }
936 }
937 Err(e) => details.push(VerificationDetail {
938 check: "manifest_cose_signature".to_string(),
939 passed: false,
940 message: Some(e.to_string()),
941 }),
942 }
943 }
944
945 let all_passed = details.iter().all(|d| d.passed);
946
947 Ok(VerificationResult {
948 valid: all_passed && !details.is_empty(),
949 issuer_did: manifest.issuer_did.clone(),
950 timestamp_ms: manifest.created_ms,
951 details,
952 })
953 }
954}
955
956pub fn verify_record(record: &ProvenanceRecord, trust: &TrustStore) -> Result<VerificationResult> {
964 let mut details = Vec::new();
965
966 if let Some(ref envelope) = record.cose_envelope {
967 let kid = cose::extract_key_id(envelope)?;
968 let pk: [u8; 32] = kid
969 .as_slice()
970 .try_into()
971 .map_err(|_| anyhow::anyhow!("COSE key_id is not 32 bytes"))?;
972 let verifying_key = ed25519_dalek::VerifyingKey::from_bytes(&pk)
973 .map_err(|e| anyhow::anyhow!("invalid public key in COSE: {e}"))?;
974 match cose::verify_and_extract(&verifying_key, envelope) {
975 Ok(_) => details.push(VerificationDetail {
976 check: "cose_signature".to_string(),
977 passed: true,
978 message: None,
979 }),
980 Err(e) => details.push(VerificationDetail {
981 check: "cose_signature".to_string(),
982 passed: false,
983 message: Some(e.to_string()),
984 }),
985 }
986 }
987
988 if let Some(ref vc_json) = record.vc_json {
989 match serde_json::from_str::<vc::FactCredential>(vc_json) {
990 Ok(ref credential) => match vc::verify_credential(credential) {
991 Ok(()) => details.push(VerificationDetail {
992 check: "vc_proof".to_string(),
993 passed: true,
994 message: None,
995 }),
996 Err(e) => details.push(VerificationDetail {
997 check: "vc_proof".to_string(),
998 passed: false,
999 message: Some(e.to_string()),
1000 }),
1001 },
1002 Err(e) => details.push(VerificationDetail {
1003 check: "vc_parse".to_string(),
1004 passed: false,
1005 message: Some(e.to_string()),
1006 }),
1007 }
1008 }
1009
1010 if !details.is_empty() {
1016 let signer_key = record
1017 .cose_envelope
1018 .as_ref()
1019 .and_then(|e| cose::extract_key_id(e).ok())
1020 .and_then(|kid| <[u8; 32]>::try_from(kid.as_slice()).ok())
1021 .or_else(|| {
1022 record
1023 .issuer_did
1024 .as_deref()
1025 .and_then(|d| did::ed25519_from_did_key(d).ok())
1026 })
1027 .and_then(|pk| ed25519_dalek::VerifyingKey::from_bytes(&pk).ok());
1028 let trusted = signer_key.as_ref().is_some_and(|k| trust.is_trusted(k));
1029 details.push(VerificationDetail {
1030 check: "trust_anchor".to_string(),
1031 passed: trusted,
1032 message: if trusted {
1033 None
1034 } else {
1035 Some("signing key is not in the trust store".to_string())
1036 },
1037 });
1038 }
1039
1040 let all_passed = details.iter().all(|d| d.passed);
1041 Ok(VerificationResult {
1042 valid: all_passed && !details.is_empty(),
1043 issuer_did: record.issuer_did.clone(),
1044 timestamp_ms: record.timestamp_ms,
1045 details,
1046 })
1047}
1048
1049fn hex_encode(bytes: &[u8]) -> String {
1050 bytes.iter().map(|b| format!("{b:02x}")).collect()
1051}
1052
1053fn hex_decode(hex: &str) -> Result<[u8; 32]> {
1054 if hex.len() != 64 {
1055 return Err(anyhow::anyhow!("invalid hex hash length"));
1056 }
1057 let mut out = [0u8; 32];
1058 for (i, chunk) in hex.as_bytes().chunks(2).enumerate() {
1059 out[i] = u8::from_str_radix(
1060 std::str::from_utf8(chunk).map_err(|e| anyhow::anyhow!("hex decode: {e}"))?,
1061 16,
1062 )
1063 .map_err(|e| anyhow::anyhow!("hex decode: {e}"))?;
1064 }
1065 Ok(out)
1066}
1067
1068#[cfg(test)]
1069mod tests {
1070 use super::*;
1071 use tempfile::tempdir;
1072
1073 #[test]
1074 fn full_fact_provenance_flow() {
1075 let dir = tempdir().unwrap();
1076 let mgr = ProvenanceManager::new(&dir.path().join("test.key"), Some(dir.path())).unwrap();
1077
1078 let record = mgr
1079 .create_fact_provenance(
1080 "fact-001",
1081 b"Paris is the capital of France",
1082 Some("https://example.com"),
1083 )
1084 .unwrap();
1085
1086 assert!(record.cose_envelope.is_some());
1087 assert!(record.vc_json.is_some());
1088 assert!(record.issuer_did.is_some());
1089 assert!(record.issuer_did.as_ref().unwrap().starts_with("did:key:z"));
1090
1091 let verification = mgr
1092 .verify_fact_provenance(&record, &mgr.self_trust())
1093 .unwrap();
1094 assert!(verification.valid);
1095 assert_eq!(verification.details.len(), 4);
1096
1097 assert!(mgr.get_record("fact-001").is_some());
1098 assert_eq!(mgr.record_count(), 1);
1099 }
1100
1101 #[test]
1102 fn full_triple_provenance_flow() {
1103 let dir = tempdir().unwrap();
1104 let mgr = ProvenanceManager::new(&dir.path().join("test.key"), Some(dir.path())).unwrap();
1105
1106 let record = mgr
1107 .create_triple_provenance(&TripleProvenanceParams {
1108 fact_id: "triple-001",
1109 content: b"paris capital_of france",
1110 dimensions: 16384,
1111 subject: "paris",
1112 relation: "capital_of",
1113 object: "france",
1114 source_uri: None,
1115 })
1116 .unwrap();
1117
1118 let vc: vc::FactCredential =
1119 serde_json::from_str(record.vc_json.as_ref().unwrap()).unwrap();
1120 assert_eq!(vc.credential_subject.subject_id.as_deref(), Some("paris"));
1121
1122 let verification = mgr
1123 .verify_fact_provenance(&record, &mgr.self_trust())
1124 .unwrap();
1125 assert!(verification.valid);
1126 assert!(mgr.get_record("triple-001").is_some());
1127 }
1128
1129 #[test]
1130 fn store_manifest_flow() {
1131 let dir = tempdir().unwrap();
1132 let mgr = ProvenanceManager::new(&dir.path().join("test.key"), Some(dir.path())).unwrap();
1133
1134 let store_data = b"serialized store contents here";
1135 let manifest = mgr
1136 .create_store_manifest("store-001", store_data, 42, 16384, Some("Test Store"))
1137 .unwrap();
1138
1139 assert!(manifest.cose_envelope.is_some());
1140
1141 let verification = mgr
1142 .verify_store_manifest(&manifest, Some(store_data), &mgr.self_trust())
1143 .unwrap();
1144 assert!(verification.valid);
1145 assert!(verification
1146 .details
1147 .iter()
1148 .any(|d| d.check == "store_hash" && d.passed));
1149
1150 let bad_verification = mgr
1151 .verify_store_manifest(&manifest, Some(b"wrong"), &mgr.self_trust())
1152 .unwrap();
1153 assert!(!bad_verification.valid);
1154 }
1155
1156 #[test]
1157 fn issuer_did_consistency() {
1158 let dir = tempdir().unwrap();
1159 let key_path = dir.path().join("test.key");
1160 let mgr1 = ProvenanceManager::new(&key_path, Some(dir.path())).unwrap();
1161 let mgr2 = ProvenanceManager::new(&key_path, Some(dir.path())).unwrap();
1162 assert_eq!(mgr1.issuer_did(), mgr2.issuer_did());
1163 }
1164
1165 #[test]
1166 fn records_persist_to_disk() {
1167 let dir = tempdir().unwrap();
1168 let key_path = dir.path().join("test.key");
1169
1170 {
1171 let mgr = ProvenanceManager::new(&key_path, Some(dir.path())).unwrap();
1172 mgr.create_fact_provenance("f1", b"content one", None)
1173 .unwrap();
1174 mgr.create_fact_provenance("f2", b"content two", None)
1175 .unwrap();
1176 assert_eq!(mgr.record_count(), 2);
1177 }
1178
1179 let mgr2 = ProvenanceManager::new(&key_path, Some(dir.path())).unwrap();
1180 assert_eq!(mgr2.record_count(), 2);
1181 assert!(mgr2.get_record("f1").is_some());
1182 assert!(mgr2.get_record("f2").is_some());
1183 }
1184
1185 #[test]
1186 fn standalone_verify_without_manager() {
1187 let dir = tempdir().unwrap();
1188 let mgr = ProvenanceManager::new(&dir.path().join("test.key"), Some(dir.path())).unwrap();
1189 let record = mgr
1190 .create_fact_provenance("standalone-001", b"verify without manager", None)
1191 .unwrap();
1192 let issuer = record.issuer_did.clone().unwrap();
1193 drop(mgr);
1194
1195 let trust = trust::TrustStore::trusting_did(&issuer).unwrap();
1196 let result = verify_record(&record, &trust).unwrap();
1197 assert!(result.valid);
1198 assert_eq!(result.details.len(), 3);
1199 }
1200
1201 #[test]
1202 fn hash_chain_integrity() {
1203 let dir = tempdir().unwrap();
1204 let mgr = ProvenanceManager::new(&dir.path().join("test.key"), Some(dir.path())).unwrap();
1205 mgr.create_fact_provenance("c1", b"first", None).unwrap();
1206 mgr.create_fact_provenance("c2", b"second", None).unwrap();
1207 mgr.create_fact_provenance("c3", b"third", None).unwrap();
1208 assert!(mgr.verify_log_integrity().unwrap());
1209 }
1210
1211 #[test]
1212 fn tampered_log_detected() {
1213 let dir = tempdir().unwrap();
1214 let mgr = ProvenanceManager::new(&dir.path().join("test.key"), Some(dir.path())).unwrap();
1215 mgr.create_fact_provenance("t1", b"one", None).unwrap();
1216 mgr.create_fact_provenance("t2", b"two", None).unwrap();
1217 drop(mgr);
1218
1219 let log_path = dir.path().join("provenance_log.jsonl");
1220 let contents = std::fs::read_to_string(&log_path).unwrap();
1221 let lines: Vec<&str> = contents.lines().collect();
1222 let tampered = lines[1].replace("\"t2\"", "\"TAMPERED\"");
1223 std::fs::write(&log_path, format!("{}\n{tampered}\n", lines[0])).unwrap();
1224
1225 let mgr2 = ProvenanceManager::new(&dir.path().join("test.key"), Some(dir.path())).unwrap();
1226 assert!(!mgr2.verify_log_integrity().unwrap());
1227 }
1228
1229 #[test]
1230 fn key_rotation_preserves_old_records() {
1231 let dir = tempdir().unwrap();
1232 let key_path = dir.path().join("orig.key");
1233 let mut mgr = ProvenanceManager::new(&key_path, Some(dir.path())).unwrap();
1234 let old_did = mgr.issuer_did().to_string();
1235
1236 let record = mgr
1237 .create_fact_provenance("before-rotate", b"old key", None)
1238 .unwrap();
1239
1240 let new_key_path = dir.path().join("rotated.key");
1241 let new_did = mgr.rotate_key(&new_key_path).unwrap();
1242 assert_ne!(old_did, new_did);
1243 assert_eq!(mgr.issuer_did(), new_did);
1244
1245 let post_record = mgr
1246 .create_fact_provenance("after-rotate", b"new key", None)
1247 .unwrap();
1248 assert_eq!(post_record.issuer_did.as_deref(), Some(new_did.as_str()));
1249
1250 let mut trust = trust::TrustStore::new();
1251 trust.trust_did(&old_did).unwrap();
1252 trust.trust_did(&new_did).unwrap();
1253
1254 let old_result = verify_record(&record, &trust).unwrap();
1255 assert!(old_result.valid);
1256
1257 let new_result = verify_record(&post_record, &trust).unwrap();
1258 assert!(new_result.valid);
1259
1260 assert!(mgr.verify_log_integrity().unwrap());
1261 }
1262
1263 #[test]
1264 fn batch_provenance_merkle() {
1265 let dir = tempdir().unwrap();
1266 let mgr = ProvenanceManager::new(&dir.path().join("test.key"), Some(dir.path())).unwrap();
1267
1268 let items: Vec<(&str, &[u8], Option<&str>)> = vec![
1269 ("b1", b"alpha", None),
1270 ("b2", b"beta", None),
1271 ("b3", b"gamma", Some("https://example.com")),
1272 ("b4", b"delta", None),
1273 ];
1274
1275 let records = mgr.create_batch_provenance(&items).unwrap();
1276 assert_eq!(records.len(), 4);
1277 assert_eq!(mgr.record_count(), 4);
1278
1279 let envelope = records[0].cose_envelope.as_ref().unwrap();
1280 for r in &records {
1281 assert_eq!(r.cose_envelope.as_ref().unwrap(), envelope);
1282 assert!(r.merkle_proof.is_some());
1283 assert!(r.vc_json.is_some());
1284 }
1285
1286 let trust = mgr.self_trust();
1287 for r in &records {
1288 let result = verify_record(r, &trust).unwrap();
1289 assert!(result
1290 .details
1291 .iter()
1292 .any(|d| d.check == "vc_proof" && d.passed));
1293 }
1294 }
1295
1296 #[test]
1297 fn credential_revocation() {
1298 let dir = tempdir().unwrap();
1299 let mgr = ProvenanceManager::new(&dir.path().join("test.key"), Some(dir.path())).unwrap();
1300
1301 let record = mgr
1302 .create_fact_provenance("fact-rev", b"revocable content", None)
1303 .unwrap();
1304
1305 let verification = mgr
1306 .verify_fact_provenance(&record, &mgr.self_trust())
1307 .unwrap();
1308 assert!(verification.valid);
1309 assert!(verification
1310 .details
1311 .iter()
1312 .any(|d| d.check == "revocation_status" && d.passed));
1313
1314 let vc: vc::FactCredential =
1315 serde_json::from_str(record.vc_json.as_ref().unwrap()).unwrap();
1316 let status_index: u64 = vc
1317 .credential_status
1318 .as_ref()
1319 .unwrap()
1320 .status_list_index
1321 .parse()
1322 .unwrap();
1323 mgr.revoke_credential(status_index).unwrap();
1324 assert!(mgr.is_revoked(status_index));
1325
1326 let verification = mgr
1327 .verify_fact_provenance(&record, &mgr.self_trust())
1328 .unwrap();
1329 assert!(!verification.valid);
1330 assert!(verification
1331 .details
1332 .iter()
1333 .any(|d| d.check == "revocation_status" && !d.passed));
1334 }
1335
1336 #[test]
1337 fn untrusted_key_rejected() {
1338 let dir = tempdir().unwrap();
1339 let mgr = ProvenanceManager::new(&dir.path().join("test.key"), Some(dir.path())).unwrap();
1340 let record = mgr
1341 .create_fact_provenance("fact-trust", b"authentic content", None)
1342 .unwrap();
1343
1344 let ok = mgr
1346 .verify_fact_provenance(&record, &mgr.self_trust())
1347 .unwrap();
1348 assert!(ok.valid);
1349
1350 let stranger = SigningKey::from_bytes(&rand::random());
1354 let foreign_trust = trust::TrustStore::trusting_key(&stranger.verifying_key());
1355 let rejected = mgr.verify_fact_provenance(&record, &foreign_trust).unwrap();
1356 assert!(!rejected.valid);
1357 assert!(rejected
1358 .details
1359 .iter()
1360 .any(|d| d.check == "trust_anchor" && !d.passed));
1361
1362 let self_consistent = mgr
1364 .verify_fact_provenance_self_consistency(&record)
1365 .unwrap();
1366 assert!(self_consistent.valid);
1367 }
1368
1369 #[test]
1370 fn vc_only_record_requires_trust() {
1371 let dir = tempdir().unwrap();
1375 let mgr = ProvenanceManager::new(&dir.path().join("test.key"), Some(dir.path())).unwrap();
1376 let record = mgr
1377 .create_fact_provenance("vc-only", b"content", None)
1378 .unwrap();
1379
1380 let mut vc_only = record.clone();
1381 vc_only.cose_envelope = None;
1382
1383 let ok = verify_record(&vc_only, &mgr.self_trust()).unwrap();
1384 assert!(ok.valid);
1385 assert!(ok
1386 .details
1387 .iter()
1388 .any(|d| d.check == "trust_anchor" && d.passed));
1389
1390 let stranger = SigningKey::from_bytes(&rand::random());
1391 let foreign = trust::TrustStore::trusting_key(&stranger.verifying_key());
1392 let rejected = verify_record(&vc_only, &foreign).unwrap();
1393 assert!(!rejected.valid);
1394 assert!(rejected
1395 .details
1396 .iter()
1397 .any(|d| d.check == "trust_anchor" && !d.passed));
1398 }
1399
1400 #[test]
1401 fn cawg_untrusted_issuer_rejected() {
1402 let dir = tempdir().unwrap();
1403 let mgr = ProvenanceManager::new(&dir.path().join("test.key"), Some(dir.path())).unwrap();
1404
1405 let referenced = vec![(
1406 "self#jumbf=c2pa.assertions/c2pa.hash.data".to_string(),
1407 "sha256".to_string(),
1408 vec![0x11u8; 32],
1409 )];
1410 let assertion = mgr.create_cawg_assertion(&referenced, "hms agent").unwrap();
1411
1412 assert!(mgr
1414 .verify_cawg_assertion(&assertion, &mgr.self_trust())
1415 .is_ok());
1416
1417 let stranger = SigningKey::from_bytes(&rand::random());
1420 let foreign = trust::TrustStore::trusting_key(&stranger.verifying_key());
1421 assert!(mgr.verify_cawg_assertion(&assertion, &foreign).is_err());
1422 }
1423}