ndn_security/
signer.rs

1use std::future::Future;
2use std::pin::Pin;
3
4use crate::TrustError;
5use bytes::Bytes;
6use ndn_packet::{Name, SignatureType};
7
8type BoxFuture<'a, T> = Pin<Box<dyn Future<Output = T> + Send + 'a>>;
9
10/// Signs a region of bytes and produces a signature value.
11///
12/// Implemented as a dyn-compatible trait using `BoxFuture` so it can be stored
13/// as `Arc<dyn Signer>` in the key store.
14pub trait Signer: Send + Sync + 'static {
15    fn sig_type(&self) -> SignatureType;
16    fn key_name(&self) -> &Name;
17    /// The certificate name to embed as a key locator in SignatureInfo, if any.
18    fn cert_name(&self) -> Option<&Name> {
19        None
20    }
21    /// Return the raw public key bytes, if available.
22    fn public_key(&self) -> Option<Bytes> {
23        None
24    }
25
26    fn sign<'a>(&'a self, region: &'a [u8]) -> BoxFuture<'a, Result<Bytes, TrustError>>;
27
28    /// Synchronous signing — avoids `Box::pin` and async state machine overhead.
29    ///
30    /// Signers whose work is pure CPU (Ed25519, HMAC) should override this.
31    fn sign_sync(&self, region: &[u8]) -> Result<Bytes, TrustError> {
32        let _ = region;
33        unimplemented!(
34            "sign_sync not implemented for this signer — override if signing is CPU-only"
35        )
36    }
37}
38
39/// Ed25519 signer using `ed25519-dalek`.
40pub struct Ed25519Signer {
41    signing_key: ed25519_dalek::SigningKey,
42    key_name: Name,
43    cert_name: Option<Name>,
44}
45
46impl Ed25519Signer {
47    pub fn new(
48        signing_key: ed25519_dalek::SigningKey,
49        key_name: Name,
50        cert_name: Option<Name>,
51    ) -> Self {
52        Self {
53            signing_key,
54            key_name,
55            cert_name,
56        }
57    }
58
59    /// Construct from raw 32-byte seed bytes.
60    pub fn from_seed(seed: &[u8; 32], key_name: Name) -> Self {
61        let signing_key = ed25519_dalek::SigningKey::from_bytes(seed);
62        Self::new(signing_key, key_name, None)
63    }
64
65    /// Return the 32-byte compressed Ed25519 public key (verifying key).
66    pub fn public_key_bytes(&self) -> [u8; 32] {
67        self.signing_key.verifying_key().to_bytes()
68    }
69}
70
71impl Signer for Ed25519Signer {
72    fn sig_type(&self) -> SignatureType {
73        SignatureType::SignatureEd25519
74    }
75
76    fn key_name(&self) -> &Name {
77        &self.key_name
78    }
79
80    fn cert_name(&self) -> Option<&Name> {
81        self.cert_name.as_ref()
82    }
83
84    fn public_key(&self) -> Option<Bytes> {
85        Some(Bytes::copy_from_slice(&self.public_key_bytes()))
86    }
87
88    fn sign<'a>(&'a self, region: &'a [u8]) -> BoxFuture<'a, Result<Bytes, TrustError>> {
89        Box::pin(async move { self.sign_sync(region) })
90    }
91
92    fn sign_sync(&self, region: &[u8]) -> Result<Bytes, TrustError> {
93        use ed25519_dalek::Signer as _;
94        let sig = self.signing_key.sign(region);
95        Ok(Bytes::copy_from_slice(&sig.to_bytes()))
96    }
97}
98
99/// HMAC-SHA256 signer for symmetric (pre-shared key) authentication.
100///
101/// Significantly faster than Ed25519 (~10x) since it only computes a keyed
102/// hash rather than elliptic curve math.
103pub struct HmacSha256Signer {
104    key: ring::hmac::Key,
105    key_name: Name,
106}
107
108impl HmacSha256Signer {
109    /// Create from raw key bytes (any length; 32+ bytes recommended).
110    pub fn new(key_bytes: &[u8], key_name: Name) -> Self {
111        Self {
112            key: ring::hmac::Key::new(ring::hmac::HMAC_SHA256, key_bytes),
113            key_name,
114        }
115    }
116}
117
118impl Signer for HmacSha256Signer {
119    fn sig_type(&self) -> SignatureType {
120        SignatureType::SignatureHmacWithSha256
121    }
122
123    fn key_name(&self) -> &Name {
124        &self.key_name
125    }
126
127    fn sign<'a>(&'a self, region: &'a [u8]) -> BoxFuture<'a, Result<Bytes, TrustError>> {
128        Box::pin(async move { self.sign_sync(region) })
129    }
130
131    fn sign_sync(&self, region: &[u8]) -> Result<Bytes, TrustError> {
132        let tag = ring::hmac::sign(&self.key, region);
133        Ok(Bytes::copy_from_slice(tag.as_ref()))
134    }
135}
136
137// ── BLAKE3 signature type codes ───────────────────────────────────────────────
138//
139// NDN defines separate SignatureType values for plain digests and keyed
140// digests for a reason: the verifier must be able to tell which algorithm to
141// run, and a shared code opens a trivial downgrade attack (an attacker strips
142// a keyed signature and replaces the Content with a plain BLAKE3 hash over
143// their forged payload — on the wire both look identical, so a verifier that
144// dispatches on type code alone picks the plain-digest path and validates
145// the forgery). This matches the existing NDN pattern:
146//
147//   type 0  DigestSha256           (plain, unauthenticated)
148//   type 4  SignatureHmacWithSha256 (keyed, authenticated)
149//
150// ndn-rs therefore assigns BLAKE3 two distinct type codes rather than reusing
151// one. Both values are **reserved** on the NDN TLV SignatureType registry
152// (<https://redmine.named-data.net/projects/ndn-tlv/wiki/SignatureType>).
153// The wire format and verification rules are documented in
154// `docs/wiki/src/reference/blake3-signature-spec.md`.
155
156/// Signature type code for **plain** BLAKE3 digest. Analogous to
157/// `DigestSha256` (type 0) — provides content integrity / self-certifying
158/// names but **no authentication**. Anyone can produce a valid signature.
159/// See `DigestBlake3` in `blake3-signature-spec.md`.
160pub const SIGNATURE_TYPE_DIGEST_BLAKE3_PLAIN: u64 = 6;
161
162/// Signature type code for **keyed** BLAKE3. Analogous to
163/// `SignatureHmacWithSha256` (type 4) — requires a 32-byte shared secret;
164/// provides both integrity **and** authentication of the source. Distinct
165/// from the plain-digest code on purpose: see the plain-vs-keyed rationale
166/// above. See `SignatureBlake3Keyed` in `blake3-signature-spec.md`.
167pub const SIGNATURE_TYPE_DIGEST_BLAKE3_KEYED: u64 = 7;
168
169/// BLAKE3 digest signer for high-throughput self-certifying content.
170///
171/// Uses signature type code [`SIGNATURE_TYPE_DIGEST_BLAKE3_PLAIN`] (6),
172/// reserved on the NDN TLV SignatureType registry. Analogous to
173/// `DigestSha256` (type 0) but uses BLAKE3, which is 3–8× faster on modern
174/// CPUs due to SIMD parallelism.
175///
176/// The "signature" is a 32-byte BLAKE3 hash of the signed region. There is no
177/// secret key — this provides integrity (content addressing) but **not**
178/// authentication. For keyed BLAKE3 (authentication), use [`Blake3KeyedSigner`],
179/// which uses a distinct type code so verifiers cannot be downgraded from
180/// keyed to plain mode via a substitution attack.
181pub struct Blake3Signer {
182    key_name: Name,
183}
184
185impl Blake3Signer {
186    pub fn new(key_name: Name) -> Self {
187        Self { key_name }
188    }
189}
190
191impl Signer for Blake3Signer {
192    fn sig_type(&self) -> SignatureType {
193        SignatureType::Other(SIGNATURE_TYPE_DIGEST_BLAKE3_PLAIN)
194    }
195
196    fn key_name(&self) -> &Name {
197        &self.key_name
198    }
199
200    fn sign<'a>(&'a self, region: &'a [u8]) -> BoxFuture<'a, Result<Bytes, TrustError>> {
201        Box::pin(async move { self.sign_sync(region) })
202    }
203
204    fn sign_sync(&self, region: &[u8]) -> Result<Bytes, TrustError> {
205        let hash = blake3_hash_auto(region);
206        Ok(Bytes::copy_from_slice(hash.as_bytes()))
207    }
208}
209
210/// Threshold above which BLAKE3 hashing switches from single-thread
211/// `Hasher::update` to multi-thread `Hasher::update_rayon`. The blake3
212/// crate documents 128 KiB as the rule-of-thumb crossover on x86_64 —
213/// below that the rayon thread-spawn overhead beats the per-byte
214/// savings, so we always take the single-thread path. Per-packet
215/// signing of normal NDN signed portions (a few hundred bytes to a
216/// few KB) never reaches this threshold; bulk content publication
217/// (multi-MB Data) does.
218pub const BLAKE3_RAYON_THRESHOLD: usize = 128 * 1024;
219
220/// Compute an unkeyed BLAKE3 hash, automatically dispatching to the
221/// multi-thread `update_rayon` path when the input is large enough to
222/// benefit. See [`BLAKE3_RAYON_THRESHOLD`]. Used by both
223/// [`Blake3Signer`] (sign side) and `Blake3DigestVerifier` (verify
224/// side) so that a single-call API "just gets faster" on large inputs
225/// without callers needing to pick a code path.
226pub fn blake3_hash_auto(region: &[u8]) -> blake3::Hash {
227    if region.len() >= BLAKE3_RAYON_THRESHOLD {
228        let mut h = blake3::Hasher::new();
229        h.update_rayon(region);
230        h.finalize()
231    } else {
232        blake3::hash(region)
233    }
234}
235
236/// Compute a keyed BLAKE3 hash, automatically dispatching to
237/// `update_rayon` when the input is large enough to benefit. See
238/// [`BLAKE3_RAYON_THRESHOLD`].
239pub fn blake3_keyed_hash_auto(key: &[u8; 32], region: &[u8]) -> blake3::Hash {
240    if region.len() >= BLAKE3_RAYON_THRESHOLD {
241        let mut h = blake3::Hasher::new_keyed(key);
242        h.update_rayon(region);
243        h.finalize()
244    } else {
245        blake3::keyed_hash(key, region)
246    }
247}
248
249/// BLAKE3 keyed signer for authenticated high-throughput content.
250///
251/// Uses signature type code [`SIGNATURE_TYPE_DIGEST_BLAKE3_KEYED`] (7),
252/// reserved on the NDN TLV SignatureType registry, distinct from the plain
253/// BLAKE3 code on purpose (see the plain-vs-keyed rationale on the type code
254/// constants above). Uses a 32-byte secret key with BLAKE3's built-in keyed
255/// hashing mode — faster than HMAC-SHA256 while providing equivalent security
256/// guarantees.
257pub struct Blake3KeyedSigner {
258    key: [u8; 32],
259    key_name: Name,
260}
261
262impl Blake3KeyedSigner {
263    /// Create from an exact 32-byte key. The `SignatureBlake3Keyed` spec
264    /// (`docs/wiki/src/reference/blake3-signature-spec.md`) requires keys
265    /// to be exactly 32 octets; the `[u8; 32]` argument enforces this at
266    /// compile time, eliminating any silent padding or truncation that
267    /// would let a caller weaken the MAC by accident.
268    pub fn new(key: [u8; 32], key_name: Name) -> Self {
269        Self { key, key_name }
270    }
271}
272
273impl Signer for Blake3KeyedSigner {
274    fn sig_type(&self) -> SignatureType {
275        SignatureType::Other(SIGNATURE_TYPE_DIGEST_BLAKE3_KEYED)
276    }
277
278    fn key_name(&self) -> &Name {
279        &self.key_name
280    }
281
282    fn sign<'a>(&'a self, region: &'a [u8]) -> BoxFuture<'a, Result<Bytes, TrustError>> {
283        Box::pin(async move { self.sign_sync(region) })
284    }
285
286    fn sign_sync(&self, region: &[u8]) -> Result<Bytes, TrustError> {
287        let hash = blake3_keyed_hash_auto(&self.key, region);
288        Ok(Bytes::copy_from_slice(hash.as_bytes()))
289    }
290}
291
292#[cfg(test)]
293mod tests {
294    use super::*;
295    use ndn_packet::NameComponent;
296
297    fn test_key_name() -> Name {
298        Name::from_components([NameComponent::generic(bytes::Bytes::from_static(
299            b"testkey",
300        ))])
301    }
302
303    #[tokio::test]
304    async fn sig_type_is_ed25519() {
305        let s = Ed25519Signer::from_seed(&[1u8; 32], test_key_name());
306        assert_eq!(s.sig_type(), SignatureType::SignatureEd25519);
307    }
308
309    #[tokio::test]
310    async fn sign_produces_64_bytes() {
311        let s = Ed25519Signer::from_seed(&[2u8; 32], test_key_name());
312        let sig = s.sign(b"hello ndn").await.unwrap();
313        assert_eq!(sig.len(), 64);
314    }
315
316    #[tokio::test]
317    async fn deterministic_signature() {
318        let seed = [3u8; 32];
319        let s1 = Ed25519Signer::from_seed(&seed, test_key_name());
320        let s2 = Ed25519Signer::from_seed(&seed, test_key_name());
321        let sig1 = s1.sign(b"region").await.unwrap();
322        let sig2 = s2.sign(b"region").await.unwrap();
323        assert_eq!(sig1, sig2);
324    }
325
326    #[tokio::test]
327    async fn different_region_different_signature() {
328        let s = Ed25519Signer::from_seed(&[4u8; 32], test_key_name());
329        let sig1 = s.sign(b"region-a").await.unwrap();
330        let sig2 = s.sign(b"region-b").await.unwrap();
331        assert_ne!(sig1, sig2);
332    }
333
334    #[test]
335    fn key_name_accessor() {
336        let name = test_key_name();
337        let s = Ed25519Signer::from_seed(&[0u8; 32], name.clone());
338        assert_eq!(s.key_name(), &name);
339    }
340
341    #[test]
342    fn cert_name_defaults_to_none() {
343        let s = Ed25519Signer::from_seed(&[0u8; 32], test_key_name());
344        assert!(s.cert_name().is_none());
345    }
346
347    // ── HMAC-SHA256 tests ──────────────────────────────────────────────────
348
349    #[test]
350    fn hmac_sig_type() {
351        let s = HmacSha256Signer::new(b"secret", test_key_name());
352        assert_eq!(s.sig_type(), SignatureType::SignatureHmacWithSha256);
353    }
354
355    #[test]
356    fn hmac_sign_sync_produces_32_bytes() {
357        let s = HmacSha256Signer::new(b"secret", test_key_name());
358        let sig = s.sign_sync(b"hello ndn").unwrap();
359        assert_eq!(sig.len(), 32);
360    }
361
362    #[test]
363    fn hmac_deterministic() {
364        let s1 = HmacSha256Signer::new(b"key", test_key_name());
365        let s2 = HmacSha256Signer::new(b"key", test_key_name());
366        assert_eq!(
367            s1.sign_sync(b"data").unwrap(),
368            s2.sign_sync(b"data").unwrap()
369        );
370    }
371
372    #[test]
373    fn hmac_different_key_different_sig() {
374        let s1 = HmacSha256Signer::new(b"key-a", test_key_name());
375        let s2 = HmacSha256Signer::new(b"key-b", test_key_name());
376        assert_ne!(
377            s1.sign_sync(b"data").unwrap(),
378            s2.sign_sync(b"data").unwrap()
379        );
380    }
381
382    #[tokio::test]
383    async fn hmac_async_matches_sync() {
384        let s = HmacSha256Signer::new(b"key", test_key_name());
385        let async_sig = s.sign(b"data").await.unwrap();
386        let sync_sig = s.sign_sync(b"data").unwrap();
387        assert_eq!(async_sig, sync_sig);
388    }
389
390    // ── Ed25519 sign_sync tests ────────────────────────────────────────────
391
392    #[test]
393    fn ed25519_sign_sync_produces_64_bytes() {
394        let s = Ed25519Signer::from_seed(&[2u8; 32], test_key_name());
395        let sig = s.sign_sync(b"hello ndn").unwrap();
396        assert_eq!(sig.len(), 64);
397    }
398
399    #[tokio::test]
400    async fn ed25519_async_matches_sync() {
401        let s = Ed25519Signer::from_seed(&[5u8; 32], test_key_name());
402        let async_sig = s.sign(b"data").await.unwrap();
403        let sync_sig = s.sign_sync(b"data").unwrap();
404        assert_eq!(async_sig, sync_sig);
405    }
406
407    // ── BLAKE3 tests ───────────────────────────────────────────────────────
408
409    /// Plain and keyed BLAKE3 must use distinct SignatureType codes so that
410    /// a verifier dispatching on type code cannot be tricked into running
411    /// the unauthenticated plain-digest path against a packet that was
412    /// originally signed with the keyed (authenticated) mode. This mirrors
413    /// the existing NDN pattern (`DigestSha256` = 0, `HmacWithSha256` = 4).
414    #[test]
415    fn blake3_plain_and_keyed_use_distinct_sig_types() {
416        let plain = Blake3Signer::new(test_key_name());
417        let keyed = Blake3KeyedSigner::new([9u8; 32], test_key_name());
418        assert_eq!(
419            plain.sig_type(),
420            SignatureType::Other(SIGNATURE_TYPE_DIGEST_BLAKE3_PLAIN)
421        );
422        assert_eq!(
423            keyed.sig_type(),
424            SignatureType::Other(SIGNATURE_TYPE_DIGEST_BLAKE3_KEYED)
425        );
426        assert_ne!(
427            plain.sig_type(),
428            keyed.sig_type(),
429            "plain and keyed BLAKE3 must not share a type code"
430        );
431    }
432
433    /// Historical values that external callers may have depended on. Kept
434    /// as an explicit assertion so any future change to the numbers is a
435    /// deliberate, flagged break.
436    #[test]
437    fn blake3_sig_type_code_values_are_pinned() {
438        assert_eq!(SIGNATURE_TYPE_DIGEST_BLAKE3_PLAIN, 6);
439        assert_eq!(SIGNATURE_TYPE_DIGEST_BLAKE3_KEYED, 7);
440    }
441
442    #[test]
443    fn blake3_plain_produces_32_bytes() {
444        let s = Blake3Signer::new(test_key_name());
445        let sig = s.sign_sync(b"hello ndn").unwrap();
446        assert_eq!(sig.len(), 32);
447    }
448
449    #[test]
450    fn blake3_keyed_produces_32_bytes() {
451        let s = Blake3KeyedSigner::new([1u8; 32], test_key_name());
452        let sig = s.sign_sync(b"hello ndn").unwrap();
453        assert_eq!(sig.len(), 32);
454    }
455
456    /// Regression: with distinct keys, keyed signatures over the same region
457    /// must differ. This is the core authenticity property that makes the
458    /// keyed variant meaningful (vs. the plain one).
459    #[test]
460    fn blake3_keyed_different_key_different_sig() {
461        let s1 = Blake3KeyedSigner::new([1u8; 32], test_key_name());
462        let s2 = Blake3KeyedSigner::new([2u8; 32], test_key_name());
463        assert_ne!(
464            s1.sign_sync(b"data").unwrap(),
465            s2.sign_sync(b"data").unwrap()
466        );
467    }
468
469    /// Plain BLAKE3 and keyed BLAKE3 with an all-zero key must still produce
470    /// different bytes over the same region (BLAKE3's keyed mode is not just
471    /// plain hash when key = 0). This is the main reason sharing a type code
472    /// would be unsafe: a verifier that picked the wrong mode would compute
473    /// a different expected hash and (usually) reject the packet — but under
474    /// a shared type code, a substitution attack recomputes the plain digest
475    /// to match, and the verifier cannot tell the difference.
476    #[test]
477    fn blake3_plain_and_keyed_with_zero_key_differ() {
478        let plain = Blake3Signer::new(test_key_name());
479        let keyed = Blake3KeyedSigner::new([0u8; 32], test_key_name());
480        assert_ne!(
481            plain.sign_sync(b"region").unwrap(),
482            keyed.sign_sync(b"region").unwrap()
483        );
484    }
485}