1use std::{path::PathBuf, sync::Arc, time::Duration};
4
5use ndn_packet::Name;
6use ndn_security::{KeyChain, SecurityManager};
7
8use crate::{
9 enroll::{ChallengeParams, NdncertClient},
10 error::IdentityError,
11 identity::NdnIdentity,
12 renewal::start_renewal,
13};
14
15#[derive(Debug, Clone)]
17pub enum FactoryCredential {
18 Token(String),
20 DidKey(String),
22 Existing {
24 cert_name: String,
25 key_seed: [u8; 32],
26 },
27}
28
29#[derive(Debug, Clone)]
31pub enum RenewalPolicy {
32 WhenPercentRemaining(u8),
34 Every(Duration),
36 Manual,
38}
39
40impl Default for RenewalPolicy {
41 fn default() -> Self {
42 RenewalPolicy::WhenPercentRemaining(20)
43 }
44}
45
46pub struct DeviceConfig {
48 pub namespace: Name,
51 pub storage: Option<PathBuf>,
53 pub factory_credential: FactoryCredential,
55 pub ca_prefix: Option<Name>,
57 pub renewal: RenewalPolicy,
59 pub delegate: Vec<Name>,
62}
63
64pub async fn run_provisioning(config: DeviceConfig) -> Result<NdnIdentity, IdentityError> {
66 let ca_prefix = config
67 .ca_prefix
68 .clone()
69 .unwrap_or_else(|| derive_ca_prefix(&config.namespace));
70
71 let manager = if let Some(ref path) = config.storage {
73 let (mgr, _) = SecurityManager::auto_init(&config.namespace, path)?;
74 mgr
75 } else {
76 SecurityManager::new()
77 };
78
79 let key_name = config
81 .namespace
82 .clone()
83 .append("KEY")
84 .append_version(now_ms());
85 manager.generate_ed25519(key_name.clone())?;
86 let signer = manager.get_signer_sync(&key_name)?;
87 let pubkey_bytes = signer
88 .public_key()
89 .ok_or_else(|| IdentityError::Enrollment("signer has no public key".to_string()))?;
90
91 let manager = Arc::new(manager);
92
93 let challenge = build_challenge(&config.factory_credential, &key_name);
95
96 let socket = std::path::Path::new("/run/ndn/router.sock");
103 if !socket.exists() {
104 return Err(IdentityError::Enrollment(
105 "ZTP requires a running NDN router at /run/ndn/router.sock; \
106 use NdncertClient directly for custom connectivity"
107 .to_string(),
108 ));
109 }
110
111 let consumer = ndn_app::Consumer::connect(socket).await?;
112 let mut client = NdncertClient::new(consumer, ca_prefix);
113
114 let cert = client
115 .enroll(
116 key_name.clone(),
117 pubkey_bytes.to_vec(),
118 86400, challenge,
120 )
121 .await?;
122
123 manager.add_trust_anchor(cert);
124
125 let renewal = match &config.renewal {
127 RenewalPolicy::Manual => None,
128 policy => Some(start_renewal(
129 manager.clone(),
130 key_name.clone(),
131 config.namespace.clone(),
132 &policy.clone(),
133 config.storage.clone(),
134 )),
135 };
136
137 let keychain = KeyChain::from_parts(manager, config.namespace.clone(), key_name);
138 Ok(NdnIdentity::from_keychain(keychain, renewal))
139}
140
141fn build_challenge(credential: &FactoryCredential, _key_name: &Name) -> ChallengeParams {
142 match credential {
143 FactoryCredential::Token(token) => ChallengeParams::Token {
144 token: token.clone(),
145 },
146 FactoryCredential::DidKey(did) => {
147 ChallengeParams::Raw({
150 let mut m = serde_json::Map::new();
151 m.insert("did_key".to_string(), did.clone().into());
152 m
153 })
154 }
155 FactoryCredential::Existing {
156 cert_name,
157 key_seed,
158 } => {
159 use ndn_security::{Ed25519Signer, Signer};
161 let signer = Ed25519Signer::from_seed(
162 key_seed,
163 cert_name
164 .parse()
165 .unwrap_or_else(|_| ndn_packet::Name::root()),
166 );
167 let sig = signer.sign_sync(cert_name.as_bytes()).unwrap_or_default();
168 ChallengeParams::Possession {
169 cert_name: cert_name.clone(),
170 signature: sig.to_vec(),
171 }
172 }
173 }
174}
175
176fn derive_ca_prefix(namespace: &Name) -> Name {
177 let comps = namespace.components();
179 if comps.len() > 1 {
180 Name::from_components(comps[..comps.len() - 1].iter().cloned()).append("CA")
181 } else {
182 namespace.clone().append("CA")
183 }
184}
185
186fn now_ms() -> u64 {
187 use std::time::{SystemTime, UNIX_EPOCH};
188 SystemTime::now()
189 .duration_since(UNIX_EPOCH)
190 .unwrap_or_default()
191 .as_millis() as u64
192}