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}