Skip to main content

assay_registry/
trust.rs

1//! Key trust store for signature verification.
2//!
3//! The trust store manages trusted signing keys for pack verification.
4//! Keys can come from:
5//! - Pinned roots (compiled into binary)
6//! - Configuration file
7//! - Remote keys manifest (fetched from registry)
8
9use std::collections::HashMap;
10use std::sync::Arc;
11
12use chrono::{DateTime, Utc};
13use ed25519_dalek::VerifyingKey;
14use tokio::sync::RwLock;
15
16use crate::error::{RegistryError, RegistryResult};
17use crate::types::{KeysManifest, TrustedKey};
18#[cfg(test)]
19use crate::verify::compute_key_id;
20#[cfg(test)]
21use base64::{engine::general_purpose::STANDARD as BASE64, Engine};
22
23/// Default cache TTL for keys manifest (24 hours).
24const DEFAULT_KEYS_TTL_SECS: i64 = 24 * 60 * 60;
25const PRODUCTION_TRUST_ROOTS_JSON: &str = include_str!("../assets/production-trust-roots.json");
26
27#[path = "trust_next/mod.rs"]
28mod trust_next;
29
30use trust_next::access;
31use trust_next::cache;
32use trust_next::manifest;
33#[cfg(test)]
34use trust_next::pinned::parse_pinned_roots_json_impl;
35use trust_next::pinned::{
36    insert_pinned_key, insert_prepared_pinned_key, load_production_roots_impl, prepare_pinned_key,
37};
38
39/// Trust store for signing keys.
40#[derive(Debug, Clone)]
41pub struct TrustStore {
42    inner: Arc<RwLock<TrustStoreInner>>,
43}
44
45#[derive(Debug)]
46struct TrustStoreInner {
47    /// Key ID -> VerifyingKey
48    keys: HashMap<String, VerifyingKey>,
49
50    /// Key metadata
51    metadata: HashMap<String, KeyMetadata>,
52
53    /// Pinned root key IDs (always trusted)
54    pinned_roots: Vec<String>,
55
56    /// When the keys manifest was last fetched
57    manifest_fetched_at: Option<DateTime<Utc>>,
58
59    /// When the cached manifest expires
60    manifest_expires_at: Option<DateTime<Utc>>,
61}
62
63/// Metadata for a trusted key.
64#[derive(Debug, Clone)]
65pub struct KeyMetadata {
66    /// Human-readable description.
67    pub description: Option<String>,
68
69    /// When the key was added.
70    pub added_at: Option<DateTime<Utc>>,
71
72    /// When the key expires.
73    pub expires_at: Option<DateTime<Utc>>,
74
75    /// Whether the key is revoked.
76    pub revoked: bool,
77
78    /// Whether this is a pinned root key.
79    pub is_pinned: bool,
80}
81
82impl TrustStore {
83    /// Create an empty trust store.
84    pub fn new() -> Self {
85        Self {
86            inner: Arc::new(RwLock::new(cache::empty_inner())),
87        }
88    }
89
90    /// Create a trust store with pinned root keys.
91    ///
92    /// Pinned roots are always trusted and cannot be revoked remotely.
93    pub fn from_pinned_roots(roots: Vec<TrustedKey>) -> RegistryResult<Self> {
94        let mut inner = cache::empty_inner();
95        for root in &roots {
96            insert_pinned_key(&mut inner, root)?;
97        }
98
99        Ok(Self {
100            inner: Arc::new(RwLock::new(inner)),
101        })
102    }
103
104    /// Create a trust store with pinned root keys.
105    ///
106    /// Pinned roots are always trusted and cannot be revoked remotely.
107    pub async fn with_pinned_roots(roots: Vec<TrustedKey>) -> RegistryResult<Self> {
108        Self::from_pinned_roots(roots)
109    }
110
111    /// Create a trust store with the default production roots.
112    pub fn from_production_roots() -> RegistryResult<Self> {
113        load_production_roots_impl(PRODUCTION_TRUST_ROOTS_JSON)
114    }
115
116    /// Create a trust store with the default production roots.
117    pub async fn with_production_roots() -> RegistryResult<Self> {
118        Self::from_production_roots()
119    }
120
121    /// Add a pinned root key.
122    pub async fn add_pinned_key(&self, key: &TrustedKey) -> RegistryResult<()> {
123        let prepared = prepare_pinned_key(key)?;
124        let mut inner = self.inner.write().await;
125        insert_prepared_pinned_key(&mut inner, prepared);
126        Ok(())
127    }
128
129    /// Add keys from a manifest (fetched from registry).
130    pub async fn add_from_manifest(&self, manifest: &KeysManifest) -> RegistryResult<()> {
131        let mut inner = self.inner.write().await;
132        manifest::add_from_manifest(&mut inner, manifest)
133    }
134
135    /// Get a key by ID.
136    pub async fn get_key_async(&self, key_id: &str) -> RegistryResult<VerifyingKey> {
137        let inner = self.inner.read().await;
138        access::get_key_inner(&inner, key_id)
139    }
140
141    /// Get a key by ID (blocking version for sync contexts).
142    pub fn get_key(&self, key_id: &str) -> RegistryResult<VerifyingKey> {
143        // Use try_read to avoid blocking
144        match self.inner.try_read() {
145            Ok(inner) => access::get_key_inner(&inner, key_id),
146            Err(_) => Err(RegistryError::KeyNotTrusted {
147                key_id: key_id.to_string(),
148            }),
149        }
150    }
151
152    /// Check if the keys manifest needs refresh.
153    pub async fn needs_refresh(&self) -> bool {
154        let inner = self.inner.read().await;
155        cache::needs_refresh(&inner)
156    }
157
158    /// Check if a key is trusted.
159    pub async fn is_trusted(&self, key_id: &str) -> bool {
160        self.get_key_async(key_id).await.is_ok()
161    }
162
163    /// Get all trusted key IDs.
164    pub async fn list_keys(&self) -> Vec<String> {
165        let inner = self.inner.read().await;
166        access::list_keys(&inner)
167    }
168
169    /// Get metadata for a key.
170    pub async fn get_metadata(&self, key_id: &str) -> Option<KeyMetadata> {
171        let inner = self.inner.read().await;
172        access::get_metadata(&inner, key_id)
173    }
174
175    /// Clear all non-pinned keys (for testing or force refresh).
176    pub async fn clear_cached_keys(&self) {
177        let mut inner = self.inner.write().await;
178        cache::clear_cached_keys(&mut inner);
179    }
180}
181
182impl Default for TrustStore {
183    fn default() -> Self {
184        Self::new()
185    }
186}
187
188#[cfg(test)]
189mod tests {
190    use super::*;
191    use ed25519_dalek::SigningKey;
192    use pkcs8::EncodePublicKey;
193
194    fn generate_trusted_key() -> (SigningKey, TrustedKey) {
195        let signing_key = SigningKey::generate(&mut rand::thread_rng());
196        let verifying_key = signing_key.verifying_key();
197
198        let spki_der = verifying_key.to_public_key_der().unwrap();
199        let public_key_b64 = BASE64.encode(spki_der.as_bytes());
200        let key_id = compute_key_id(spki_der.as_bytes());
201
202        let trusted = TrustedKey {
203            key_id,
204            algorithm: "Ed25519".to_string(),
205            public_key: public_key_b64,
206            description: Some("Test key".to_string()),
207            added_at: Some(Utc::now()),
208            expires_at: None,
209            revoked: false,
210        };
211
212        (signing_key, trusted)
213    }
214
215    #[tokio::test]
216    async fn test_empty_trust_store() {
217        let store = TrustStore::new();
218        let result = store.get_key_async("sha256:unknown").await;
219        assert!(matches!(result, Err(RegistryError::KeyNotTrusted { .. })));
220    }
221
222    #[tokio::test]
223    async fn test_with_production_roots_loads_embedded_roots() -> RegistryResult<()> {
224        let store = TrustStore::with_production_roots().await?;
225        let keys = store.list_keys().await;
226        assert_eq!(keys.len(), 1);
227        assert_eq!(
228            keys[0],
229            "sha256:3a64307d5655ba86fa3c95118ed8fe9665ef6bd37c752ca93f3bbe8f16e83a7f"
230        );
231
232        let meta = store
233            .get_metadata(&keys[0])
234            .await
235            .ok_or_else(|| RegistryError::Config {
236                message: "embedded production root metadata missing".to_string(),
237            })?;
238        assert!(meta.is_pinned);
239        assert!(!meta.revoked);
240        Ok(())
241    }
242
243    #[test]
244    fn test_parse_pinned_roots_json_rejects_empty_rootset() {
245        assert!(matches!(
246            parse_pinned_roots_json_impl("[]"),
247            Err(RegistryError::Config { .. })
248        ));
249    }
250
251    #[test]
252    fn test_parse_pinned_roots_json_rejects_duplicate_key_ids() {
253        let duplicate = r#"[
254            {
255                "key_id": "sha256:dup",
256                "algorithm": "Ed25519",
257                "public_key": "MCowBQYDK2VwAyEAykCN7Cf9EQAB4UPonG5AtKfTVny0H4xaKpPI6wIGBwE=",
258                "revoked": false
259            },
260            {
261                "key_id": "sha256:dup",
262                "algorithm": "Ed25519",
263                "public_key": "MCowBQYDK2VwAyEAykCN7Cf9EQAB4UPonG5AtKfTVny0H4xaKpPI6wIGBwE=",
264                "revoked": false
265            }
266        ]"#;
267
268        assert!(matches!(
269            parse_pinned_roots_json_impl(duplicate),
270            Err(RegistryError::Config { .. })
271        ));
272    }
273
274    #[test]
275    fn test_load_production_roots_maps_key_mismatch_to_config() {
276        let mismatched = r#"[
277            {
278                "key_id": "sha256:not-the-real-key-id",
279                "algorithm": "Ed25519",
280                "public_key": "MCowBQYDK2VwAyEAykCN7Cf9EQAB4UPonG5AtKfTVny0H4xaKpPI6wIGBwE=",
281                "revoked": false
282            }
283        ]"#;
284
285        let err = load_production_roots_impl(mismatched).unwrap_err();
286        assert!(matches!(err, RegistryError::Config { .. }));
287        assert!(err
288            .to_string()
289            .contains("invalid production trust root sha256:not-the-real-key-id"));
290    }
291
292    #[tokio::test]
293    async fn test_add_pinned_key() {
294        let store = TrustStore::new();
295        let (_signing_key, trusted) = generate_trusted_key();
296
297        store.add_pinned_key(&trusted).await.unwrap();
298
299        let key = store.get_key_async(&trusted.key_id).await.unwrap();
300        assert_eq!(key.as_bytes().len(), 32);
301
302        let meta = store.get_metadata(&trusted.key_id).await.unwrap();
303        assert!(meta.is_pinned);
304        assert!(!meta.revoked);
305    }
306
307    #[tokio::test]
308    async fn test_add_from_manifest() {
309        let store = TrustStore::new();
310        let (_, trusted1) = generate_trusted_key();
311        let (_, trusted2) = generate_trusted_key();
312
313        let manifest = KeysManifest {
314            version: 1,
315            keys: vec![trusted1.clone(), trusted2.clone()],
316            expires_at: Some(Utc::now() + chrono::Duration::hours(24)),
317        };
318
319        store.add_from_manifest(&manifest).await.unwrap();
320
321        assert!(store.is_trusted(&trusted1.key_id).await);
322        assert!(store.is_trusted(&trusted2.key_id).await);
323    }
324
325    #[tokio::test]
326    async fn test_revoked_key_in_manifest() {
327        let store = TrustStore::new();
328        let (_, mut trusted) = generate_trusted_key();
329        trusted.revoked = true;
330
331        let manifest = KeysManifest {
332            version: 1,
333            keys: vec![trusted.clone()],
334            expires_at: None,
335        };
336
337        store.add_from_manifest(&manifest).await.unwrap();
338
339        // Revoked key should not be added
340        assert!(!store.is_trusted(&trusted.key_id).await);
341    }
342
343    #[tokio::test]
344    async fn test_expired_key_in_manifest() {
345        let store = TrustStore::new();
346        let (_, mut trusted) = generate_trusted_key();
347        trusted.expires_at = Some(Utc::now() - chrono::Duration::hours(1));
348
349        let manifest = KeysManifest {
350            version: 1,
351            keys: vec![trusted.clone()],
352            expires_at: None,
353        };
354
355        store.add_from_manifest(&manifest).await.unwrap();
356
357        // Expired key should not be added
358        assert!(!store.is_trusted(&trusted.key_id).await);
359    }
360
361    #[tokio::test]
362    async fn test_pinned_key_not_overwritten() {
363        let store = TrustStore::new();
364        let (_, trusted) = generate_trusted_key();
365
366        // Add as pinned
367        store.add_pinned_key(&trusted).await.unwrap();
368
369        // Try to add revoked version via manifest
370        let mut revoked = trusted.clone();
371        revoked.revoked = true;
372
373        let manifest = KeysManifest {
374            version: 1,
375            keys: vec![revoked],
376            expires_at: None,
377        };
378
379        store.add_from_manifest(&manifest).await.unwrap();
380
381        // Should still be trusted (pinned cannot be revoked)
382        assert!(store.is_trusted(&trusted.key_id).await);
383        let meta = store.get_metadata(&trusted.key_id).await.unwrap();
384        assert!(meta.is_pinned);
385        assert!(!meta.revoked);
386    }
387
388    #[tokio::test]
389    async fn test_needs_refresh() {
390        let store = TrustStore::new();
391
392        // Empty store needs refresh
393        assert!(store.needs_refresh().await);
394
395        // Add manifest
396        let manifest = KeysManifest {
397            version: 1,
398            keys: vec![],
399            expires_at: Some(Utc::now() + chrono::Duration::hours(24)),
400        };
401        store.add_from_manifest(&manifest).await.unwrap();
402
403        // Should not need refresh
404        assert!(!store.needs_refresh().await);
405    }
406
407    #[tokio::test]
408    async fn test_clear_cached_keys() {
409        let store = TrustStore::new();
410        let (_, pinned) = generate_trusted_key();
411        let (_, cached) = generate_trusted_key();
412
413        // Add pinned key
414        store.add_pinned_key(&pinned).await.unwrap();
415
416        // Add cached key via manifest
417        let manifest = KeysManifest {
418            version: 1,
419            keys: vec![cached.clone()],
420            expires_at: None,
421        };
422        store.add_from_manifest(&manifest).await.unwrap();
423
424        assert!(store.is_trusted(&pinned.key_id).await);
425        assert!(store.is_trusted(&cached.key_id).await);
426
427        // Clear cached
428        store.clear_cached_keys().await;
429
430        // Pinned should remain, cached should be gone
431        assert!(store.is_trusted(&pinned.key_id).await);
432        assert!(!store.is_trusted(&cached.key_id).await);
433    }
434
435    #[tokio::test]
436    async fn test_list_keys() {
437        let store = TrustStore::new();
438        let (_, key1) = generate_trusted_key();
439        let (_, key2) = generate_trusted_key();
440
441        store.add_pinned_key(&key1).await.unwrap();
442        store.add_pinned_key(&key2).await.unwrap();
443
444        let keys = store.list_keys().await;
445        assert_eq!(keys.len(), 2);
446        assert!(keys.contains(&key1.key_id));
447        assert!(keys.contains(&key2.key_id));
448    }
449
450    #[tokio::test]
451    async fn test_key_id_mismatch_rejected() {
452        let store = TrustStore::new();
453        let (_, mut trusted) = generate_trusted_key();
454
455        // Corrupt the key_id
456        trusted.key_id =
457            "sha256:0000000000000000000000000000000000000000000000000000000000000000".to_string();
458
459        let result = store.add_pinned_key(&trusted).await;
460        assert!(matches!(
461            result,
462            Err(RegistryError::SignatureInvalid { .. })
463        ));
464    }
465
466    // ==================== Trust Rotation Tests ====================
467
468    #[tokio::test]
469    async fn test_trust_rotation_new_key_via_manifest() {
470        // Scenario: Root validates manifest A → manifest B adds new key → new key works
471        let store = TrustStore::new();
472
473        // Start with initial key (manifest A)
474        let (_, key_a) = generate_trusted_key();
475        let manifest_a = KeysManifest {
476            version: 1,
477            keys: vec![key_a.clone()],
478            expires_at: Some(Utc::now() + chrono::Duration::hours(24)),
479        };
480        store.add_from_manifest(&manifest_a).await.unwrap();
481        assert!(store.is_trusted(&key_a.key_id).await);
482
483        // Rotate: manifest B adds a new key
484        let (_, key_b) = generate_trusted_key();
485        let manifest_b = KeysManifest {
486            version: 1,
487            keys: vec![key_a.clone(), key_b.clone()], // Both keys
488            expires_at: Some(Utc::now() + chrono::Duration::hours(24)),
489        };
490        store.add_from_manifest(&manifest_b).await.unwrap();
491
492        // Both keys should now be trusted
493        assert!(store.is_trusted(&key_a.key_id).await);
494        assert!(store.is_trusted(&key_b.key_id).await);
495    }
496
497    #[tokio::test]
498    async fn test_trust_rotation_revoke_old_key() {
499        // Scenario: Key A active → manifest revokes key A → key A no longer trusted
500        let store = TrustStore::new();
501
502        // Start with key A
503        let (_, key_a) = generate_trusted_key();
504        let manifest_v1 = KeysManifest {
505            version: 1,
506            keys: vec![key_a.clone()],
507            expires_at: Some(Utc::now() + chrono::Duration::hours(24)),
508        };
509        store.add_from_manifest(&manifest_v1).await.unwrap();
510        assert!(store.is_trusted(&key_a.key_id).await);
511
512        // Manifest v2: key A is now revoked
513        let mut key_a_revoked = key_a.clone();
514        key_a_revoked.revoked = true;
515
516        let (_, key_b) = generate_trusted_key();
517        let manifest_v2 = KeysManifest {
518            version: 1,
519            keys: vec![key_a_revoked, key_b.clone()],
520            expires_at: Some(Utc::now() + chrono::Duration::hours(24)),
521        };
522        store.add_from_manifest(&manifest_v2).await.unwrap();
523
524        // Key A should no longer be trusted
525        assert!(!store.is_trusted(&key_a.key_id).await);
526        // Key B should be trusted
527        assert!(store.is_trusted(&key_b.key_id).await);
528    }
529
530    #[tokio::test]
531    async fn test_trust_rotation_pinned_root_survives_revocation() {
532        // Scenario: Pinned root cannot be revoked by manifest
533        let store = TrustStore::new();
534
535        // Add pinned root
536        let (_, pinned_root) = generate_trusted_key();
537        store.add_pinned_key(&pinned_root).await.unwrap();
538        assert!(store.is_trusted(&pinned_root.key_id).await);
539
540        // Manifest tries to revoke the pinned root
541        let mut revoked_root = pinned_root.clone();
542        revoked_root.revoked = true;
543
544        let manifest = KeysManifest {
545            version: 1,
546            keys: vec![revoked_root],
547            expires_at: Some(Utc::now() + chrono::Duration::hours(24)),
548        };
549        store.add_from_manifest(&manifest).await.unwrap();
550
551        // Pinned root MUST still be trusted (cannot be revoked remotely)
552        assert!(store.is_trusted(&pinned_root.key_id).await);
553        let meta = store.get_metadata(&pinned_root.key_id).await.unwrap();
554        assert!(meta.is_pinned);
555        assert!(!meta.revoked);
556    }
557
558    #[tokio::test]
559    async fn test_trust_rotation_expired_key_not_added() {
560        // Scenario: Manifest contains already-expired key → should not be added
561        let store = TrustStore::new();
562
563        let (_, mut expired_key) = generate_trusted_key();
564        expired_key.expires_at = Some(Utc::now() - chrono::Duration::hours(1)); // Expired 1 hour ago
565
566        let manifest = KeysManifest {
567            version: 1,
568            keys: vec![expired_key.clone()],
569            expires_at: Some(Utc::now() + chrono::Duration::hours(24)),
570        };
571        store.add_from_manifest(&manifest).await.unwrap();
572
573        // Expired key should NOT be trusted
574        assert!(!store.is_trusted(&expired_key.key_id).await);
575    }
576
577    #[tokio::test]
578    async fn test_trust_rotation_key_expires_after_added() {
579        // Scenario: Key added while valid, later becomes expired → should fail trust check
580        let store = TrustStore::new();
581
582        let (_, mut soon_to_expire) = generate_trusted_key();
583        // Set to expire in the past (simulating time passing)
584        soon_to_expire.expires_at = Some(Utc::now() - chrono::Duration::seconds(1));
585
586        // First add without expiry check (simulating it was valid when added)
587        // We need to manually add it to test the runtime check
588        let manifest = KeysManifest {
589            version: 1,
590            keys: vec![soon_to_expire.clone()],
591            expires_at: None,
592        };
593        // This won't add the key because it's already expired
594        store.add_from_manifest(&manifest).await.unwrap();
595
596        // The key should NOT be trusted because it was already expired when manifest was processed
597        assert!(!store.is_trusted(&soon_to_expire.key_id).await);
598    }
599}