ndn_security/
keychain.rs

1//! [`KeyChain`] — the primary security API for NDN applications.
2
3use std::path::Path;
4use std::sync::Arc;
5
6use ndn_packet::Name;
7
8use ndn_packet::SignatureType;
9use ndn_packet::encode::{DataBuilder, InterestBuilder};
10
11use crate::{CertCache, Certificate, SecurityManager, Signer, TrustError, TrustSchema, Validator};
12
13/// A named NDN identity with an associated signing key and trust anchors.
14///
15/// `KeyChain` is the single entry point for NDN security in both applications
16/// and the forwarder. It owns a signing key, a certificate cache, and a set of
17/// trust anchors, and exposes methods for signing packets and building validators.
18///
19/// # Constructors
20///
21/// - [`KeyChain::ephemeral`] — in-memory, self-signed; ideal for tests and
22///   short-lived producers.
23/// - [`KeyChain::open_or_create`] — file-backed PIB; generates a key on first
24///   run and reloads it on subsequent runs.
25/// - [`KeyChain::from_parts`] — construct from a pre-built [`SecurityManager`];
26///   intended for framework code (NDNCERT enrollment, device provisioning).
27///
28/// # Examples
29///
30/// ```rust,no_run
31/// use ndn_security::KeyChain;
32///
33/// // Ephemeral identity (testing / short-lived producers)
34/// let kc = KeyChain::ephemeral("/com/example/alice")?;
35/// let signer = kc.signer()?;
36///
37/// // Persistent identity
38/// let kc = KeyChain::open_or_create(
39///     std::path::Path::new("/var/lib/ndn"),
40///     "/com/example/alice",
41/// )?;
42/// # Ok::<(), ndn_security::TrustError>(())
43/// ```
44pub struct KeyChain {
45    pub(crate) mgr: Arc<SecurityManager>,
46    name: Name,
47    key_name: Name,
48}
49
50/// Default certificate validity (365 days in milliseconds).
51const DEFAULT_CERT_VALIDITY_MS: u64 = 365 * 24 * 3600 * 1_000;
52
53impl KeyChain {
54    // ── Constructors ──────────────────────────────────────────────────────────
55
56    /// Create an ephemeral, in-memory identity with a freshly generated Ed25519 key.
57    ///
58    /// The key is self-signed with a 365-day certificate and registered as a
59    /// trust anchor. Keys are not persisted — use [`open_or_create`] for
60    /// long-lived identities.
61    ///
62    /// [`open_or_create`]: Self::open_or_create
63    pub fn ephemeral(name: impl AsRef<str>) -> Result<Self, TrustError> {
64        let name: Name = name
65            .as_ref()
66            .parse()
67            .map_err(|_| TrustError::KeyStore(format!("invalid NDN name: {}", name.as_ref())))?;
68
69        let mgr = SecurityManager::new();
70        let key_name = name.clone().append("KEY").append("v=0");
71        mgr.generate_ed25519(key_name.clone())?;
72
73        let signer = mgr.get_signer_sync(&key_name)?;
74        let pubkey = signer.public_key().unwrap_or_default();
75        let cert = mgr.issue_self_signed(&key_name, pubkey, DEFAULT_CERT_VALIDITY_MS)?;
76        mgr.add_trust_anchor(cert);
77
78        Ok(Self {
79            mgr: Arc::new(mgr),
80            name,
81            key_name,
82        })
83    }
84
85    /// Open a persistent identity from a PIB directory, creating it if absent.
86    ///
87    /// On first run, generates an Ed25519 key and self-signed certificate.
88    /// On subsequent runs, loads the existing key and certificate from disk.
89    pub fn open_or_create(path: &Path, name: impl AsRef<str>) -> Result<Self, TrustError> {
90        let name: Name = name
91            .as_ref()
92            .parse()
93            .map_err(|_| TrustError::KeyStore(format!("invalid NDN name: {}", name.as_ref())))?;
94
95        let (mgr, _created) = SecurityManager::auto_init(&name, path)?;
96
97        let key_name = derive_key_name(&name, &mgr)
98            .unwrap_or_else(|| name.clone().append("KEY").append("v=0"));
99
100        Ok(Self {
101            mgr: Arc::new(mgr),
102            name,
103            key_name,
104        })
105    }
106
107    /// Construct a `KeyChain` from a pre-built `SecurityManager`.
108    ///
109    /// This is an escape hatch for framework code (NDNCERT enrollment, device
110    /// provisioning) that needs to build a `SecurityManager` before wrapping it.
111    /// Prefer [`ephemeral`] or [`open_or_create`] for application code.
112    ///
113    /// [`ephemeral`]: Self::ephemeral
114    /// [`open_or_create`]: Self::open_or_create
115    pub fn from_parts(mgr: Arc<SecurityManager>, name: Name, key_name: Name) -> Self {
116        Self {
117            mgr,
118            name,
119            key_name,
120        }
121    }
122
123    // ── Identity accessors ────────────────────────────────────────────────────
124
125    /// The NDN name of this identity (e.g. `/com/acme/alice`).
126    pub fn name(&self) -> &Name {
127        &self.name
128    }
129
130    /// The name of the active signing key (e.g. `/com/acme/alice/KEY/v=0`).
131    pub fn key_name(&self) -> &Name {
132        &self.key_name
133    }
134
135    // ── Security operations ───────────────────────────────────────────────────
136
137    /// Get the signer for this identity.
138    pub fn signer(&self) -> Result<Arc<dyn Signer>, TrustError> {
139        self.mgr.get_signer_sync(&self.key_name)
140    }
141
142    /// Build a [`Validator`] pre-configured with this identity's trust anchors.
143    ///
144    /// Uses [`TrustSchema::accept_all`] by default (any correctly-signed packet
145    /// whose certificate chain terminates in a known anchor is accepted). For
146    /// stricter namespace-based policy, call
147    /// [`Validator::set_schema`](crate::Validator::set_schema) on the result or
148    /// use [`TrustSchema::hierarchical`].
149    pub fn validator(&self) -> Validator {
150        let v = Validator::new(TrustSchema::accept_all());
151        for anchor_name in self.mgr.trust_anchor_names() {
152            if let Some(cert) = self.mgr.trust_anchor(&anchor_name) {
153                v.cert_cache().insert(cert);
154            }
155        }
156        v
157    }
158
159    /// Add an external trust anchor certificate.
160    ///
161    /// Use this to accept data signed by a CA that was not issued by this
162    /// identity (e.g., a network-wide trust anchor discovered via NDNCERT).
163    pub fn add_trust_anchor(&self, cert: Certificate) {
164        self.mgr.add_trust_anchor(cert);
165    }
166
167    /// Access the certificate cache.
168    ///
169    /// Useful for pre-populating the cache with known intermediate certificates
170    /// before validation.
171    pub fn cert_cache(&self) -> &CertCache {
172        self.mgr.cert_cache()
173    }
174
175    /// Build a [`Validator`] that trusts only certificates issued under `anchor_prefix`.
176    ///
177    /// Shorthand for creating a consumer-side validator when you know the
178    /// trust-anchor prefix and don't need a full KeyChain. For example, to
179    /// accept Data signed by any certificate under `/ndn/testbed`:
180    ///
181    /// ```rust
182    /// use ndn_security::KeyChain;
183    ///
184    /// let validator = KeyChain::trust_only("/ndn/testbed").unwrap();
185    /// ```
186    ///
187    /// Uses [`TrustSchema::hierarchical`] so the Data name must be a sub-name
188    /// of the signing certificate prefix.
189    pub fn trust_only(anchor_prefix: impl AsRef<str>) -> Result<Validator, TrustError> {
190        let prefix: Name = anchor_prefix.as_ref().parse().map_err(|_| {
191            TrustError::KeyStore(format!("invalid prefix: {}", anchor_prefix.as_ref()))
192        })?;
193        let kc = Self::ephemeral(anchor_prefix.as_ref())?;
194        let v = Validator::new(TrustSchema::hierarchical());
195        // Register the self-signed certificate as the trust anchor.
196        for anchor_name in kc.mgr.trust_anchor_names() {
197            if anchor_name.to_string().starts_with(&prefix.to_string())
198                && let Some(cert) = kc.mgr.trust_anchor(&anchor_name)
199            {
200                v.cert_cache().insert(cert);
201            }
202        }
203        Ok(v)
204    }
205
206    /// Sign a Data packet using this KeyChain's signing key.
207    ///
208    /// Returns the encoded, signed Data wire bytes. Uses Ed25519 with the
209    /// key locator set to this identity's key name.
210    ///
211    /// # Errors
212    ///
213    /// Returns [`TrustError`] if the signing key is not available.
214    pub fn sign_data(&self, builder: DataBuilder) -> Result<bytes::Bytes, TrustError> {
215        let signer = self.signer()?;
216        let key_name = self.key_name.clone();
217        Ok(
218            builder.sign_sync(SignatureType::SignatureEd25519, Some(&key_name), |region| {
219                signer.sign_sync(region).unwrap_or_default()
220            }),
221        )
222    }
223
224    /// Sign an Interest using this KeyChain's signing key.
225    ///
226    /// Returns the encoded, signed Interest wire bytes. Uses Ed25519 with the
227    /// key locator set to this identity's key name.
228    ///
229    /// # Errors
230    ///
231    /// Returns [`TrustError`] if the signing key is not available.
232    pub fn sign_interest(&self, builder: InterestBuilder) -> Result<bytes::Bytes, TrustError> {
233        let signer = self.signer()?;
234        let key_name = self.key_name.clone();
235        Ok(
236            builder.sign_sync(SignatureType::SignatureEd25519, Some(&key_name), |region| {
237                signer.sign_sync(region).unwrap_or_default()
238            }),
239        )
240    }
241
242    /// Build a [`Validator`] pre-configured with this identity's trust anchors.
243    ///
244    /// Alias for [`validator`](Self::validator). Provided for API symmetry with
245    /// the `trust_only` constructor.
246    pub fn build_validator(&self) -> Validator {
247        self.validator()
248    }
249
250    // ── Escape hatch ─────────────────────────────────────────────────────────
251
252    /// The `Arc`-wrapped `SecurityManager` backing this keychain.
253    ///
254    /// Intended for framework code (e.g., background renewal tasks) that needs
255    /// to share the manager across async tasks. Prefer the higher-level methods
256    /// for application code.
257    pub fn manager_arc(&self) -> Arc<SecurityManager> {
258        Arc::clone(&self.mgr)
259    }
260}
261
262impl std::fmt::Debug for KeyChain {
263    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
264        f.debug_struct("KeyChain")
265            .field("name", &self.name.to_string())
266            .field("key_name", &self.key_name.to_string())
267            .finish()
268    }
269}
270
271/// Derive the signing key name from the trust anchors already loaded into a
272/// `SecurityManager`.
273///
274/// Looks for an anchor whose name begins with `identity_name` and contains a
275/// `/KEY/` component.
276pub(crate) fn derive_key_name(identity_name: &Name, mgr: &SecurityManager) -> Option<Name> {
277    let name_str = identity_name.to_string();
278    for anchor_name in mgr.trust_anchor_names() {
279        let anchor_str = anchor_name.to_string();
280        if anchor_str.starts_with(&name_str)
281            && anchor_str.contains("/KEY/")
282            && let Ok(key_name) = anchor_str.parse::<Name>()
283        {
284            return Some(key_name);
285        }
286    }
287    None
288}
289
290#[cfg(test)]
291mod tests {
292    use super::*;
293
294    #[test]
295    fn ephemeral_generates_key_and_anchor() {
296        let kc = KeyChain::ephemeral("/test/alice").unwrap();
297        assert_eq!(kc.name().to_string(), "/test/alice");
298        assert!(kc.key_name().to_string().contains("/KEY/"));
299        assert!(kc.signer().is_ok());
300        // Trust anchor registered → validator can be built without panicking.
301        let _v = kc.validator();
302    }
303
304    #[test]
305    fn open_or_create_generates_on_empty_pib() {
306        let dir = tempfile::tempdir().unwrap();
307        // Pass a non-existent sub-path; auto_init will create the PIB there.
308        let pib_path = dir.path().join("pib");
309        let kc = KeyChain::open_or_create(&pib_path, "/test/router1").unwrap();
310        assert!(kc.signer().is_ok());
311
312        // Second call reloads, does not regenerate.
313        let kc2 = KeyChain::open_or_create(&pib_path, "/test/router1").unwrap();
314        assert_eq!(kc.key_name().to_string(), kc2.key_name().to_string(),);
315    }
316
317    #[test]
318    fn from_parts_roundtrip() {
319        let mgr = SecurityManager::new();
320        let name: Name = "/test/node".parse().unwrap();
321        let key_name: Name = "/test/node/KEY/v=0".parse().unwrap();
322        let kc = KeyChain::from_parts(Arc::new(mgr), name.clone(), key_name.clone());
323        assert_eq!(kc.name(), &name);
324        assert_eq!(kc.key_name(), &key_name);
325    }
326}