Skip to main content

lean_ctx/core/policy/org/
trust.rs

1//! Org key trust store — file-backed pinning (ADR-023, GL #674).
2//!
3//! Keys are pinned to `<data_dir>/org-trust.toml`. A pinned key means the
4//! endpoint trusts artifacts signed by that org. Without any pinned keys,
5//! [`active_resolved`](super::active_resolved) returns `None` and no org
6//! policy is enforced (fail-open by default).
7
8use std::path::PathBuf;
9
10use serde::{Deserialize, Serialize};
11
12#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
13pub struct TrustedKey {
14    pub org: String,
15    pub public_key: String,
16    pub added_at: String,
17}
18
19#[derive(Clone, Debug, Default, Serialize, Deserialize)]
20pub struct TrustStore {
21    pub keys: Vec<TrustedKey>,
22}
23
24pub fn trust_path() -> Result<PathBuf, String> {
25    let dir = crate::core::paths::data_dir()?;
26    Ok(dir.join("org-trust.toml"))
27}
28
29pub fn load() -> Result<TrustStore, String> {
30    let path = trust_path()?;
31    if !path.exists() {
32        return Ok(TrustStore::default());
33    }
34    let text =
35        std::fs::read_to_string(&path).map_err(|e| format!("read {}: {e}", path.display()))?;
36    toml::from_str(&text).map_err(|e| format!("parse {}: {e}", path.display()))
37}
38
39pub fn save(store: &TrustStore) -> Result<(), String> {
40    let path = trust_path()?;
41    if let Some(parent) = path.parent() {
42        std::fs::create_dir_all(parent).map_err(|e| format!("mkdir {}: {e}", parent.display()))?;
43    }
44    let text = toml::to_string_pretty(store).map_err(|e| format!("serialize trust store: {e}"))?;
45    std::fs::write(&path, text).map_err(|e| format!("write {}: {e}", path.display()))
46}
47
48pub fn pin(org: &str, public_key: &str) -> Result<bool, String> {
49    let mut store = load()?;
50    if store.keys.iter().any(|k| k.public_key == public_key) {
51        return Ok(false);
52    }
53    store.keys.push(TrustedKey {
54        org: org.to_string(),
55        public_key: public_key.to_string(),
56        added_at: chrono::Utc::now().to_rfc3339(),
57    });
58    save(&store)?;
59    Ok(true)
60}
61
62pub fn remove(public_key: &str) -> Result<bool, String> {
63    let mut store = load()?;
64    let before = store.keys.len();
65    store.keys.retain(|k| k.public_key != public_key);
66    if store.keys.len() < before {
67        save(&store)?;
68        Ok(true)
69    } else {
70        Ok(false)
71    }
72}
73
74pub fn trusted_keys() -> Vec<TrustedKey> {
75    load().unwrap_or_default().keys
76}
77
78pub fn is_trusted(public_key: &str) -> bool {
79    load()
80        .map(|s| s.keys.iter().any(|k| k.public_key == public_key))
81        .unwrap_or(false)
82}
83
84pub fn any_pinned() -> bool {
85    load().map(|s| !s.keys.is_empty()).unwrap_or(false)
86}