ndn_security/
cert_cache.rs

1use crate::TrustError;
2use bytes::Bytes;
3use dashmap::DashMap;
4use ndn_packet::tlv_type;
5use ndn_packet::{Data, Name};
6use ndn_tlv::TlvReader;
7use std::sync::Arc;
8
9/// A decoded NDN certificate (a signed Data packet with a public key payload).
10#[derive(Clone, Debug)]
11pub struct Certificate {
12    pub name: Arc<Name>,
13    pub public_key: Bytes,
14    pub valid_from: u64,
15    pub valid_until: u64,
16    /// The issuer's key name (from SignatureInfo.KeyLocator).
17    /// Populated by `Certificate::decode()`; `None` for manually constructed certs.
18    pub issuer: Option<Arc<Name>>,
19    /// The signed region of the certificate Data (Name through end of SigInfo).
20    /// Needed for chain-walking verification of this cert's own signature.
21    pub signed_region: Option<Bytes>,
22    /// The signature value of the certificate Data.
23    pub sig_value: Option<Bytes>,
24}
25
26impl Certificate {
27    /// Decode a certificate from a Data packet.
28    ///
29    /// The Content field is expected to contain:
30    /// - TLV type 0x00: raw public key bytes
31    /// - TLV type 0xFD (ValidityPeriod): nested tlv_type::NOT_BEFORE (0xFE) + tlv_type::NOT_AFTER (0xFF)
32    ///   as big-endian nanosecond timestamps
33    pub fn decode(data: &Data) -> Result<Self, TrustError> {
34        let content = data.content().ok_or(TrustError::InvalidKey)?;
35
36        let mut public_key: Option<Bytes> = None;
37        let mut valid_from: u64 = 0;
38        let mut valid_until: u64 = u64::MAX;
39
40        let mut reader = TlvReader::new(content.clone());
41        while !reader.is_empty() {
42            let (typ, value) = reader.read_tlv().map_err(|_| TrustError::InvalidKey)?;
43            match typ {
44                0x00 => {
45                    public_key = Some(value);
46                }
47                tlv_type::VALIDITY_PERIOD => {
48                    let mut vr = TlvReader::new(value);
49                    while !vr.is_empty() {
50                        let (vtyp, vval) = vr.read_tlv().map_err(|_| TrustError::InvalidKey)?;
51                        match vtyp {
52                            tlv_type::NOT_BEFORE => {
53                                valid_from = decode_be_u64(&vval);
54                            }
55                            tlv_type::NOT_AFTER => {
56                                valid_until = decode_be_u64(&vval);
57                            }
58                            _ => {}
59                        }
60                    }
61                }
62                _ => {}
63            }
64        }
65
66        let public_key = public_key.ok_or(TrustError::InvalidKey)?;
67        if public_key.is_empty() {
68            return Err(TrustError::InvalidKey);
69        }
70
71        // Extract issuer name from SignatureInfo.KeyLocator for chain walking.
72        let issuer = data
73            .sig_info()
74            .and_then(|si| si.key_locator.as_ref())
75            .map(Arc::clone);
76
77        // Preserve signed region and signature value for chain verification.
78        let signed_region = Some(Bytes::copy_from_slice(data.signed_region()));
79        let sig_value = Some(Bytes::copy_from_slice(data.sig_value()));
80
81        Ok(Certificate {
82            name: Arc::clone(&data.name),
83            public_key,
84            valid_from,
85            valid_until,
86            issuer,
87            signed_region,
88            sig_value,
89        })
90    }
91
92    /// Returns `true` if the certificate is valid at the given time (nanoseconds
93    /// since Unix epoch).
94    pub fn is_valid_at(&self, now_ns: u64) -> bool {
95        now_ns >= self.valid_from && now_ns <= self.valid_until
96    }
97}
98
99/// Decode a big-endian variable-length integer (up to 8 bytes).
100fn decode_be_u64(bytes: &[u8]) -> u64 {
101    let mut val: u64 = 0;
102    for &b in bytes.iter().take(8) {
103        val = (val << 8) | b as u64;
104    }
105    val
106}
107
108/// In-memory certificate cache.
109///
110/// Certificates are just named Data packets — fetching one is a normal NDN
111/// Interest. The cache avoids re-fetching recently validated certificates.
112///
113/// Two indices are maintained: by certificate name (the canonical lookup
114/// used when a packet's `KeyLocator` carries a `Name`), and by SHA-256 of
115/// the public key (used when the locator carries a `KeyDigest`). Both are
116/// populated on `insert` so callers do not need to know which form a
117/// future verification will use.
118pub struct CertCache {
119    local: DashMap<Arc<Name>, Certificate>,
120    by_digest: DashMap<[u8; 32], Arc<Name>>,
121}
122
123impl CertCache {
124    pub fn new() -> Self {
125        Self {
126            local: DashMap::new(),
127            by_digest: DashMap::new(),
128        }
129    }
130
131    pub fn get(&self, key_name: &Arc<Name>) -> Option<Certificate> {
132        self.local.get(key_name).map(|r| r.clone())
133    }
134
135    /// Look up a certificate by the SHA-256 of its public key — used to
136    /// resolve `KeyLocator` → `KeyDigest` references. Only matches
137    /// certificates already in the cache: `KeyDigest` cannot be used to
138    /// fetch a certificate over the network because the cert's name is
139    /// not known to the caller.
140    pub fn get_by_key_digest(&self, digest: &[u8]) -> Option<Certificate> {
141        let key: &[u8; 32] = digest.try_into().ok()?;
142        let name = self.by_digest.get(key)?.clone();
143        self.local.get(&name).map(|r| r.clone())
144    }
145
146    pub fn insert(&self, cert: Certificate) {
147        use sha2::{Digest, Sha256};
148        let digest = Sha256::digest(&cert.public_key);
149        let digest_arr: [u8; 32] = digest.into();
150        self.by_digest.insert(digest_arr, Arc::clone(&cert.name));
151        self.local.insert(Arc::clone(&cert.name), cert);
152    }
153}
154
155impl Default for CertCache {
156    fn default() -> Self {
157        Self::new()
158    }
159}
160
161#[cfg(test)]
162mod tests {
163    use super::*;
164    use ndn_packet::NameComponent;
165    use ndn_tlv::TlvWriter;
166
167    /// Build a minimal Data packet with a certificate Content field.
168    fn make_cert_data(pk: &[u8], valid_from: u64, valid_until: u64) -> Bytes {
169        let mut signed = TlvWriter::new();
170
171        // Name: /test/KEY/k1
172        signed.write_nested(0x07, |w| {
173            w.write_tlv(0x08, b"test");
174            w.write_tlv(0x08, b"KEY");
175            w.write_tlv(0x08, b"k1");
176        });
177
178        // Content: public key + validity period
179        signed.write_nested(0x15, |w| {
180            w.write_tlv(0x00, pk);
181            w.write_nested(tlv_type::VALIDITY_PERIOD, |w| {
182                w.write_tlv(tlv_type::NOT_BEFORE, &valid_from.to_be_bytes());
183                w.write_tlv(tlv_type::NOT_AFTER, &valid_until.to_be_bytes());
184            });
185        });
186
187        // SignatureInfo (minimal)
188        signed.write_nested(0x16, |w| {
189            w.write_tlv(0x1b, &[5u8]); // Ed25519
190        });
191
192        let signed_region = signed.finish();
193
194        // SignatureValue (dummy)
195        let mut outer = TlvWriter::new();
196        let sig_val = vec![0u8; 64];
197        let mut inner = signed_region.to_vec();
198        {
199            let mut sw = TlvWriter::new();
200            sw.write_tlv(0x17, &sig_val);
201            inner.extend_from_slice(&sw.finish());
202        }
203        outer.write_tlv(0x06, &inner);
204        outer.finish()
205    }
206
207    #[test]
208    fn decode_certificate_from_data() {
209        let pk = vec![1u8; 32];
210        let wire = make_cert_data(&pk, 1000, 2000);
211        let data = Data::decode(wire).unwrap();
212        let cert = Certificate::decode(&data).unwrap();
213
214        assert_eq!(cert.public_key.as_ref(), &pk[..]);
215        assert_eq!(cert.valid_from, 1000);
216        assert_eq!(cert.valid_until, 2000);
217        assert_eq!(cert.name.components().len(), 3);
218    }
219
220    #[test]
221    fn decode_certificate_no_content_fails() {
222        // Data with no Content TLV
223        let mut signed = TlvWriter::new();
224        signed.write_nested(0x07, |w| {
225            w.write_tlv(0x08, b"test");
226        });
227        signed.write_nested(0x16, |w| {
228            w.write_tlv(0x1b, &[5u8]);
229        });
230        let signed_region = signed.finish();
231        let mut inner = signed_region.to_vec();
232        {
233            let mut sw = TlvWriter::new();
234            sw.write_tlv(0x17, &[0u8; 64]);
235            inner.extend_from_slice(&sw.finish());
236        }
237        let mut outer = TlvWriter::new();
238        outer.write_tlv(0x06, &inner);
239        let wire = outer.finish();
240
241        let data = Data::decode(wire).unwrap();
242        assert!(Certificate::decode(&data).is_err());
243    }
244
245    #[test]
246    fn decode_certificate_empty_key_fails() {
247        let wire = make_cert_data(&[], 0, u64::MAX);
248        let data = Data::decode(wire).unwrap();
249        assert!(Certificate::decode(&data).is_err());
250    }
251
252    #[test]
253    fn get_by_key_digest_returns_inserted_cert() {
254        let pk = vec![7u8; 32];
255        let cache = CertCache::new();
256        cache.insert(Certificate {
257            name: Arc::new(Name::from_components([NameComponent::generic(
258                Bytes::from_static(b"k1"),
259            )])),
260            public_key: Bytes::copy_from_slice(&pk),
261            valid_from: 0,
262            valid_until: u64::MAX,
263            issuer: None,
264            signed_region: None,
265            sig_value: None,
266        });
267
268        // Digest is SHA-256 of the public key bytes.
269        use sha2::{Digest, Sha256};
270        let expected = Sha256::digest(&pk);
271        let cert = cache
272            .get_by_key_digest(expected.as_slice())
273            .expect("digest lookup should hit");
274        assert_eq!(cert.public_key.as_ref(), &pk[..]);
275    }
276
277    #[test]
278    fn get_by_key_digest_misses_for_unknown_digest() {
279        let cache = CertCache::new();
280        cache.insert(Certificate {
281            name: Arc::new(Name::from_components([NameComponent::generic(
282                Bytes::from_static(b"k1"),
283            )])),
284            public_key: Bytes::copy_from_slice(&[1u8; 32]),
285            valid_from: 0,
286            valid_until: u64::MAX,
287            issuer: None,
288            signed_region: None,
289            sig_value: None,
290        });
291        let bogus = [0xFFu8; 32];
292        assert!(cache.get_by_key_digest(&bogus).is_none());
293    }
294
295    #[test]
296    fn get_by_key_digest_rejects_wrong_length() {
297        let cache = CertCache::new();
298        // 31-byte digest cannot be valid SHA-256.
299        assert!(cache.get_by_key_digest(&[0u8; 31]).is_none());
300        // 33-byte digest cannot be valid SHA-256.
301        assert!(cache.get_by_key_digest(&[0u8; 33]).is_none());
302    }
303
304    #[test]
305    fn is_valid_at_checks_time_range() {
306        let cert = Certificate {
307            name: Arc::new(Name::from_components([NameComponent::generic(
308                Bytes::from_static(b"k"),
309            )])),
310            public_key: Bytes::from_static(&[1; 32]),
311            valid_from: 1000,
312            valid_until: 2000,
313            issuer: None,
314            signed_region: None,
315            sig_value: None,
316        };
317        assert!(!cert.is_valid_at(999));
318        assert!(cert.is_valid_at(1000));
319        assert!(cert.is_valid_at(1500));
320        assert!(cert.is_valid_at(2000));
321        assert!(!cert.is_valid_at(2001));
322    }
323}