ndn_security/
sign_ext.rs

1//! [`SignWith`] — extension trait for ergonomic packet signing with a [`Signer`].
2
3use std::cell::RefCell;
4
5use bytes::Bytes;
6use ndn_packet::encode::{DataBuilder, InterestBuilder};
7
8use crate::{Signer, TrustError};
9
10/// Extension trait that adds a high-level `sign_with` method to packet builders.
11///
12/// Extracts the signature algorithm and key locator from the signer automatically,
13/// eliminating the need to pass them explicitly.
14///
15/// # Example
16///
17/// ```rust,no_run
18/// use ndn_packet::encode::DataBuilder;
19/// use ndn_security::{KeyChain, SignWith};
20///
21/// # fn example() -> Result<(), ndn_security::TrustError> {
22/// let kc = KeyChain::ephemeral("/com/example/alice")?;
23/// let signer = kc.signer()?;
24///
25/// let wire = DataBuilder::new("/com/example/alice/data", b"hello")
26///     .sign_with_sync(&*signer)?;
27/// # Ok(())
28/// # }
29/// ```
30pub trait SignWith: Sized {
31    /// Sign this packet using the given signer (synchronous).
32    ///
33    /// The signature algorithm and key locator name are taken from `signer`.
34    /// Use this for Ed25519 and HMAC-SHA256 signers, which are always
35    /// CPU-bound and have fast synchronous paths.
36    fn sign_with_sync(self, signer: &dyn Signer) -> Result<Bytes, TrustError>;
37}
38
39impl SignWith for DataBuilder {
40    fn sign_with_sync(self, signer: &dyn Signer) -> Result<Bytes, TrustError> {
41        let sig_type = signer.sig_type();
42        let key_name = signer.key_name().clone();
43        let captured_err: RefCell<Option<TrustError>> = RefCell::new(None);
44        let wire = self.sign_sync(sig_type, Some(&key_name), |region| {
45            match signer.sign_sync(region) {
46                Ok(sig) => sig,
47                Err(e) => {
48                    *captured_err.borrow_mut() = Some(e);
49                    Bytes::new()
50                }
51            }
52        });
53        if let Some(e) = captured_err.into_inner() {
54            Err(e)
55        } else {
56            Ok(wire)
57        }
58    }
59}
60
61impl SignWith for InterestBuilder {
62    fn sign_with_sync(self, signer: &dyn Signer) -> Result<Bytes, TrustError> {
63        let sig_type = signer.sig_type();
64        let key_name = signer.key_name().clone();
65        let captured_err: RefCell<Option<TrustError>> = RefCell::new(None);
66        let wire = self.sign_sync(sig_type, Some(&key_name), |region| {
67            match signer.sign_sync(region) {
68                Ok(sig) => sig,
69                Err(e) => {
70                    *captured_err.borrow_mut() = Some(e);
71                    Bytes::new()
72                }
73            }
74        });
75        if let Some(e) = captured_err.into_inner() {
76            Err(e)
77        } else {
78            Ok(wire)
79        }
80    }
81}
82
83#[cfg(test)]
84mod tests {
85    use super::*;
86    use crate::{KeyChain, signer::Ed25519Signer};
87    use ndn_packet::encode::DataBuilder;
88
89    #[test]
90    fn data_sign_with_sync_roundtrip() {
91        let kc = KeyChain::ephemeral("/test/producer").unwrap();
92        let signer = kc.signer().unwrap();
93
94        let wire = DataBuilder::new("/test/producer/data", b"payload")
95            .sign_with_sync(&*signer)
96            .unwrap();
97
98        // Wire must decode as a valid Data packet.
99        let data = ndn_packet::Data::decode(wire).unwrap();
100        assert_eq!(data.name.to_string(), "/test/producer/data");
101
102        // SignatureInfo must carry the correct key locator.
103        let si = data.sig_info().unwrap();
104        let kl = si.key_locator.as_ref().unwrap();
105        assert_eq!(kl.to_string(), signer.key_name().to_string());
106    }
107
108    #[test]
109    fn interest_sign_with_sync_roundtrip() {
110        let seed = [42u8; 32];
111        let key_name: ndn_packet::Name = "/test/key".parse().unwrap();
112        let signer = Ed25519Signer::from_seed(&seed, key_name);
113
114        let wire = InterestBuilder::new("/test/prefix")
115            .sign_with_sync(&signer)
116            .unwrap();
117
118        // Must decode as a valid Interest packet.
119        let _interest = ndn_packet::Interest::decode(wire).unwrap();
120    }
121}