ndn_identity/
identity.rs

1//! [`NdnIdentity`] — a named NDN identity with full lifecycle management.
2
3use std::{path::Path, sync::Arc};
4
5use ndn_security::KeyChain;
6use ndn_security::did::{UniversalResolver, name_to_did};
7
8use crate::{
9    device::DeviceConfig, enroll::EnrollConfig, error::IdentityError, renewal::RenewalHandle,
10};
11
12/// A named NDN identity with full lifecycle management.
13///
14/// `NdnIdentity` extends [`KeyChain`] with identity lifecycle operations:
15/// NDNCERT enrollment, fleet provisioning, DID-based trust, and background
16/// certificate renewal.
17///
18/// For the vast majority of applications — signing data and validating
19/// incoming packets — use [`KeyChain`] directly (available as
20/// `ndn_app::KeyChain` or `ndn_security::KeyChain`). Reach for `NdnIdentity`
21/// when you need:
22/// - [`enroll`] / [`provision`] — NDNCERT certificate issuance
23/// - [`from_did`] — trust bootstrapping from a DID document
24/// - [`did`] — `did:ndn` URI for this identity
25///
26/// [`enroll`]: Self::enroll
27/// [`provision`]: Self::provision
28/// [`from_did`]: Self::from_did
29/// [`did`]: Self::did
30pub struct NdnIdentity {
31    pub(crate) keychain: KeyChain,
32    #[allow(dead_code)]
33    pub(crate) renewal: Option<RenewalHandle>,
34}
35
36impl std::ops::Deref for NdnIdentity {
37    type Target = KeyChain;
38
39    fn deref(&self) -> &KeyChain {
40        &self.keychain
41    }
42}
43
44impl NdnIdentity {
45    // ── Constructors ──────────────────────────────────────────────────────────
46
47    /// Create an ephemeral, in-memory, self-signed identity.
48    ///
49    /// Suitable for testing and short-lived producers. Keys are not persisted.
50    pub fn ephemeral(name: impl AsRef<str>) -> Result<Self, IdentityError> {
51        let keychain = KeyChain::ephemeral(name)?;
52        Ok(Self {
53            keychain,
54            renewal: None,
55        })
56    }
57
58    /// Open a persistent identity from a PIB directory, creating it if absent.
59    ///
60    /// On first run, generates an Ed25519 key and self-signed certificate.
61    /// On subsequent runs, loads the existing key and certificate.
62    pub fn open_or_create(path: &Path, name: impl AsRef<str>) -> Result<Self, IdentityError> {
63        let keychain = KeyChain::open_or_create(path, name)?;
64        Ok(Self {
65            keychain,
66            renewal: None,
67        })
68    }
69
70    /// Enroll via NDNCERT using the given configuration.
71    ///
72    /// Performs the full NDNCERT exchange: INFO → NEW → CHALLENGE. The issued
73    /// certificate is persisted if `config.storage` is set.
74    pub async fn enroll(config: EnrollConfig) -> Result<Self, IdentityError> {
75        crate::enroll::run_enrollment(config).await
76    }
77
78    /// Zero-touch device provisioning.
79    ///
80    /// Selects a challenge type based on [`FactoryCredential`], enrolls with
81    /// the CA, and starts a background renewal task if requested.
82    ///
83    /// [`FactoryCredential`]: crate::device::FactoryCredential
84    pub async fn provision(config: DeviceConfig) -> Result<Self, IdentityError> {
85        crate::device::run_provisioning(config).await
86    }
87
88    /// Bootstrap trust from a DID document and create a local ephemeral identity
89    /// that trusts it.
90    ///
91    /// - `did:key:…` — public key used directly as a trust anchor.
92    /// - `did:ndn:…` / `did:web:…` — document resolved via `resolver`.
93    pub async fn from_did(
94        did: &str,
95        name: impl AsRef<str>,
96        resolver: &UniversalResolver,
97    ) -> Result<Self, IdentityError> {
98        let doc = resolver.resolve_document(did).await?;
99        let identity = Self::ephemeral(name)?;
100        if let Some(anchor) = ndn_security::did::did_document_to_trust_anchor(
101            &doc,
102            Arc::new(identity.keychain.name().clone()),
103        ) {
104            identity.keychain.add_trust_anchor(anchor);
105        }
106        Ok(identity)
107    }
108
109    // ── Internal constructor ──────────────────────────────────────────────────
110
111    /// Construct from a pre-built [`KeyChain`] and an optional renewal handle.
112    ///
113    /// Used by `enroll.rs` and `device.rs` which build the `SecurityManager`
114    /// directly before wrapping it.
115    pub(crate) fn from_keychain(keychain: KeyChain, renewal: Option<RenewalHandle>) -> Self {
116        Self { keychain, renewal }
117    }
118
119    // ── Identity-specific accessors ───────────────────────────────────────────
120
121    /// The `did:ndn` URI for this identity.
122    pub fn did(&self) -> String {
123        name_to_did(self.keychain.name())
124    }
125
126    /// Convert this `NdnIdentity` into the underlying [`KeyChain`].
127    ///
128    /// The renewal task (if any) is dropped and its background task cancelled.
129    pub fn into_keychain(self) -> KeyChain {
130        self.keychain
131    }
132}
133
134impl std::fmt::Debug for NdnIdentity {
135    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
136        f.debug_struct("NdnIdentity")
137            .field("name", &self.keychain.name().to_string())
138            .field("key_name", &self.keychain.key_name().to_string())
139            .finish()
140    }
141}