1use std::{
9 sync::Arc,
10 time::{Duration, SystemTime, UNIX_EPOCH},
11};
12
13use base64::Engine;
14use dashmap::{DashMap, DashSet};
15use ndn_security::{Certificate, SecurityManager};
16
17use crate::{
18 challenge::{ChallengeHandler, ChallengeOutcome, ChallengeState},
19 ecdh::{EcdhKeypair, SessionKey},
20 error::CertError,
21 policy::{NamespacePolicy, PolicyDecision},
22 protocol::CertRequest,
23 tlv::{
24 CaProfileTlv, ChallengeResponseTlv, NewRequestTlv, NewResponseTlv, ProbeResponseTlv,
25 REVOKE_STATUS_NOT_FOUND, REVOKE_STATUS_REVOKED, REVOKE_STATUS_UNAUTHORIZED,
26 RevokeRequestTlv, RevokeResponseTlv, STATUS_FAILURE, STATUS_PENDING, STATUS_SUCCESS,
27 },
28};
29
30pub struct CaConfig {
32 pub prefix: ndn_packet::Name,
34 pub info: String,
36 pub default_validity: Duration,
38 pub max_validity: Duration,
40 pub challenges: Vec<Box<dyn ChallengeHandler>>,
42 pub policy: Box<dyn NamespacePolicy>,
44}
45
46struct PendingRequest {
48 cert_request: CertRequest,
49 challenge_state: Option<ChallengeState>,
52 challenge_type: Option<String>,
54 created_at: u64,
55 session_key: SessionKey,
57 request_id_bytes: [u8; 8],
59}
60
61pub struct CaState {
66 config: CaConfig,
67 manager: Arc<SecurityManager>,
68 pending: DashMap<String, PendingRequest>,
69 revoked: DashSet<String>,
71}
72
73impl CaState {
74 pub fn new(config: CaConfig, manager: Arc<SecurityManager>) -> Self {
75 Self {
76 config,
77 manager,
78 pending: DashMap::new(),
79 revoked: DashSet::new(),
80 }
81 }
82
83 pub fn cleanup_expired(&self, ttl_secs: u64) {
88 let cutoff = now_secs().saturating_sub(ttl_secs);
89 self.pending.retain(|_, v| v.created_at >= cutoff);
90 }
91
92 pub fn is_revoked(&self, cert_name: &str) -> bool {
94 self.revoked.contains(cert_name)
95 }
96
97 pub fn handle_info(&self) -> Vec<u8> {
99 let ca_certificate = self
100 .manager
101 .trust_anchor_names()
102 .first()
103 .and_then(|name| self.manager.trust_anchor(name))
104 .map(|cert| bytes::Bytes::from(serialize_cert(&cert)))
105 .unwrap_or_else(|| {
106 tracing::warn!(
107 "CA has no trust anchor configured; INFO response has empty ca_certificate"
108 );
109 bytes::Bytes::new()
110 });
111
112 let profile = CaProfileTlv {
113 ca_prefix: self.config.prefix.to_string(),
114 ca_info: self.config.info.clone(),
115 ca_certificate,
116 max_validity_secs: self.config.max_validity.as_secs(),
117 challenges: self
118 .config
119 .challenges
120 .iter()
121 .map(|c| c.challenge_type().to_string())
122 .collect(),
123 };
124 profile.encode().to_vec()
125 }
126
127 pub fn handle_probe(&self, requested_name: &str) -> Vec<u8> {
132 let result: Result<ndn_packet::Name, _> = requested_name.parse();
133 let resp = match result {
134 Err(_) => ProbeResponseTlv {
135 allowed: false,
136 reason: Some(format!("invalid NDN name: {requested_name}")),
137 max_suffix_length: None,
138 },
139 Ok(name) => match self
140 .config
141 .policy
142 .evaluate(&name, None, &self.config.prefix)
143 {
144 PolicyDecision::Allow => ProbeResponseTlv {
145 allowed: true,
146 reason: None,
147 max_suffix_length: None,
148 },
149 PolicyDecision::Deny(reason) => ProbeResponseTlv {
150 allowed: false,
151 reason: Some(reason),
152 max_suffix_length: None,
153 },
154 },
155 };
156 resp.encode().to_vec()
157 }
158
159 pub async fn handle_new(&self, body: &[u8]) -> Result<Vec<u8>, CertError> {
164 self.cleanup_expired(60);
166
167 let new_req = NewRequestTlv::decode(bytes::Bytes::copy_from_slice(body))?;
169
170 let req = decode_cert_request_bytes(&new_req.cert_request)?;
172
173 let name: ndn_packet::Name = req
175 .name
176 .parse()
177 .map_err(|_| CertError::Name(format!("invalid name: {}", req.name)))?;
178
179 match self
180 .config
181 .policy
182 .evaluate(&name, None, &self.config.prefix)
183 {
184 PolicyDecision::Allow => {}
185 PolicyDecision::Deny(reason) => return Err(CertError::PolicyDenied(reason)),
186 }
187
188 if self.config.challenges.is_empty() {
189 return Err(CertError::InvalidRequest(
190 "CA has no challenge handlers".to_string(),
191 ));
192 }
193
194 let ca_kp = EcdhKeypair::generate();
196 let ca_pub_bytes = ca_kp.public_key_bytes();
197 let salt = EcdhKeypair::random_salt();
198 let request_id_bytes = generate_request_id_bytes();
199
200 let session_key = ca_kp.derive_session_key(&new_req.ecdh_pub, &salt, &request_id_bytes)?;
201
202 let request_id_hex = bytes_to_hex(&request_id_bytes);
203
204 self.pending.insert(
206 request_id_hex,
207 PendingRequest {
208 cert_request: req,
209 challenge_state: None,
210 challenge_type: None,
211 created_at: now_secs(),
212 session_key,
213 request_id_bytes,
214 },
215 );
216
217 let resp = NewResponseTlv {
218 ecdh_pub: bytes::Bytes::from(ca_pub_bytes),
219 salt,
220 request_id: request_id_bytes,
221 challenges: self
222 .config
223 .challenges
224 .iter()
225 .map(|c| c.challenge_type().to_string())
226 .collect(),
227 };
228 Ok(resp.encode().to_vec())
229 }
230
231 pub async fn handle_challenge(
236 &self,
237 request_id: &str,
238 body: &[u8],
239 ) -> Result<Vec<u8>, CertError> {
240 use crate::tlv::ChallengeRequestTlv;
241
242 let chal_tlv = ChallengeRequestTlv::decode(bytes::Bytes::copy_from_slice(body))?;
243
244 let req_id_from_tlv = bytes_to_hex(&chal_tlv.request_id);
246 if req_id_from_tlv != request_id {
247 return Err(CertError::InvalidRequest(
248 "request_id in TLV does not match Interest name".into(),
249 ));
250 }
251
252 let (cert_request, existing_state, existing_type, session_key, request_id_bytes) = {
254 let pending = self
255 .pending
256 .get(request_id)
257 .ok_or_else(|| CertError::RequestNotFound(request_id.to_string()))?;
258 (
259 pending.cert_request.clone(),
260 pending.challenge_state.clone(),
261 pending.challenge_type.clone(),
262 pending.session_key.clone(),
263 pending.request_id_bytes,
264 )
265 };
266
267 let params_json = session_key.decrypt(
269 &chal_tlv.iv,
270 &chal_tlv.encrypted_payload,
271 &chal_tlv.auth_tag,
272 &request_id_bytes,
273 )?;
274 let parameters: serde_json::Map<String, serde_json::Value> =
275 serde_json::from_slice(¶ms_json)?;
276
277 let challenge_type = &chal_tlv.selected_challenge;
278
279 if let Some(ref locked_type) = existing_type
281 && locked_type != challenge_type
282 {
283 return Err(CertError::InvalidRequest(format!(
284 "challenge type locked to '{}' for this request",
285 locked_type
286 )));
287 }
288
289 let handler = self
291 .config
292 .challenges
293 .iter()
294 .find(|h| h.challenge_type() == challenge_type)
295 .ok_or_else(|| {
296 CertError::InvalidRequest(format!("unsupported challenge type: {challenge_type}",))
297 })?;
298
299 let state = match existing_state {
301 Some(s) => s,
302 None => {
303 let s = handler.begin(&cert_request).await?;
304 if let Some(mut entry) = self.pending.get_mut(request_id) {
305 entry.challenge_state = Some(s.clone());
306 entry.challenge_type = Some(challenge_type.clone());
307 }
308 s
309 }
310 };
311
312 let outcome = handler.verify(&state, ¶meters).await?;
313
314 match outcome {
315 ChallengeOutcome::Denied(reason) => {
316 self.pending.remove(request_id);
317 Ok(ChallengeResponseTlv {
318 status: STATUS_FAILURE,
319 challenge_status: None,
320 remaining_tries: None,
321 remaining_time_secs: None,
322 issued_cert_name: None,
323 error_code: Some(7), error_info: Some(reason),
325 iv: None,
326 encrypted_payload: None,
327 auth_tag: None,
328 }
329 .encode()
330 .to_vec())
331 }
332
333 ChallengeOutcome::Pending {
334 status_message,
335 remaining_tries,
336 remaining_time_secs,
337 next_state,
338 } => {
339 if let Some(mut entry) = self.pending.get_mut(request_id) {
340 entry.challenge_state = Some(next_state);
341 }
342 Ok(ChallengeResponseTlv {
343 status: STATUS_PENDING,
344 challenge_status: Some(status_message),
345 remaining_tries: Some(remaining_tries),
346 remaining_time_secs: Some(remaining_time_secs),
347 issued_cert_name: None,
348 error_code: None,
349 error_info: None,
350 iv: None,
351 encrypted_payload: None,
352 auth_tag: None,
353 }
354 .encode()
355 .to_vec())
356 }
357
358 ChallengeOutcome::Approved => {
359 let (_, pending) = self
360 .pending
361 .remove(request_id)
362 .ok_or_else(|| CertError::RequestNotFound(request_id.to_string()))?;
363
364 let cert = self.issue_certificate(&pending.cert_request).await?;
365 let cert_name = cert.name.to_string();
366 let cert_bytes = serialize_cert(&cert);
367
368 Ok(ChallengeResponseTlv {
371 status: STATUS_SUCCESS,
372 challenge_status: None,
373 remaining_tries: None,
374 remaining_time_secs: None,
375 issued_cert_name: Some(cert_name),
376 error_code: None,
377 error_info: None,
378 iv: None,
379 encrypted_payload: Some(bytes::Bytes::from(cert_bytes)),
380 auth_tag: None,
381 }
382 .encode()
383 .to_vec())
384 }
385 }
386 }
387
388 pub async fn handle_revoke(&self, body: &[u8]) -> Vec<u8> {
393 let status = self.do_revoke(body).await;
394 RevokeResponseTlv {
395 status,
396 reason: None,
397 }
398 .encode()
399 .to_vec()
400 }
401
402 async fn do_revoke(&self, body: &[u8]) -> u8 {
403 let req = match RevokeRequestTlv::decode(bytes::Bytes::copy_from_slice(body)) {
404 Ok(r) => r,
405 Err(_) => return REVOKE_STATUS_UNAUTHORIZED,
406 };
407
408 let cert_name_parsed: ndn_packet::Name = match req.cert_name.parse() {
410 Ok(n) => n,
411 Err(_) => return REVOKE_STATUS_NOT_FOUND,
412 };
413
414 let anchor = self.manager.trust_anchor(&cert_name_parsed);
415 let public_key = match anchor {
416 Some(c) => c.public_key,
417 None => return REVOKE_STATUS_NOT_FOUND,
418 };
419
420 use ndn_security::{Ed25519Verifier, Verifier, VerifyOutcome};
422 let outcome = Ed25519Verifier
423 .verify(req.cert_name.as_bytes(), &req.signature, &public_key)
424 .await;
425
426 match outcome {
427 Ok(VerifyOutcome::Valid) => {
428 self.revoked.insert(req.cert_name);
429 REVOKE_STATUS_REVOKED
430 }
431 _ => REVOKE_STATUS_UNAUTHORIZED,
432 }
433 }
434
435 async fn issue_certificate(&self, req: &CertRequest) -> Result<Certificate, CertError> {
437 let subject_name: ndn_packet::Name = req
438 .name
439 .parse()
440 .map_err(|_| CertError::Name(req.name.clone()))?;
441
442 let public_key = base64::engine::general_purpose::URL_SAFE_NO_PAD
443 .decode(&req.public_key)
444 .map_err(|_| CertError::InvalidRequest("invalid public key base64".to_string()))?;
445
446 if self.is_revoked(&req.name) {
448 return Err(CertError::PolicyDenied(format!(
449 "certificate {} has been revoked",
450 req.name
451 )));
452 }
453
454 let ca_key_names = self.manager.trust_anchor_names();
456 let ca_key_name = ca_key_names.first().ok_or_else(|| {
457 CertError::InvalidRequest("CA has no signing key configured".to_string())
458 })?;
459
460 let max_validity_ms = self.config.max_validity.as_millis() as u64;
463 let requested_ms = req.not_after.saturating_sub(req.not_before);
464 let validity_ms = if requested_ms > 0 {
465 requested_ms.min(max_validity_ms)
466 } else {
467 self.config
468 .default_validity
469 .as_millis()
470 .min(self.config.max_validity.as_millis()) as u64
471 };
472
473 let cert = self
474 .manager
475 .certify(
476 &subject_name,
477 bytes::Bytes::from(public_key),
478 ca_key_name.as_ref(),
479 validity_ms,
480 )
481 .await
482 .map_err(CertError::Security)?;
483
484 Ok(cert)
485 }
486}
487
488fn serialize_cert(cert: &Certificate) -> Vec<u8> {
493 let name_bytes = cert.name.to_string().into_bytes();
494 let mut out = Vec::new();
495 out.extend_from_slice(&cert.valid_from.to_be_bytes());
496 out.extend_from_slice(&cert.valid_until.to_be_bytes());
497 out.extend_from_slice(&(cert.public_key.len() as u32).to_be_bytes());
498 out.extend_from_slice(&cert.public_key);
499 out.extend_from_slice(&(name_bytes.len() as u32).to_be_bytes());
500 out.extend_from_slice(&name_bytes);
501 out
502}
503
504pub fn deserialize_cert(data: &[u8]) -> Option<Certificate> {
506 if data.len() < 20 {
507 return None;
508 }
509 let valid_from = u64::from_be_bytes(data[0..8].try_into().ok()?);
510 let valid_until = u64::from_be_bytes(data[8..16].try_into().ok()?);
511 let pk_len = u32::from_be_bytes(data[16..20].try_into().ok()?) as usize;
512 if data.len() < 20 + pk_len + 4 {
513 return None;
514 }
515 let public_key = bytes::Bytes::copy_from_slice(&data[20..20 + pk_len]);
516 let name_len = u32::from_be_bytes(data[20 + pk_len..24 + pk_len].try_into().ok()?) as usize;
517 let name_bytes = data.get(24 + pk_len..24 + pk_len + name_len)?;
518 let name_str = std::str::from_utf8(name_bytes).ok()?;
519 let name: ndn_packet::Name = name_str.parse().ok()?;
520 Some(Certificate {
521 name: std::sync::Arc::new(name),
522 public_key,
523 valid_from,
524 valid_until,
525 issuer: None,
526 signed_region: None,
527 sig_value: None,
528 })
529}
530
531fn generate_request_id_bytes() -> [u8; 8] {
532 use ring::rand::{SecureRandom, SystemRandom};
533 let rng = SystemRandom::new();
534 let mut bytes = [0u8; 8];
535 rng.fill(&mut bytes).unwrap_or(());
536 bytes
537}
538
539fn bytes_to_hex(bytes: &[u8; 8]) -> String {
540 bytes.iter().map(|b| format!("{b:02x}")).collect()
541}
542
543#[cfg(test)]
548pub(crate) fn decode_cert_request_bytes_pub(data: &[u8]) -> Result<CertRequest, CertError> {
549 decode_cert_request_bytes(data)
550}
551
552fn decode_cert_request_bytes(data: &[u8]) -> Result<CertRequest, CertError> {
553 if data.len() < 20 {
554 return Err(CertError::InvalidRequest("cert request too short".into()));
555 }
556 let not_before = u64::from_be_bytes(data[0..8].try_into().unwrap());
557 let not_after = u64::from_be_bytes(data[8..16].try_into().unwrap());
558 let pk_len = u32::from_be_bytes(data[16..20].try_into().unwrap()) as usize;
559 if data.len() < 20 + pk_len + 4 {
560 return Err(CertError::InvalidRequest(
561 "cert request truncated at pubkey".into(),
562 ));
563 }
564 let public_key =
565 base64::engine::general_purpose::URL_SAFE_NO_PAD.encode(&data[20..20 + pk_len]);
566 let name_len = u32::from_be_bytes(data[20 + pk_len..24 + pk_len].try_into().unwrap()) as usize;
567 if data.len() < 24 + pk_len + name_len {
568 return Err(CertError::InvalidRequest(
569 "cert request truncated at name".into(),
570 ));
571 }
572 let name = std::str::from_utf8(&data[24 + pk_len..24 + pk_len + name_len])
573 .map_err(|_| CertError::InvalidRequest("invalid name UTF-8 in cert request".into()))?
574 .to_string();
575 Ok(CertRequest {
576 name,
577 public_key,
578 not_before,
579 not_after,
580 })
581}
582
583fn now_secs() -> u64 {
584 SystemTime::now()
585 .duration_since(UNIX_EPOCH)
586 .unwrap_or_default()
587 .as_secs()
588}
589
590#[allow(dead_code)]
591fn now_ms() -> u64 {
592 SystemTime::now()
593 .duration_since(UNIX_EPOCH)
594 .unwrap_or_default()
595 .as_millis() as u64
596}