1use std::path::{Path, PathBuf};
2use std::sync::Arc;
3
4use bytes::Bytes;
5use thiserror::Error;
6
7use ndn_packet::{Name, NameComponent};
8
9use crate::{TrustError, cert_cache::Certificate, signer::Ed25519Signer};
10
11#[derive(Debug, Error)]
14pub enum PibError {
15 #[error("I/O error: {0}")]
16 Io(#[from] std::io::Error),
17 #[error("key not found in PIB: {0}")]
18 KeyNotFound(String),
19 #[error("certificate not found in PIB: {0}")]
20 CertNotFound(String),
21 #[error("corrupt PIB data: {0}")]
22 Corrupt(String),
23 #[error("invalid name")]
24 InvalidName,
25}
26
27impl From<PibError> for TrustError {
28 fn from(e: PibError) -> Self {
29 TrustError::KeyStore(e.to_string())
30 }
31}
32
33pub struct FilePib {
63 root: PathBuf,
64}
65
66impl FilePib {
67 pub fn new(root: impl Into<PathBuf>) -> Result<Self, PibError> {
69 let root = root.into();
70 std::fs::create_dir_all(root.join("keys"))?;
71 std::fs::create_dir_all(root.join("anchors"))?;
72 Ok(Self { root })
73 }
74
75 pub fn open(root: impl Into<PathBuf>) -> Result<Self, PibError> {
78 let root = root.into();
79 if !root.join("keys").exists() {
80 return Err(PibError::Io(std::io::Error::new(
81 std::io::ErrorKind::NotFound,
82 format!(
83 "PIB not found at {} (run `ndn-sec keygen` to create one)",
84 root.display()
85 ),
86 )));
87 }
88 Ok(Self { root })
89 }
90
91 pub fn root(&self) -> &Path {
93 &self.root
94 }
95
96 pub fn generate_ed25519(&self, key_name: &Name) -> Result<Ed25519Signer, PibError> {
102 let seed = random_seed();
103 let signer = Ed25519Signer::from_seed(&seed, key_name.clone());
104 let dir = self.key_dir(key_name)?;
105 std::fs::write(dir.join("private.key"), seed)?;
106 std::fs::write(dir.join("name.uri"), name_to_uri(key_name))?;
107 Ok(signer)
108 }
109
110 pub fn get_signer(&self, key_name: &Name) -> Result<Ed25519Signer, PibError> {
112 let dir = self
113 .existing_key_dir(key_name)
114 .ok_or_else(|| PibError::KeyNotFound(name_to_uri(key_name)))?;
115 let seed_bytes = std::fs::read(dir.join("private.key"))?;
116 if seed_bytes.len() != 32 {
117 return Err(PibError::Corrupt(
118 "private.key must be exactly 32 bytes".into(),
119 ));
120 }
121 let mut seed = [0u8; 32];
122 seed.copy_from_slice(&seed_bytes);
123 Ok(Ed25519Signer::from_seed(&seed, key_name.clone()))
124 }
125
126 pub fn delete_key(&self, key_name: &Name) -> Result<(), PibError> {
128 if let Some(dir) = self.existing_key_dir(key_name) {
129 std::fs::remove_dir_all(dir)?;
130 }
131 Ok(())
132 }
133
134 pub fn list_keys(&self) -> Result<Vec<Name>, PibError> {
136 list_names_in(&self.root.join("keys"))
137 }
138
139 pub fn store_cert(&self, key_name: &Name, cert: &Certificate) -> Result<(), PibError> {
143 let dir = self.key_dir(key_name)?;
144 std::fs::write(dir.join("cert.ndnc"), encode_cert(cert))?;
145 Ok(())
146 }
147
148 pub fn get_cert(&self, key_name: &Name) -> Result<Certificate, PibError> {
150 let dir = self
151 .existing_key_dir(key_name)
152 .ok_or_else(|| PibError::CertNotFound(name_to_uri(key_name)))?;
153 let data = std::fs::read(dir.join("cert.ndnc"))
154 .map_err(|_| PibError::CertNotFound(name_to_uri(key_name)))?;
155 decode_cert(Arc::new(key_name.clone()), &data)
156 }
157
158 pub fn add_trust_anchor(&self, key_name: &Name, cert: &Certificate) -> Result<(), PibError> {
162 let dir = self.anchor_dir(key_name)?;
163 std::fs::write(dir.join("cert.ndnc"), encode_cert(cert))?;
164 std::fs::write(dir.join("name.uri"), name_to_uri(key_name))?;
165 Ok(())
166 }
167
168 pub fn remove_trust_anchor(&self, key_name: &Name) -> Result<(), PibError> {
170 let dir = self.root.join("anchors").join(name_hash(key_name));
171 if dir.exists() {
172 std::fs::remove_dir_all(dir)?;
173 }
174 Ok(())
175 }
176
177 pub fn trust_anchors(&self) -> Result<Vec<Certificate>, PibError> {
179 let anchors_root = self.root.join("anchors");
180 if !anchors_root.exists() {
181 return Ok(vec![]);
182 }
183 let mut certs = Vec::new();
184 for entry in std::fs::read_dir(&anchors_root)? {
185 let entry = entry?;
186 let path = entry.path();
187 if !path.is_dir() {
188 continue;
189 }
190 let name_uri = std::fs::read_to_string(path.join("name.uri")).unwrap_or_default();
191 let name = name_from_uri(name_uri.trim()).unwrap_or_else(|_| Name::root());
192 if let Ok(data) = std::fs::read(path.join("cert.ndnc"))
193 && let Ok(cert) = decode_cert(Arc::new(name), &data)
194 {
195 certs.push(cert);
196 }
197 }
198 Ok(certs)
199 }
200
201 pub fn list_anchors(&self) -> Result<Vec<Name>, PibError> {
203 list_names_in(&self.root.join("anchors"))
204 }
205
206 fn key_dir(&self, name: &Name) -> Result<PathBuf, PibError> {
211 let dir = self.root.join("keys").join(name_hash(name));
212 std::fs::create_dir_all(&dir)?;
213 Ok(dir)
214 }
215
216 fn existing_key_dir(&self, name: &Name) -> Option<PathBuf> {
218 let dir = self.root.join("keys").join(name_hash(name));
219 if dir.exists() { Some(dir) } else { None }
220 }
221
222 fn anchor_dir(&self, name: &Name) -> Result<PathBuf, PibError> {
223 let dir = self.root.join("anchors").join(name_hash(name));
224 std::fs::create_dir_all(&dir)?;
225 Ok(dir)
226 }
227}
228
229const NDNC_MAGIC: &[u8; 4] = b"NDNC";
232const NDNC_VERSION: u8 = 1;
233
234fn encode_cert(cert: &Certificate) -> Vec<u8> {
235 let pk = cert.public_key.as_ref();
236 let mut buf = Vec::with_capacity(25 + pk.len());
237 buf.extend_from_slice(NDNC_MAGIC);
238 buf.push(NDNC_VERSION);
239 buf.extend_from_slice(&cert.valid_from.to_be_bytes());
240 buf.extend_from_slice(&cert.valid_until.to_be_bytes());
241 buf.extend_from_slice(&(pk.len() as u32).to_be_bytes());
242 buf.extend_from_slice(pk);
243 buf
244}
245
246pub fn decode_cert(name: Arc<Name>, data: &[u8]) -> Result<Certificate, PibError> {
248 if data.len() < 25 {
249 return Err(PibError::Corrupt("cert too short".into()));
250 }
251 if &data[..4] != NDNC_MAGIC {
252 return Err(PibError::Corrupt("invalid magic bytes".into()));
253 }
254 let valid_from = u64::from_be_bytes(data[5..13].try_into().unwrap());
256 let valid_until = u64::from_be_bytes(data[13..21].try_into().unwrap());
257 let pk_len = u32::from_be_bytes(data[21..25].try_into().unwrap()) as usize;
258 if data.len() < 25 + pk_len {
259 return Err(PibError::Corrupt("cert data truncated".into()));
260 }
261 let pk = Bytes::copy_from_slice(&data[25..25 + pk_len]);
262 Ok(Certificate {
263 name,
264 public_key: pk,
265 valid_from,
266 valid_until,
267 issuer: None,
268 signed_region: None,
269 sig_value: None,
270 })
271}
272
273fn name_hash(name: &Name) -> String {
278 use ring::digest;
279 let mut bytes: Vec<u8> = Vec::new();
280 for comp in name.components() {
281 let len = comp.value.len() as u32;
282 bytes.extend_from_slice(&len.to_be_bytes());
283 bytes.extend_from_slice(&comp.value);
284 }
285 let hash = digest::digest(&digest::SHA256, &bytes);
286 hex_encode(hash.as_ref())
287}
288
289fn hex_encode(bytes: &[u8]) -> String {
290 bytes.iter().map(|b| format!("{:02x}", b)).collect()
291}
292
293pub fn name_to_uri(name: &Name) -> String {
298 if name.components().is_empty() {
299 return "/".to_string();
300 }
301 name.components()
302 .iter()
303 .map(|c| {
304 let mut s = String::from("/");
305 for &b in c.value.iter() {
306 if b.is_ascii_alphanumeric() || matches!(b, b'-' | b'.' | b'_' | b'~') {
307 s.push(b as char);
308 } else {
309 s.push_str(&format!("%{:02X}", b));
310 }
311 }
312 s
313 })
314 .collect()
315}
316
317pub fn name_from_uri(uri: &str) -> Result<Name, PibError> {
319 if uri == "/" || uri.is_empty() {
320 return Ok(Name::root());
321 }
322 let comps: Result<Vec<NameComponent>, PibError> = uri
323 .split('/')
324 .filter(|s| !s.is_empty())
325 .map(|seg| {
326 let mut bytes: Vec<u8> = Vec::new();
327 let seg = seg.as_bytes();
328 let mut i = 0;
329 while i < seg.len() {
330 if seg[i] == b'%' && i + 2 < seg.len() {
331 let hi = hex_digit(seg[i + 1]).ok_or(PibError::InvalidName)?;
332 let lo = hex_digit(seg[i + 2]).ok_or(PibError::InvalidName)?;
333 bytes.push((hi << 4) | lo);
334 i += 3;
335 } else {
336 bytes.push(seg[i]);
337 i += 1;
338 }
339 }
340 Ok(NameComponent::generic(Bytes::from(bytes)))
341 })
342 .collect();
343 Ok(Name::from_components(comps?))
344}
345
346fn hex_digit(b: u8) -> Option<u8> {
347 match b {
348 b'0'..=b'9' => Some(b - b'0'),
349 b'a'..=b'f' => Some(b - b'a' + 10),
350 b'A'..=b'F' => Some(b - b'A' + 10),
351 _ => None,
352 }
353}
354
355fn list_names_in(dir: &Path) -> Result<Vec<Name>, PibError> {
356 if !dir.exists() {
357 return Ok(vec![]);
358 }
359 let mut names = Vec::new();
360 for entry in std::fs::read_dir(dir)? {
361 let entry = entry?;
362 let path = entry.path();
363 if !path.is_dir() {
364 continue;
365 }
366 let uri_path = path.join("name.uri");
367 if uri_path.exists() {
368 let uri = std::fs::read_to_string(&uri_path)?;
369 if let Ok(name) = name_from_uri(uri.trim()) {
370 names.push(name);
371 }
372 }
373 }
374 Ok(names)
375}
376
377fn random_seed() -> [u8; 32] {
380 use ring::rand::{SecureRandom, SystemRandom};
381 let rng = SystemRandom::new();
382 let mut seed = [0u8; 32];
383 rng.fill(&mut seed).expect("system RNG unavailable");
384 seed
385}
386
387#[cfg(test)]
390mod tests {
391 use super::*;
392 use crate::Signer;
393 use bytes::Bytes;
394 use ndn_packet::NameComponent;
395 use std::time::{SystemTime, UNIX_EPOCH};
396
397 fn key_name(s: &str) -> Name {
398 Name::from_components([NameComponent::generic(Bytes::copy_from_slice(s.as_bytes()))])
399 }
400
401 fn tmp_pib() -> (tempfile::TempDir, FilePib) {
402 let dir = tempfile::tempdir().unwrap();
403 let pib = FilePib::new(dir.path()).unwrap();
404 (dir, pib)
405 }
406
407 fn now_ns() -> u64 {
408 SystemTime::now()
409 .duration_since(UNIX_EPOCH)
410 .unwrap()
411 .as_nanos() as u64
412 }
413
414 #[test]
415 fn create_pib_creates_dirs() {
416 let dir = tempfile::tempdir().unwrap();
417 FilePib::new(dir.path()).unwrap();
418 assert!(dir.path().join("keys").exists());
419 assert!(dir.path().join("anchors").exists());
420 }
421
422 #[test]
423 fn open_nonexistent_pib_errors() {
424 let r = FilePib::open("/tmp/ndn_pib_nonexistent_xyz_abc");
425 assert!(r.is_err());
426 }
427
428 #[test]
429 fn generate_and_retrieve_signer() {
430 let (_dir, pib) = tmp_pib();
431 let name = key_name("mykey");
432 pib.generate_ed25519(&name).unwrap();
433 let signer = pib.get_signer(&name).unwrap();
434 assert_eq!(signer.key_name(), &name);
435 }
436
437 #[test]
438 fn get_signer_missing_key_errors() {
439 let (_dir, pib) = tmp_pib();
440 let name = key_name("missing");
441 assert!(matches!(
442 pib.get_signer(&name),
443 Err(PibError::KeyNotFound(_))
444 ));
445 }
446
447 #[test]
448 fn delete_key_removes_it() {
449 let (_dir, pib) = tmp_pib();
450 let name = key_name("delkey");
451 pib.generate_ed25519(&name).unwrap();
452 pib.delete_key(&name).unwrap();
453 assert!(matches!(
454 pib.get_signer(&name),
455 Err(PibError::KeyNotFound(_))
456 ));
457 }
458
459 #[test]
460 fn list_keys_returns_all() {
461 let (_dir, pib) = tmp_pib();
462 let n1 = key_name("key1");
463 let n2 = key_name("key2");
464 pib.generate_ed25519(&n1).unwrap();
465 pib.generate_ed25519(&n2).unwrap();
466 let mut keys = pib.list_keys().unwrap();
467 keys.sort_by_key(|a| a.to_string());
468 assert_eq!(keys.len(), 2);
469 }
470
471 #[test]
472 fn store_and_get_cert() {
473 let (_dir, pib) = tmp_pib();
474 let name = key_name("certkey");
475 let signer = pib.generate_ed25519(&name).unwrap();
476 let pk = Bytes::copy_from_slice(&signer.public_key_bytes());
477 let now = now_ns();
478 let cert = Certificate {
479 name: Arc::new(name.clone()),
480 public_key: pk.clone(),
481 valid_from: now,
482 valid_until: now + 365 * 24 * 3600 * 1_000_000_000u64,
483 issuer: None,
484 signed_region: None,
485 sig_value: None,
486 };
487 pib.store_cert(&name, &cert).unwrap();
488 let loaded = pib.get_cert(&name).unwrap();
489 assert_eq!(loaded.public_key, pk);
490 assert_eq!(loaded.valid_from, now);
491 }
492
493 #[test]
494 fn get_cert_missing_errors() {
495 let (_dir, pib) = tmp_pib();
496 let name = key_name("nocert");
497 pib.generate_ed25519(&name).unwrap();
498 assert!(matches!(
499 pib.get_cert(&name),
500 Err(PibError::CertNotFound(_))
501 ));
502 }
503
504 #[test]
505 fn trust_anchor_roundtrip() {
506 let (_dir, pib) = tmp_pib();
507 let name = key_name("anchor");
508 let cert = Certificate {
509 name: Arc::new(name.clone()),
510 public_key: Bytes::from_static(&[0xAB; 32]),
511 valid_from: 0,
512 valid_until: u64::MAX,
513 issuer: None,
514 signed_region: None,
515 sig_value: None,
516 };
517 pib.add_trust_anchor(&name, &cert).unwrap();
518 let anchors = pib.trust_anchors().unwrap();
519 assert_eq!(anchors.len(), 1);
520 assert_eq!(anchors[0].public_key.as_ref(), &[0xABu8; 32]);
521 }
522
523 #[test]
524 fn list_anchors_returns_names() {
525 let (_dir, pib) = tmp_pib();
526 let name = key_name("ta");
527 let cert = Certificate {
528 name: Arc::new(name.clone()),
529 public_key: Bytes::from_static(&[1u8; 32]),
530 valid_from: 0,
531 valid_until: u64::MAX,
532 issuer: None,
533 signed_region: None,
534 sig_value: None,
535 };
536 pib.add_trust_anchor(&name, &cert).unwrap();
537 let names = pib.list_anchors().unwrap();
538 assert_eq!(names.len(), 1);
539 }
540
541 #[test]
542 fn name_uri_roundtrip_ascii() {
543 let name = Name::from_components([
544 NameComponent::generic(Bytes::from_static(b"ndn")),
545 NameComponent::generic(Bytes::from_static(b"router1")),
546 ]);
547 let uri = name_to_uri(&name);
548 assert_eq!(uri, "/ndn/router1");
549 let back = name_from_uri(&uri).unwrap();
550 assert_eq!(back, name);
551 }
552
553 #[test]
554 fn name_uri_roundtrip_binary() {
555 let name = Name::from_components([
556 NameComponent::generic(Bytes::from_static(b"ndn")),
557 NameComponent::generic(Bytes::from(vec![0x08, 0x01, 0xFF])),
558 ]);
559 let uri = name_to_uri(&name);
560 let back = name_from_uri(&uri).unwrap();
561 assert_eq!(back, name);
562 }
563
564 #[test]
565 fn root_name_uri() {
566 let uri = name_to_uri(&Name::root());
567 assert_eq!(uri, "/");
568 let back = name_from_uri(&uri).unwrap();
569 assert_eq!(back, Name::root());
570 }
571
572 #[test]
573 fn cert_encode_decode_roundtrip() {
574 let name = Arc::new(key_name("enc"));
575 let cert = Certificate {
576 name: Arc::clone(&name),
577 public_key: Bytes::from_static(&[0x55; 32]),
578 valid_from: 1_000_000,
579 valid_until: 9_999_999,
580 issuer: None,
581 signed_region: None,
582 sig_value: None,
583 };
584 let encoded = encode_cert(&cert);
585 let decoded = decode_cert(Arc::clone(&name), &encoded).unwrap();
586 assert_eq!(decoded.public_key, cert.public_key);
587 assert_eq!(decoded.valid_from, cert.valid_from);
588 assert_eq!(decoded.valid_until, cert.valid_until);
589 }
590
591 #[test]
592 fn corrupt_cert_errors() {
593 let name = Arc::new(key_name("bad"));
594 assert!(decode_cert(name.clone(), b"").is_err());
595 assert!(decode_cert(name.clone(), b"BADC\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00").is_err());
596 }
597}