1use 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
23const 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#[derive(Debug, Clone)]
41pub struct TrustStore {
42 inner: Arc<RwLock<TrustStoreInner>>,
43}
44
45#[derive(Debug)]
46struct TrustStoreInner {
47 keys: HashMap<String, VerifyingKey>,
49
50 metadata: HashMap<String, KeyMetadata>,
52
53 pinned_roots: Vec<String>,
55
56 manifest_fetched_at: Option<DateTime<Utc>>,
58
59 manifest_expires_at: Option<DateTime<Utc>>,
61}
62
63#[derive(Debug, Clone)]
65pub struct KeyMetadata {
66 pub description: Option<String>,
68
69 pub added_at: Option<DateTime<Utc>>,
71
72 pub expires_at: Option<DateTime<Utc>>,
74
75 pub revoked: bool,
77
78 pub is_pinned: bool,
80}
81
82impl TrustStore {
83 pub fn new() -> Self {
85 Self {
86 inner: Arc::new(RwLock::new(cache::empty_inner())),
87 }
88 }
89
90 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 pub async fn with_pinned_roots(roots: Vec<TrustedKey>) -> RegistryResult<Self> {
108 Self::from_pinned_roots(roots)
109 }
110
111 pub fn from_production_roots() -> RegistryResult<Self> {
113 load_production_roots_impl(PRODUCTION_TRUST_ROOTS_JSON)
114 }
115
116 pub async fn with_production_roots() -> RegistryResult<Self> {
118 Self::from_production_roots()
119 }
120
121 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 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 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 pub fn get_key(&self, key_id: &str) -> RegistryResult<VerifyingKey> {
143 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 pub async fn needs_refresh(&self) -> bool {
154 let inner = self.inner.read().await;
155 cache::needs_refresh(&inner)
156 }
157
158 pub async fn is_trusted(&self, key_id: &str) -> bool {
160 self.get_key_async(key_id).await.is_ok()
161 }
162
163 pub async fn list_keys(&self) -> Vec<String> {
165 let inner = self.inner.read().await;
166 access::list_keys(&inner)
167 }
168
169 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 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 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 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 store.add_pinned_key(&trusted).await.unwrap();
368
369 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 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 assert!(store.needs_refresh().await);
394
395 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 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 store.add_pinned_key(&pinned).await.unwrap();
415
416 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 store.clear_cached_keys().await;
429
430 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 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 #[tokio::test]
469 async fn test_trust_rotation_new_key_via_manifest() {
470 let store = TrustStore::new();
472
473 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 let (_, key_b) = generate_trusted_key();
485 let manifest_b = KeysManifest {
486 version: 1,
487 keys: vec![key_a.clone(), key_b.clone()], expires_at: Some(Utc::now() + chrono::Duration::hours(24)),
489 };
490 store.add_from_manifest(&manifest_b).await.unwrap();
491
492 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 let store = TrustStore::new();
501
502 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 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 assert!(!store.is_trusted(&key_a.key_id).await);
526 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 let store = TrustStore::new();
534
535 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 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 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 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)); 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 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 let store = TrustStore::new();
581
582 let (_, mut soon_to_expire) = generate_trusted_key();
583 soon_to_expire.expires_at = Some(Utc::now() - chrono::Duration::seconds(1));
585
586 let manifest = KeysManifest {
589 version: 1,
590 keys: vec![soon_to_expire.clone()],
591 expires_at: None,
592 };
593 store.add_from_manifest(&manifest).await.unwrap();
595
596 assert!(!store.is_trusted(&soon_to_expire.key_id).await);
598 }
599}