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