Skip to main content

trust_store/
lib.rs

1//! # trust-store
2//!
3//! Web of Trust with TOFU (Trust-On-First-Use) key verification and social graph
4//! attestation for P2P networks.
5//!
6//! ## Features
7//!
8//! - **TOFU key pinning** — pin a peer's public key on first contact
9//! - **Key change detection** — flag potential MITM attacks
10//! - **Social graph attestation** — peers vouch for each other's keys
11//! - **Computed trust scores** — bubble trust through the graph with decay
12//! - **Ed25519 signature verification** — cryptographically verify attestations
13//!
14//! ## Quick Start
15//!
16//! ```rust
17//! use trust_store::TrustStore;
18//!
19//! # #[tokio::main]
20//! # async fn main() {
21//! let store = TrustStore::new();
22//! let key = vec![0x01u8; 32];
23//!
24//! // First contact — TOFU pin
25//! let is_new = store.verify_or_pin("peer1", &key).await.unwrap();
26//! assert!(is_new);
27//!
28//! // Second contact with same key — trusted
29//! let is_new = store.verify_or_pin("peer1", &key).await.unwrap();
30//! assert!(!is_new);
31//! # }
32//! ```
33//!
34//! ## No PKI, No CA, No Blockchain
35//!
36//! Pure peer attestation — like SSH known_hosts + web of trust combined.
37
38#![warn(missing_docs)]
39
40use serde::{Deserialize, Serialize};
41use std::collections::HashMap;
42use std::sync::Arc;
43use thiserror::Error;
44use tokio::sync::RwLock;
45
46/// Errors that can occur during trust store operations.
47#[derive(Debug, Error)]
48#[allow(missing_docs)]
49pub enum TrustError {
50    /// Key has changed since first contact — possible MITM
51    #[error("Key change detected for peer {peer_id}")]
52    KeyChanged { peer_id: String },
53    /// Unknown peer
54    #[error("Unknown peer: {peer_id}")]
55    UnknownPeer { peer_id: String },
56    /// Attestation verification failed
57    #[error("Attestation verification failed: {0}")]
58    AttestationFailed(String),
59    /// Key mismatch during verification
60    #[error("Key mismatch during verification")]
61    KeyMismatch,
62}
63
64/// Result type for trust store operations
65pub type TrustResult<T> = Result<T, TrustError>;
66
67/// Trust level for a known peer
68#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
69pub enum TrustLevel {
70    /// Never seen before
71    Unknown,
72    /// First contact — key pinned but not verified
73    FirstContact,
74    /// Key matches what we've seen before
75    Trusted,
76    /// Someone we trust has vouched for this peer
77    Vouched,
78    /// We have met this peer out-of-band (QR scan, in person)
79    Verified,
80    /// Key has changed since we first saw it — possible MITM
81    KeyChanged,
82    /// Explicitly blocked by the user
83    Blocked,
84}
85
86impl TrustLevel {
87    /// Numeric score for this trust level
88    pub fn score(&self) -> f32 {
89        match self {
90            Self::Unknown => 0.0,
91            Self::FirstContact => 0.3,
92            Self::Trusted => 0.6,
93            Self::Vouched => 0.7,
94            Self::Verified => 1.0,
95            Self::KeyChanged => -1.0,
96            Self::Blocked => -2.0,
97        }
98    }
99
100    /// Whether this trust level is usable for communication
101    pub fn is_usable(&self) -> bool {
102        !matches!(self, Self::Blocked | Self::KeyChanged)
103    }
104}
105
106/// A vouching attestation from one peer about another
107#[derive(Debug, Clone, Serialize, Deserialize)]
108pub struct Attestation {
109    /// The peer making the claim
110    pub attester_id: String,
111    /// The peer being attested
112    pub subject_id: String,
113    /// The subject's public key being attested
114    pub subject_pub_key: Vec<u8>,
115    /// Ed25519 signature from attester
116    pub signature: Vec<u8>,
117    /// Unix timestamp
118    pub timestamp: u64,
119    /// Optional human-readable note
120    pub note: Option<String>,
121}
122
123/// Our record of a known peer
124#[derive(Debug, Clone, Serialize, Deserialize)]
125#[allow(missing_docs)]
126pub struct PeerRecord {
127    pub peer_id: String,
128    /// The first public key we saw from this peer (pinned)
129    pub pinned_key: Vec<u8>,
130    /// The most recently seen key
131    pub current_key: Vec<u8>,
132    pub trust_level: TrustLevel,
133    pub first_seen: u64,
134    pub last_seen: u64,
135    pub attestations: Vec<Attestation>,
136    pub interaction_count: u32,
137    pub display_name: Option<String>,
138}
139
140impl PeerRecord {
141    /// Whether the key has changed since first contact
142    pub fn has_key_changed(&self) -> bool {
143        self.pinned_key != self.current_key
144    }
145
146    /// Compute trust score incorporating attestations and interaction history
147    pub fn computed_trust_score(&self, our_attesters: &HashMap<String, f32>) -> f32 {
148        let base = self.trust_level.score();
149        if base < 0.0 {
150            return base;
151        }
152
153        let attestation_bonus: f32 = self
154            .attestations
155            .iter()
156            .filter_map(|a| our_attesters.get(&a.attester_id))
157            .map(|&attester_score| attester_score * 0.3)
158            .sum::<f32>()
159            .min(0.3);
160
161        let interaction_bonus = (self.interaction_count as f32 / 100.0).min(0.1);
162
163        (base + attestation_bonus + interaction_bonus).min(1.0)
164    }
165}
166
167/// The trust store — manages all known peer records
168pub struct TrustStore {
169    peers: Arc<RwLock<HashMap<String, PeerRecord>>>,
170    our_trust_scores: Arc<RwLock<HashMap<String, f32>>>,
171}
172
173impl TrustStore {
174    /// Create a new empty trust store
175    pub fn new() -> Self {
176        Self {
177            peers: Arc::new(RwLock::new(HashMap::new())),
178            our_trust_scores: Arc::new(RwLock::new(HashMap::new())),
179        }
180    }
181
182    /// Called when we first encounter a peer's public key (TOFU).
183    ///
184    /// Returns `Ok(true)` if this is a new peer, `Ok(false)` if key matches.
185    /// Returns `Err` if key has changed for a known peer.
186    pub async fn verify_or_pin(&self, peer_id: &str, public_key: &[u8]) -> TrustResult<bool> {
187        let mut peers = self.peers.write().await;
188
189        if let Some(record) = peers.get_mut(peer_id) {
190            record.current_key = public_key.to_vec();
191            record.last_seen = now_secs();
192            record.interaction_count += 1;
193
194            if record.pinned_key != public_key {
195                record.trust_level = TrustLevel::KeyChanged;
196                return Err(TrustError::KeyChanged {
197                    peer_id: peer_id.to_string(),
198                });
199            }
200
201            if record.trust_level == TrustLevel::FirstContact {
202                record.trust_level = TrustLevel::Trusted;
203            }
204
205            Ok(false)
206        } else {
207            peers.insert(
208                peer_id.to_string(),
209                PeerRecord {
210                    peer_id: peer_id.to_string(),
211                    pinned_key: public_key.to_vec(),
212                    current_key: public_key.to_vec(),
213                    trust_level: TrustLevel::FirstContact,
214                    first_seen: now_secs(),
215                    last_seen: now_secs(),
216                    attestations: vec![],
217                    interaction_count: 1,
218                    display_name: None,
219                },
220            );
221            Ok(true)
222        }
223    }
224
225    /// Record an attestation from a trusted peer about another peer
226    pub async fn add_attestation(&self, attestation: Attestation) -> TrustResult<()> {
227        self.verify_attestation_signature(&attestation)
228            .await
229            .map_err(TrustError::AttestationFailed)?;
230
231        let mut peers = self.peers.write().await;
232        let record = peers
233            .entry(attestation.subject_id.clone())
234            .or_insert_with(|| PeerRecord {
235                peer_id: attestation.subject_id.clone(),
236                pinned_key: attestation.subject_pub_key.clone(),
237                current_key: attestation.subject_pub_key.clone(),
238                trust_level: TrustLevel::Vouched,
239                first_seen: now_secs(),
240                last_seen: now_secs(),
241                attestations: vec![],
242                interaction_count: 0,
243                display_name: None,
244            });
245
246        if !record
247            .attestations
248            .iter()
249            .any(|a| a.attester_id == attestation.attester_id)
250        {
251            record.attestations.push(attestation);
252
253            if record.trust_level == TrustLevel::Unknown
254                || record.trust_level == TrustLevel::FirstContact
255            {
256                record.trust_level = TrustLevel::Vouched;
257            }
258        }
259
260        Ok(())
261    }
262
263    /// Upgrade a peer to Verified status (after out-of-band verification)
264    pub async fn mark_verified(&self, peer_id: &str, verified_key: &[u8]) -> TrustResult<()> {
265        let mut peers = self.peers.write().await;
266        let record = peers
267            .get_mut(peer_id)
268            .ok_or_else(|| TrustError::UnknownPeer {
269                peer_id: peer_id.to_string(),
270            })?;
271
272        if record.pinned_key != verified_key {
273            return Err(TrustError::KeyMismatch);
274        }
275
276        record.trust_level = TrustLevel::Verified;
277        Ok(())
278    }
279
280    /// Block a peer
281    pub async fn block(&self, peer_id: &str) {
282        let mut peers = self.peers.write().await;
283        if let Some(record) = peers.get_mut(peer_id) {
284            record.trust_level = TrustLevel::Blocked;
285        }
286    }
287
288    /// Get trust info for a peer
289    pub async fn get(&self, peer_id: &str) -> Option<PeerRecord> {
290        self.peers.read().await.get(peer_id).cloned()
291    }
292
293    /// Get trust score (0.0–1.0, or negative for warnings)
294    pub async fn trust_score(&self, peer_id: &str) -> f32 {
295        let peers = self.peers.read().await;
296        let scores = self.our_trust_scores.read().await;
297        peers
298            .get(peer_id)
299            .map(|r| r.computed_trust_score(&scores))
300            .unwrap_or(0.0)
301    }
302
303    /// List all known peers, sorted by trust score descending
304    pub async fn all_peers(&self) -> Vec<PeerRecord> {
305        let peers = self.peers.read().await;
306        let scores = self.our_trust_scores.read().await;
307        let mut list: Vec<_> = peers.values().cloned().collect();
308        list.sort_by(|a, b| {
309            b.computed_trust_score(&scores)
310                .partial_cmp(&a.computed_trust_score(&scores))
311                .unwrap_or(std::cmp::Ordering::Equal)
312        });
313        list
314    }
315
316    /// Set our trust score for a peer
317    pub async fn set_attester_trust(&self, peer_id: String, score: f32) {
318        self.our_trust_scores
319            .write()
320            .await
321            .insert(peer_id, score.clamp(0.0, 1.0));
322    }
323
324    /// Count of blocked peers
325    pub async fn blocked_count(&self) -> usize {
326        self.peers
327            .read()
328            .await
329            .values()
330            .filter(|r| r.trust_level == TrustLevel::Blocked)
331            .count()
332    }
333
334    /// Average trust score across all known peers
335    pub async fn average_trust_score(&self) -> f32 {
336        let peers = self.peers.read().await;
337        if peers.is_empty() {
338            return 0.0;
339        }
340        let sum: f32 = peers.values().map(|r| r.trust_level.score()).sum();
341        sum / peers.len() as f32
342    }
343
344    async fn verify_attestation_signature(&self, att: &Attestation) -> Result<(), String> {
345        use ring::signature::{UnparsedPublicKey, ED25519};
346
347        if att.signature.is_empty() {
348            return Err(format!(
349                "Attestation from {} has empty signature",
350                att.attester_id
351            ));
352        }
353        if att.subject_pub_key.is_empty() {
354            return Err(format!(
355                "Attestation from {} has empty subject public key",
356                att.attester_id
357            ));
358        }
359
360        let attester_pub_key = {
361            let peers = self.peers.read().await;
362            peers
363                .get(&att.attester_id)
364                .map(|r| r.current_key.clone())
365                .ok_or_else(|| {
366                    format!(
367                        "Cannot verify attestation from unknown peer: {}",
368                        att.attester_id
369                    )
370                })?
371        };
372
373        let payload = [
374            att.subject_id.as_bytes(),
375            &att.subject_pub_key,
376            &att.timestamp.to_le_bytes(),
377        ]
378        .concat();
379
380        let pub_key = UnparsedPublicKey::new(&ED25519, &attester_pub_key);
381        pub_key.verify(&payload, &att.signature).map_err(|e| {
382            format!(
383                "Attestation signature verification failed for {} → {}: {}",
384                att.attester_id, att.subject_id, e
385            )
386        })
387    }
388}
389
390impl Default for TrustStore {
391    fn default() -> Self {
392        Self::new()
393    }
394}
395
396fn now_secs() -> u64 {
397    std::time::SystemTime::now()
398        .duration_since(std::time::UNIX_EPOCH)
399        .unwrap_or_default()
400        .as_secs()
401}
402
403#[cfg(test)]
404mod tests {
405    use super::*;
406
407    #[tokio::test]
408    async fn tofu_pin_on_first_contact() {
409        let store = TrustStore::new();
410        let key = vec![0x01u8; 32];
411        let is_new = store.verify_or_pin("peer1", &key).await.unwrap();
412        assert!(is_new);
413        let record = store.get("peer1").await.unwrap();
414        assert_eq!(record.trust_level, TrustLevel::FirstContact);
415    }
416
417    #[tokio::test]
418    async fn second_contact_with_same_key_is_trusted() {
419        let store = TrustStore::new();
420        let key = vec![0x02u8; 32];
421        store.verify_or_pin("peer1", &key).await.unwrap();
422        let is_new = store.verify_or_pin("peer1", &key).await.unwrap();
423        assert!(!is_new);
424        let record = store.get("peer1").await.unwrap();
425        assert_eq!(record.trust_level, TrustLevel::Trusted);
426    }
427
428    #[tokio::test]
429    async fn key_change_returns_error() {
430        let store = TrustStore::new();
431        let key1 = vec![0x03u8; 32];
432        let key2 = vec![0x04u8; 32];
433        store.verify_or_pin("peer1", &key1).await.unwrap();
434        let result = store.verify_or_pin("peer1", &key2).await;
435        assert!(result.is_err());
436        let record = store.get("peer1").await.unwrap();
437        assert_eq!(record.trust_level, TrustLevel::KeyChanged);
438    }
439
440    #[tokio::test]
441    async fn block_sets_level() {
442        let store = TrustStore::new();
443        let key = vec![0x05u8; 32];
444        store.verify_or_pin("peer1", &key).await.unwrap();
445        store.block("peer1").await;
446        let record = store.get("peer1").await.unwrap();
447        assert_eq!(record.trust_level, TrustLevel::Blocked);
448        assert!(!record.trust_level.is_usable());
449    }
450}