Skip to main content

shield_core/
signatures.rs

1//! Digital signatures without public-key cryptography.
2//!
3//! Provides HMAC-based signatures and Lamport one-time signatures.
4
5use ring::hmac;
6use std::num::NonZeroU32;
7use std::time::{SystemTime, UNIX_EPOCH};
8use subtle::ConstantTimeEq;
9use zeroize::{Zeroize, ZeroizeOnDrop};
10
11use crate::error::{Result, ShieldError};
12
13/// HMAC-based symmetric signature.
14///
15/// Keys are securely zeroized from memory when dropped.
16#[derive(Zeroize, ZeroizeOnDrop)]
17pub struct SymmetricSignature {
18    signing_key: [u8; 32],
19    verification_key: [u8; 32],
20}
21
22impl SymmetricSignature {
23    /// Create from signing key.
24    #[must_use]
25    pub fn new(signing_key: [u8; 32]) -> Self {
26        let verification_key = {
27            let hmac_key = hmac::Key::new(hmac::HMAC_SHA256, &signing_key);
28            let tag = hmac::sign(&hmac_key, b"verify");
29            let mut key = [0u8; 32];
30            key.copy_from_slice(&tag.as_ref()[..32]);
31            key
32        };
33
34        Self {
35            signing_key,
36            verification_key,
37        }
38    }
39
40    /// Generate new random signing identity.
41    pub fn generate() -> Result<Self> {
42        let key: [u8; 32] = crate::random::random_bytes()?;
43        Ok(Self::new(key))
44    }
45
46    /// Derive from password and identity.
47    #[must_use]
48    pub fn from_password(password: &str, identity: &str) -> Self {
49        let salt_data = format!("sign:{identity}");
50        let salt = ring::digest::digest(&ring::digest::SHA256, salt_data.as_bytes());
51
52        let mut key = [0u8; 32];
53        ring::pbkdf2::derive(
54            ring::pbkdf2::PBKDF2_HMAC_SHA256,
55            NonZeroU32::new(100_000).unwrap(),
56            salt.as_ref(),
57            password.as_bytes(),
58            &mut key,
59        );
60
61        Self::new(key)
62    }
63
64    /// Sign a message.
65    #[must_use]
66    pub fn sign(&self, message: &[u8], include_timestamp: bool) -> Vec<u8> {
67        if include_timestamp {
68            let timestamp = SystemTime::now()
69                .duration_since(UNIX_EPOCH)
70                .map_or(0, |d| d.as_secs());
71
72            let mut sig_data = Vec::with_capacity(8 + message.len());
73            sig_data.extend_from_slice(&timestamp.to_le_bytes());
74            sig_data.extend_from_slice(message);
75
76            let key = hmac::Key::new(hmac::HMAC_SHA256, &self.signing_key);
77            let tag = hmac::sign(&key, &sig_data);
78
79            let mut result = Vec::with_capacity(8 + 32);
80            result.extend_from_slice(&timestamp.to_le_bytes());
81            result.extend_from_slice(tag.as_ref());
82            result
83        } else {
84            let key = hmac::Key::new(hmac::HMAC_SHA256, &self.signing_key);
85            let tag = hmac::sign(&key, message);
86            tag.as_ref().to_vec()
87        }
88    }
89
90    /// Verify a signature.
91    #[must_use]
92    pub fn verify(
93        &self,
94        message: &[u8],
95        signature: &[u8],
96        verification_key: &[u8; 32],
97        max_age: u64,
98    ) -> bool {
99        if verification_key.ct_eq(&self.verification_key).unwrap_u8() != 1 {
100            return false;
101        }
102
103        if signature.len() == 40 {
104            // Timestamped signature
105            let Ok(ts_bytes) = signature[..8].try_into() else {
106                return false;
107            };
108            let timestamp = u64::from_le_bytes(ts_bytes);
109            let sig = &signature[8..];
110
111            if max_age > 0 {
112                let now = SystemTime::now()
113                    .duration_since(UNIX_EPOCH)
114                    .map_or(0, |d| d.as_secs());
115                if now.abs_diff(timestamp) > max_age {
116                    return false;
117                }
118            }
119
120            let mut sig_data = Vec::with_capacity(8 + message.len());
121            sig_data.extend_from_slice(&timestamp.to_le_bytes());
122            sig_data.extend_from_slice(message);
123
124            let key = hmac::Key::new(hmac::HMAC_SHA256, &self.signing_key);
125            let expected = hmac::sign(&key, &sig_data);
126            sig.ct_eq(expected.as_ref()).unwrap_u8() == 1
127        } else if signature.len() == 32 {
128            let key = hmac::Key::new(hmac::HMAC_SHA256, &self.signing_key);
129            let expected = hmac::sign(&key, message);
130            signature.ct_eq(expected.as_ref()).unwrap_u8() == 1
131        } else {
132            false
133        }
134    }
135
136    /// Get verification key.
137    #[must_use]
138    pub fn verification_key(&self) -> &[u8; 32] {
139        &self.verification_key
140    }
141
142    /// Get key fingerprint.
143    #[must_use]
144    pub fn fingerprint(&self) -> String {
145        let hash = ring::digest::digest(&ring::digest::SHA256, &self.verification_key);
146        hex::encode(&hash.as_ref()[..8])
147    }
148}
149
150/// Lamport one-time signature (post-quantum secure).
151///
152/// Private key material is securely zeroized from memory when dropped.
153#[derive(Zeroize, ZeroizeOnDrop)]
154pub struct LamportSignature {
155    private_key: Vec<([u8; 32], [u8; 32])>,
156    #[zeroize(skip)]
157    public_key: Vec<u8>,
158    #[zeroize(skip)]
159    used: bool,
160}
161
162impl LamportSignature {
163    const BITS: usize = 256;
164
165    /// Generate new Lamport key pair.
166    pub fn generate() -> Result<Self> {
167        let mut private_key = Vec::with_capacity(Self::BITS);
168        let mut public_key = Vec::with_capacity(Self::BITS * 64);
169
170        for _ in 0..Self::BITS {
171            let key0: [u8; 32] = crate::random::random_bytes()?;
172            let key1: [u8; 32] = crate::random::random_bytes()?;
173
174            let hash0 = ring::digest::digest(&ring::digest::SHA256, &key0);
175            let hash1 = ring::digest::digest(&ring::digest::SHA256, &key1);
176
177            public_key.extend_from_slice(hash0.as_ref());
178            public_key.extend_from_slice(hash1.as_ref());
179            private_key.push((key0, key1));
180        }
181
182        Ok(Self {
183            private_key,
184            public_key,
185            used: false,
186        })
187    }
188
189    /// Sign message (ONE TIME ONLY).
190    pub fn sign(&mut self, message: &[u8]) -> Result<Vec<u8>> {
191        if self.used {
192            return Err(ShieldError::LamportKeyUsed);
193        }
194        self.used = true;
195
196        let msg_hash = ring::digest::digest(&ring::digest::SHA256, message);
197        let hash_bytes = msg_hash.as_ref();
198        let mut signature = Vec::with_capacity(Self::BITS * 32);
199
200        for i in 0..Self::BITS {
201            let byte_idx = i / 8;
202            let bit_idx = i % 8;
203            let bit = (hash_bytes[byte_idx] >> bit_idx) & 1;
204
205            let (key0, key1) = &self.private_key[i];
206            if bit == 1 {
207                signature.extend_from_slice(key1);
208            } else {
209                signature.extend_from_slice(key0);
210            }
211        }
212
213        Ok(signature)
214    }
215
216    /// Verify a Lamport signature (constant-time: no early returns on mismatch).
217    #[must_use]
218    pub fn verify(message: &[u8], signature: &[u8], public_key: &[u8]) -> bool {
219        if signature.len() != 256 * 32 || public_key.len() != 256 * 64 {
220            return false;
221        }
222
223        let msg_hash = ring::digest::digest(&ring::digest::SHA256, message);
224        let hash_bytes = msg_hash.as_ref();
225
226        // Accumulate all comparison results — no early returns to avoid
227        // leaking which bit position failed verification.
228        let mut all_valid = 1u8;
229
230        for i in 0..256 {
231            let byte_idx = i / 8;
232            let bit_idx = i % 8;
233            let bit = (hash_bytes[byte_idx] >> bit_idx) & 1;
234
235            let revealed = &signature[i * 32..(i + 1) * 32];
236            let hashed = ring::digest::digest(&ring::digest::SHA256, revealed);
237
238            let expected = if bit == 1 {
239                &public_key[i * 64 + 32..i * 64 + 64]
240            } else {
241                &public_key[i * 64..i * 64 + 32]
242            };
243
244            all_valid &= hashed.as_ref().ct_eq(expected).unwrap_u8();
245        }
246
247        all_valid == 1
248    }
249
250    /// Check if key has been used.
251    #[must_use]
252    pub fn is_used(&self) -> bool {
253        self.used
254    }
255
256    /// Get public key.
257    #[must_use]
258    pub fn public_key(&self) -> &[u8] {
259        &self.public_key
260    }
261
262    /// Get key fingerprint.
263    #[must_use]
264    pub fn fingerprint(&self) -> String {
265        let hash = ring::digest::digest(&ring::digest::SHA256, &self.public_key);
266        hex::encode(&hash.as_ref()[..8])
267    }
268}
269
270#[cfg(test)]
271mod tests {
272    use super::*;
273
274    #[test]
275    fn test_symmetric_sign_verify() {
276        let signer = SymmetricSignature::generate().unwrap();
277        let message = b"Hello, World!";
278        let signature = signer.sign(message, true);
279        assert!(signer.verify(message, &signature, signer.verification_key(), 300));
280    }
281
282    #[test]
283    fn test_symmetric_wrong_key() {
284        let signer1 = SymmetricSignature::generate().unwrap();
285        let signer2 = SymmetricSignature::generate().unwrap();
286        let message = b"test";
287        let signature = signer1.sign(message, true);
288        assert!(!signer2.verify(message, &signature, signer2.verification_key(), 300));
289    }
290
291    #[test]
292    fn test_symmetric_from_password() {
293        let signer1 = SymmetricSignature::from_password("password", "user@example.com");
294        let signer2 = SymmetricSignature::from_password("password", "user@example.com");
295        assert_eq!(signer1.verification_key(), signer2.verification_key());
296    }
297
298    #[test]
299    fn test_lamport_sign_verify() {
300        let mut lamport = LamportSignature::generate().unwrap();
301        let message = b"Test message";
302        let signature = lamport.sign(message).unwrap();
303        assert!(LamportSignature::verify(
304            message,
305            &signature,
306            lamport.public_key()
307        ));
308    }
309
310    #[test]
311    fn test_lamport_one_time() {
312        let mut lamport = LamportSignature::generate().unwrap();
313        lamport.sign(b"first").unwrap();
314        assert!(lamport.sign(b"second").is_err());
315    }
316
317    #[test]
318    fn test_lamport_is_used() {
319        let mut lamport = LamportSignature::generate().unwrap();
320        assert!(!lamport.is_used());
321        lamport.sign(b"message").unwrap();
322        assert!(lamport.is_used());
323    }
324}