ndn_security/validator/
mod.rs

1mod chain;
2
3use std::sync::{Arc, RwLock};
4
5use dashmap::DashMap;
6use ndn_packet::{Data, Name};
7
8use crate::cert_cache::Certificate;
9use crate::cert_fetcher::CertFetcher;
10use crate::trust_schema::SchemaRule;
11use crate::verifier::Verifier;
12use crate::{CertCache, Ed25519Verifier, SafeData, TrustError, TrustSchema, VerifyOutcome};
13
14/// Result of a validation attempt.
15#[derive(Debug)]
16pub enum ValidationResult {
17    /// Signature verified and trust schema satisfied.
18    Valid(Box<SafeData>),
19    /// Signature was cryptographically invalid or schema rejected.
20    Invalid(TrustError),
21    /// Certificate chain is not yet resolved; validation is async.
22    Pending,
23}
24
25/// Validates Data packets against a trust schema and certificate chain.
26///
27/// The active [`TrustSchema`] is stored in an `Arc<RwLock<TrustSchema>>` so it
28/// can be replaced or extended at runtime without rebuilding the validator.
29/// Reads (hot path) acquire a shared lock; writes (management API) acquire an
30/// exclusive lock.
31pub struct Validator {
32    pub(super) schema: Arc<RwLock<TrustSchema>>,
33    pub(super) cert_cache: Arc<CertCache>,
34    pub(super) verifier: Ed25519Verifier,
35    pub(super) max_chain: usize,
36    /// Trust anchors — implicitly trusted certificates (chain terminators).
37    pub(super) trust_anchors: Arc<DashMap<Arc<Name>, Certificate>>,
38    /// Optional fetcher for retrieving missing certificates over NDN.
39    pub(super) cert_fetcher: Option<Arc<CertFetcher>>,
40}
41
42impl Validator {
43    /// Create a validator with a private cert cache (no chain walking).
44    pub fn new(schema: TrustSchema) -> Self {
45        Self {
46            schema: Arc::new(RwLock::new(schema)),
47            cert_cache: Arc::new(CertCache::new()),
48            verifier: Ed25519Verifier,
49            max_chain: 5,
50            trust_anchors: Arc::new(DashMap::new()),
51            cert_fetcher: None,
52        }
53    }
54
55    /// Create a validator wired to shared infrastructure for chain walking.
56    pub fn with_chain(
57        schema: TrustSchema,
58        cert_cache: Arc<CertCache>,
59        trust_anchors: Arc<DashMap<Arc<Name>, Certificate>>,
60        cert_fetcher: Option<Arc<CertFetcher>>,
61        max_chain: usize,
62    ) -> Self {
63        Self {
64            schema: Arc::new(RwLock::new(schema)),
65            cert_cache,
66            verifier: Ed25519Verifier,
67            max_chain,
68            trust_anchors,
69            cert_fetcher,
70        }
71    }
72
73    /// Access the certificate cache.
74    pub fn cert_cache(&self) -> &CertCache {
75        &self.cert_cache
76    }
77
78    /// Register a trust anchor.
79    pub fn add_trust_anchor(&self, cert: Certificate) {
80        self.cert_cache.insert(cert.clone());
81        self.trust_anchors.insert(Arc::clone(&cert.name), cert);
82    }
83
84    /// Check if a name is a trust anchor.
85    pub fn is_trust_anchor(&self, name: &Name) -> bool {
86        self.trust_anchors.iter().any(|r| r.key().as_ref() == name)
87    }
88
89    // ── Runtime schema modification ───────────────────────────────────────────
90
91    /// Replace the active trust schema.
92    ///
93    /// Takes effect immediately for all subsequent validations.
94    pub fn set_schema(&self, schema: TrustSchema) {
95        *self.schema.write().expect("schema RwLock poisoned") = schema;
96    }
97
98    /// Append a rule to the active schema.
99    pub fn add_schema_rule(&self, rule: SchemaRule) {
100        self.schema
101            .write()
102            .expect("schema RwLock poisoned")
103            .add_rule(rule);
104    }
105
106    /// Remove the rule at `index` from the active schema.
107    ///
108    /// Returns the removed rule, or `None` if `index` is out of bounds.
109    pub fn remove_schema_rule(&self, index: usize) -> Option<SchemaRule> {
110        let mut guard = self.schema.write().expect("schema RwLock poisoned");
111        if index < guard.rules().len() {
112            Some(guard.remove_rule(index))
113        } else {
114            None
115        }
116    }
117
118    /// Snapshot the current schema rules as `(data_pattern, key_pattern)` text pairs.
119    pub fn schema_rules_text(&self) -> Vec<(String, String)> {
120        self.schema
121            .read()
122            .expect("schema RwLock poisoned")
123            .rules()
124            .iter()
125            .map(|r| (r.data_pattern.to_string(), r.key_pattern.to_string()))
126            .collect()
127    }
128
129    /// Returns a clone of the current [`TrustSchema`].
130    pub fn schema_snapshot(&self) -> TrustSchema {
131        self.schema.read().expect("schema RwLock poisoned").clone()
132    }
133
134    // ── Validation ────────────────────────────────────────────────────────────
135
136    /// Validate a Data packet (single-hop, returns Pending if cert missing).
137    ///
138    /// For full chain walking with async cert fetching, use `validate_chain`.
139    pub async fn validate(&self, data: &Data) -> ValidationResult {
140        let Some(sig_info) = data.sig_info() else {
141            return ValidationResult::Invalid(TrustError::InvalidSignature);
142        };
143        let Some(key_locator) = &sig_info.key_locator else {
144            return ValidationResult::Invalid(TrustError::InvalidSignature);
145        };
146
147        if !self
148            .schema
149            .read()
150            .expect("schema RwLock poisoned")
151            .allows(&data.name, key_locator)
152        {
153            return ValidationResult::Invalid(TrustError::SchemaMismatch);
154        }
155
156        let Some(cert) = self.cert_cache.get(key_locator) else {
157            return ValidationResult::Pending;
158        };
159
160        if !cert.is_valid_at(now_ns()) {
161            return ValidationResult::Invalid(TrustError::CertNotFound {
162                name: format!("expired or not-yet-valid: {}", key_locator),
163            });
164        }
165
166        match self
167            .verifier
168            .verify(data.signed_region(), data.sig_value(), &cert.public_key)
169            .await
170        {
171            Ok(VerifyOutcome::Valid) => {
172                let safe = SafeData {
173                    inner: Data::decode(data.raw().clone()).unwrap(),
174                    trust_path: crate::safe_data::TrustPath::CertChain(vec![
175                        key_locator.as_ref().clone(),
176                    ]),
177                    verified_at: now_ns(),
178                };
179                ValidationResult::Valid(Box::new(safe))
180            }
181            Ok(VerifyOutcome::Invalid) => ValidationResult::Invalid(TrustError::InvalidSignature),
182            Err(e) => ValidationResult::Invalid(e),
183        }
184    }
185}
186
187pub(crate) fn now_ns() -> u64 {
188    use std::time::{SystemTime, UNIX_EPOCH};
189    SystemTime::now()
190        .duration_since(UNIX_EPOCH)
191        .map(|d| d.as_nanos() as u64)
192        .unwrap_or(0)
193}
194
195#[cfg(test)]
196mod tests {
197    use super::*;
198    use crate::cert_cache::Certificate;
199    use crate::signer::{Ed25519Signer, Signer};
200    use crate::trust_schema::{NamePattern, PatternComponent, SchemaRule};
201    use bytes::Bytes;
202    use ndn_packet::{Name, NameComponent};
203    use std::sync::Arc;
204
205    fn comp(s: &'static str) -> NameComponent {
206        NameComponent::generic(Bytes::from_static(s.as_bytes()))
207    }
208    fn name1(c: &'static str) -> Name {
209        Name::from_components([comp(c)])
210    }
211
212    /// Build a Data TLV signed with `signer`.
213    ///
214    /// Structure: DATA > NAME(/data_comp) + SIGINFO(Ed25519, key=/key_comp) + SIGVALUE
215    /// The signed region is NAME + SIGINFO (everything inside DATA before SIGVALUE).
216    async fn make_signed_data(
217        signer: &Ed25519Signer,
218        data_comp: &'static str,
219        key_comp: &'static str,
220    ) -> Bytes {
221        use ndn_tlv::TlvWriter;
222
223        let nc = {
224            let mut w = TlvWriter::new();
225            w.write_tlv(0x08, data_comp.as_bytes());
226            w.finish()
227        };
228        let name_tlv = {
229            let mut w = TlvWriter::new();
230            w.write_tlv(0x07, &nc);
231            w.finish()
232        };
233
234        let knc = {
235            let mut w = TlvWriter::new();
236            w.write_tlv(0x08, key_comp.as_bytes());
237            w.finish()
238        };
239        let kname_tlv = {
240            let mut w = TlvWriter::new();
241            w.write_tlv(0x07, &knc);
242            w.finish()
243        };
244        let kloc_tlv = {
245            let mut w = TlvWriter::new();
246            w.write_tlv(0x1c, &kname_tlv);
247            w.finish()
248        };
249        let stype_tlv = {
250            let mut w = TlvWriter::new();
251            w.write_tlv(0x1b, &[7u8]);
252            w.finish()
253        };
254        let sinfo_inner: Vec<u8> = stype_tlv.iter().chain(kloc_tlv.iter()).copied().collect();
255        let sinfo_tlv = {
256            let mut w = TlvWriter::new();
257            w.write_tlv(0x16, &sinfo_inner);
258            w.finish()
259        };
260
261        let signed_region: Vec<u8> = name_tlv.iter().chain(sinfo_tlv.iter()).copied().collect();
262        let sig = signer.sign(&signed_region).await.unwrap();
263
264        let sval_tlv = {
265            let mut w = TlvWriter::new();
266            w.write_tlv(0x17, &sig);
267            w.finish()
268        };
269        let inner: Vec<u8> = signed_region
270            .iter()
271            .chain(sval_tlv.iter())
272            .copied()
273            .collect();
274        let mut w = TlvWriter::new();
275        w.write_tlv(0x06, &inner);
276        w.finish()
277    }
278
279    fn open_schema(data_comp: &'static str, key_comp: &'static str) -> TrustSchema {
280        let mut schema = TrustSchema::new();
281        schema.add_rule(SchemaRule {
282            data_pattern: NamePattern(vec![PatternComponent::Literal(comp(data_comp))]),
283            key_pattern: NamePattern(vec![PatternComponent::Literal(comp(key_comp))]),
284        });
285        schema
286    }
287
288    #[tokio::test]
289    async fn no_sig_info_returns_invalid() {
290        // A Data with no SignatureInfo (just name + content)
291        use ndn_tlv::TlvWriter;
292        let nc = {
293            let mut w = TlvWriter::new();
294            w.write_tlv(0x08, b"test");
295            w.finish()
296        };
297        let name_tlv = {
298            let mut w = TlvWriter::new();
299            w.write_tlv(0x07, &nc);
300            w.finish()
301        };
302        let inner: Vec<u8> = name_tlv.to_vec();
303        let data_bytes = {
304            let mut w = TlvWriter::new();
305            w.write_tlv(0x06, &inner);
306            w.finish()
307        };
308        let data = Data::decode(data_bytes).unwrap();
309
310        let validator = Validator::new(TrustSchema::new());
311        assert!(matches!(
312            validator.validate(&data).await,
313            ValidationResult::Invalid(_)
314        ));
315    }
316
317    #[tokio::test]
318    async fn schema_mismatch_returns_invalid() {
319        let seed = [10u8; 32];
320        let key_name = name1("key");
321        let signer = Ed25519Signer::from_seed(&seed, key_name.clone());
322        let data_bytes = make_signed_data(&signer, "data", "key").await;
323        let data = Data::decode(data_bytes).unwrap();
324
325        // Schema only allows /other → /key, not /data → /key
326        let mut schema = TrustSchema::new();
327        schema.add_rule(SchemaRule {
328            data_pattern: NamePattern(vec![PatternComponent::Literal(comp("other"))]),
329            key_pattern: NamePattern(vec![PatternComponent::Literal(comp("key"))]),
330        });
331
332        let validator = Validator::new(schema);
333        assert!(matches!(
334            validator.validate(&data).await,
335            ValidationResult::Invalid(_)
336        ));
337    }
338
339    #[tokio::test]
340    async fn no_cert_returns_pending() {
341        let seed = [11u8; 32];
342        let key_name = name1("key");
343        let signer = Ed25519Signer::from_seed(&seed, key_name);
344        let data_bytes = make_signed_data(&signer, "data", "key").await;
345        let data = Data::decode(data_bytes).unwrap();
346
347        let validator = Validator::new(open_schema("data", "key"));
348        assert!(matches!(
349            validator.validate(&data).await,
350            ValidationResult::Pending
351        ));
352    }
353
354    #[tokio::test]
355    async fn valid_signature_returns_valid() {
356        let seed = [12u8; 32];
357        let key_name = name1("key");
358        let signer = Ed25519Signer::from_seed(&seed, key_name.clone());
359        let data_bytes = make_signed_data(&signer, "data", "key").await;
360        let data = Data::decode(data_bytes).unwrap();
361
362        let vk_bytes = ed25519_dalek::SigningKey::from_bytes(&seed)
363            .verifying_key()
364            .to_bytes();
365        let cert = Certificate {
366            name: Arc::new(key_name),
367            public_key: Bytes::copy_from_slice(&vk_bytes),
368            valid_from: 0,
369            valid_until: u64::MAX,
370            issuer: None,
371            signed_region: None,
372            sig_value: None,
373        };
374        let validator = Validator::new(open_schema("data", "key"));
375        validator.cert_cache().insert(cert);
376
377        assert!(matches!(
378            validator.validate(&data).await,
379            ValidationResult::Valid(_)
380        ));
381    }
382
383    #[tokio::test]
384    async fn expired_cert_returns_invalid() {
385        let seed = [15u8; 32];
386        let key_name = name1("key");
387        let signer = Ed25519Signer::from_seed(&seed, key_name.clone());
388        let data_bytes = make_signed_data(&signer, "data", "key").await;
389        let data = Data::decode(data_bytes).unwrap();
390
391        let vk_bytes = ed25519_dalek::SigningKey::from_bytes(&seed)
392            .verifying_key()
393            .to_bytes();
394        let cert = Certificate {
395            name: Arc::new(key_name),
396            public_key: Bytes::copy_from_slice(&vk_bytes),
397            valid_from: 0,
398            valid_until: 1, // expired in 1970
399            issuer: None,
400            signed_region: None,
401            sig_value: None,
402        };
403        let validator = Validator::new(open_schema("data", "key"));
404        validator.cert_cache().insert(cert);
405
406        assert!(matches!(
407            validator.validate(&data).await,
408            ValidationResult::Invalid(_)
409        ));
410    }
411
412    #[tokio::test]
413    async fn invalid_signature_returns_invalid() {
414        // Sign with seed A but put seed B's public key in the cert cache
415        let seed_a = [13u8; 32];
416        let seed_b = [14u8; 32];
417        let key_name = name1("key");
418        let signer = Ed25519Signer::from_seed(&seed_a, key_name.clone());
419        let data_bytes = make_signed_data(&signer, "data", "key").await;
420        let data = Data::decode(data_bytes).unwrap();
421
422        let wrong_pk = ed25519_dalek::SigningKey::from_bytes(&seed_b)
423            .verifying_key()
424            .to_bytes();
425        let cert = Certificate {
426            name: Arc::new(key_name),
427            public_key: Bytes::copy_from_slice(&wrong_pk),
428            valid_from: 0,
429            valid_until: u64::MAX,
430            issuer: None,
431            signed_region: None,
432            sig_value: None,
433        };
434        let validator = Validator::new(open_schema("data", "key"));
435        validator.cert_cache().insert(cert);
436
437        assert!(matches!(
438            validator.validate(&data).await,
439            ValidationResult::Invalid(_)
440        ));
441    }
442}