fleetforge_trust/
vault.rs

1use 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
12/// Canonicalises a JSON value by recursively ordering object keys.
13fn 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
31/// Computes a deterministic SHA256 digest for the supplied JSON value.
32pub 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
38/// Computes a deterministic SHA256 digest for a byte slice.
39pub fn digest_bytes(bytes: &[u8]) -> String {
40    let mut hasher = Sha256::new();
41    hasher.update(bytes);
42    hex::encode(hasher.finalize())
43}
44
45/// In-memory attestation vault used for tests and local development.
46#[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}