1use std::sync::Arc;
7use std::time::Duration;
8
9use ndn_cert::{CaConfig, CaState, ChallengeHandler, HierarchicalPolicy, NamespacePolicy};
10use ndn_packet::Name;
11use ndn_security::SecurityManager;
12use tracing::{debug, warn};
13
14use crate::{error::IdentityError, identity::NdnIdentity};
15
16pub struct NdncertCaBuilder {
18 prefix: Option<Name>,
19 info: String,
20 identity: Option<Arc<SecurityManager>>,
21 challenges: Vec<Box<dyn ChallengeHandler>>,
22 policy: Box<dyn NamespacePolicy>,
23 default_validity: Duration,
24 max_validity: Duration,
25}
26
27impl NdncertCaBuilder {
28 fn new() -> Self {
29 Self {
30 prefix: None,
31 info: "NDN Certificate Authority".to_string(),
32 identity: None,
33 challenges: Vec::new(),
34 policy: Box::new(HierarchicalPolicy),
35 default_validity: Duration::from_secs(86400), max_validity: Duration::from_secs(365 * 86400), }
38 }
39
40 pub fn name(mut self, prefix: impl AsRef<str>) -> Result<Self, IdentityError> {
41 let name: Name = prefix
42 .as_ref()
43 .parse()
44 .map_err(|_| IdentityError::Name(prefix.as_ref().to_string()))?;
45 self.prefix = Some(name);
46 Ok(self)
47 }
48
49 pub fn info(mut self, info: impl Into<String>) -> Self {
50 self.info = info.into();
51 self
52 }
53
54 pub fn signing_identity(mut self, identity: &NdnIdentity) -> Self {
55 self.identity = Some(identity.manager_arc());
56 self
57 }
58
59 pub fn challenge(mut self, handler: impl ChallengeHandler + 'static) -> Self {
60 self.challenges.push(Box::new(handler));
61 self
62 }
63
64 pub fn policy(mut self, policy: impl NamespacePolicy + 'static) -> Self {
65 self.policy = Box::new(policy);
66 self
67 }
68
69 pub fn cert_lifetime(mut self, d: Duration) -> Self {
70 self.default_validity = d;
71 self
72 }
73
74 pub fn max_cert_lifetime(mut self, d: Duration) -> Self {
75 self.max_validity = d;
76 self
77 }
78
79 pub fn build(self) -> Result<NdncertCa, IdentityError> {
80 let prefix = self
81 .prefix
82 .ok_or_else(|| IdentityError::Name("CA prefix not set".to_string()))?;
83 let manager = self.identity.ok_or(IdentityError::NotEnrolled)?;
84
85 if self.challenges.is_empty() {
86 return Err(IdentityError::Enrollment(
87 "at least one challenge handler is required".to_string(),
88 ));
89 }
90
91 let config = CaConfig {
92 prefix: prefix.clone(),
93 info: self.info,
94 default_validity: self.default_validity,
95 max_validity: self.max_validity,
96 challenges: self.challenges,
97 policy: self.policy,
98 };
99
100 Ok(NdncertCa {
101 state: Arc::new(CaState::new(config, manager)),
102 prefix,
103 })
104 }
105}
106
107pub struct NdncertCa {
112 state: Arc<CaState>,
113 prefix: Name,
114}
115
116impl NdncertCa {
117 pub fn builder() -> NdncertCaBuilder {
118 NdncertCaBuilder::new()
119 }
120
121 pub fn prefix(&self) -> &Name {
123 &self.prefix
124 }
125
126 pub async fn serve(self, producer: ndn_app::Producer) -> Result<(), IdentityError> {
130 let state = self.state.clone();
131 let ca_prefix = self.prefix.clone();
132
133 producer
134 .serve(move |interest, responder| {
135 let state = state.clone();
136 let ca_prefix = ca_prefix.clone();
137 async move {
138 if let Some(wire) = handle_interest(&state, &ca_prefix, interest).await {
139 responder.respond_bytes(wire).await.ok();
140 }
141 }
142 })
143 .await?;
144
145 Ok(())
146 }
147}
148
149async fn handle_interest(
150 state: &CaState,
151 ca_prefix: &Name,
152 interest: ndn_packet::Interest,
153) -> Option<bytes::Bytes> {
154 let name = &*interest.name;
155 let name_str = name.to_string();
156 let ca_prefix_str = ca_prefix.to_string();
157
158 debug!(name = %name_str, "NDNCERT: received Interest");
159
160 let suffix = name_str.strip_prefix(&ca_prefix_str).unwrap_or(&name_str);
161
162 if suffix == "/CA/INFO" || suffix.ends_with("/CA/INFO") {
163 let body = state.handle_info();
164 return Some(bytes::Bytes::from(body));
165 }
166
167 if suffix.contains("/CA/NEW") {
168 let body = interest.app_parameters().cloned().unwrap_or_default();
169 match state.handle_new(&body).await {
170 Ok(resp) => return Some(bytes::Bytes::from(resp)),
171 Err(e) => {
172 warn!(error = %e, "NDNCERT NEW failed");
173 return None;
174 }
175 }
176 }
177
178 if suffix.contains("/CA/CHALLENGE/") {
179 let request_id = name
181 .components()
182 .last()
183 .and_then(|c| std::str::from_utf8(&c.value).ok())
184 .map(|s| s.to_string())?;
185
186 let body = interest.app_parameters().cloned().unwrap_or_default();
187 match state.handle_challenge(&request_id, &body).await {
188 Ok(resp) => return Some(bytes::Bytes::from(resp)),
189 Err(e) => {
190 warn!(error = %e, "NDNCERT CHALLENGE failed");
191 return None;
192 }
193 }
194 }
195
196 warn!(name = %name_str, "NDNCERT: unrecognised Interest");
197 None
198}