fleetforge_trust/
vault.rs1use std::collections::BTreeMap;
2use std::sync::{Arc, RwLock};
3
4use anyhow::{anyhow, Result};
5use async_trait::async_trait;
6use serde_json::Value;
7use sha2::{Digest, Sha256};
8use uuid::Uuid;
9
10use crate::{Attestation, TrustSubject};
11
12fn canonicalise(value: &Value) -> Value {
14 match value {
15 Value::Object(map) => {
16 let mut ordered = BTreeMap::new();
17 for (key, value) in map.iter() {
18 ordered.insert(key.clone(), canonicalise(value));
19 }
20 let mut object = serde_json::Map::new();
21 for (key, value) in ordered {
22 object.insert(key, value);
23 }
24 Value::Object(object)
25 }
26 Value::Array(items) => Value::Array(items.iter().map(canonicalise).collect()),
27 other => other.clone(),
28 }
29}
30
31pub fn digest_json(value: &Value) -> String {
33 let canonical = canonicalise(value);
34 let bytes = serde_json::to_vec(&canonical).expect("canonical JSON serialisation must succeed");
35 digest_bytes(&bytes)
36}
37
38pub fn digest_bytes(bytes: &[u8]) -> String {
40 let mut hasher = Sha256::new();
41 hasher.update(bytes);
42 hex::encode(hasher.finalize())
43}
44
45#[derive(Clone, Default)]
47pub struct InMemoryAttestationVault {
48 state: Arc<RwLock<BTreeMap<Uuid, Attestation>>>,
49}
50
51impl InMemoryAttestationVault {
52 pub fn new() -> Self {
53 Self::default()
54 }
55}
56
57#[async_trait]
58pub trait AttestationVault: Send + Sync {
59 async fn record(&self, attestation: Attestation) -> Result<Uuid>;
60 async fn fetch(&self, id: Uuid) -> Result<Option<Attestation>>;
61 async fn list_by_subject(&self, subject: &TrustSubject) -> Result<Vec<Attestation>>;
62}
63
64#[async_trait]
65impl AttestationVault for InMemoryAttestationVault {
66 async fn record(&self, attestation: Attestation) -> Result<Uuid> {
67 let mut guard = self.state.write().expect("lock poisoned");
68 let entry = guard.entry(attestation.id);
69 if entry.or_insert_with(|| attestation.clone()).id == attestation.id {
70 Ok(attestation.id)
71 } else {
72 Err(anyhow!("attestation {} already exists", attestation.id))
73 }
74 }
75
76 async fn fetch(&self, id: Uuid) -> Result<Option<Attestation>> {
77 let guard = self.state.read().expect("lock poisoned");
78 Ok(guard.get(&id).cloned())
79 }
80
81 async fn list_by_subject(&self, subject: &TrustSubject) -> Result<Vec<Attestation>> {
82 let guard = self.state.read().expect("lock poisoned");
83 Ok(guard
84 .values()
85 .filter(|attestation| attestation.subject.as_ref() == Some(subject))
86 .cloned()
87 .collect())
88 }
89}