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}