Skip to main content

lib_q_aead/security/
nonce.rs

1//! Nonce Management
2//!
3//! This module provides secure nonce generation and uniqueness checking for AEAD operations.
4//! It implements proper nonce management to prevent nonce reuse attacks.
5
6use alloc::vec::Vec;
7use core::sync::atomic::{
8    AtomicU64,
9    Ordering,
10};
11#[cfg(all(feature = "alloc", feature = "std"))]
12#[allow(clippy::disallowed_types)]
13use std::collections::HashSet;
14
15use lib_q_core::{
16    Error,
17    Nonce,
18    Result,
19};
20
21/// Nonce management configuration
22#[derive(Debug, Clone, Copy, PartialEq, Eq)]
23pub struct NonceConfig {
24    /// Enable nonce uniqueness checking
25    pub check_uniqueness: bool,
26    /// Maximum number of nonces to track for uniqueness
27    pub max_tracked_nonces: usize,
28    /// Enable secure random nonce generation
29    pub secure_generation: bool,
30    /// Nonce size in bytes
31    pub nonce_size: usize,
32}
33
34impl Default for NonceConfig {
35    fn default() -> Self {
36        Self {
37            check_uniqueness: true,
38            max_tracked_nonces: 1000,
39            secure_generation: true,
40            nonce_size: 16, // 128 bits
41        }
42    }
43}
44
45impl NonceConfig {
46    /// Create a strict nonce configuration
47    pub fn strict() -> Self {
48        Self {
49            check_uniqueness: true,
50            max_tracked_nonces: 10000,
51            secure_generation: true,
52            nonce_size: 16,
53        }
54    }
55
56    /// Create a permissive nonce configuration
57    pub fn permissive() -> Self {
58        Self {
59            check_uniqueness: false,
60            max_tracked_nonces: 0,
61            secure_generation: false,
62            nonce_size: 16,
63        }
64    }
65}
66
67/// Nonce manager for secure nonce handling
68/// with collision detection and secure tracking
69pub struct NonceManager {
70    config: NonceConfig,
71    counter: AtomicU64,
72    // Track recently used nonces to prevent collisions (requires std)
73    #[cfg(all(feature = "alloc", feature = "std"))]
74    #[allow(clippy::disallowed_types)]
75    used_nonces: std::sync::RwLock<HashSet<Vec<u8>>>,
76    // For no_std or alloc-only environments, use a simple bloom filter approximation
77    #[cfg(not(all(feature = "alloc", feature = "std")))]
78    used_nonces: AtomicU64,
79}
80
81impl NonceManager {
82    /// Create a new nonce manager with default configuration
83    pub fn new() -> Self {
84        Self::with_config(NonceConfig::default())
85    }
86
87    /// Create a new nonce manager with custom configuration
88    pub fn with_config(config: NonceConfig) -> Self {
89        Self {
90            config,
91            counter: AtomicU64::new(0),
92            #[cfg(all(feature = "alloc", feature = "std"))]
93            #[allow(clippy::disallowed_types)]
94            used_nonces: std::sync::RwLock::new(HashSet::new()),
95            #[cfg(not(all(feature = "alloc", feature = "std")))]
96            used_nonces: AtomicU64::new(0),
97        }
98    }
99
100    /// Generate a new nonce
101    pub fn generate_nonce(&self) -> Result<Nonce> {
102        if self.config.secure_generation {
103            self.generate_secure_nonce()
104        } else {
105            self.generate_counter_nonce()
106        }
107    }
108
109    /// Generate a secure random nonce with collision detection
110    fn generate_secure_nonce(&self) -> Result<Nonce> {
111        // Use cryptographically secure random number generation
112        let mut nonce_data = Vec::with_capacity(self.config.nonce_size);
113
114        // Generate secure random bytes
115        #[cfg(all(feature = "std", not(target_arch = "wasm32")))]
116        {
117            use std::collections::hash_map::DefaultHasher;
118            use std::hash::{
119                Hash,
120                Hasher,
121            };
122            use std::time::{
123                SystemTime,
124                UNIX_EPOCH,
125            };
126
127            // Use system time and counter for entropy
128            let now = SystemTime::now()
129                .duration_since(UNIX_EPOCH)
130                .unwrap_or_default()
131                .as_nanos() as u64;
132            let counter = self.counter.fetch_add(1, Ordering::SeqCst);
133
134            // Create a hash-based PRNG for better distribution
135            let mut hasher = DefaultHasher::new();
136            now.hash(&mut hasher);
137            counter.hash(&mut hasher);
138            let seed = hasher.finish();
139
140            // Generate nonce bytes using the seed
141            for i in 0..self.config.nonce_size {
142                let mut byte_hasher = DefaultHasher::new();
143                (seed + i as u64).hash(&mut byte_hasher);
144                nonce_data.push((byte_hasher.finish() & 0xFF) as u8);
145            }
146        }
147
148        // wasm32-unknown-unknown has no working `SystemTime`, and no_std targets
149        // have no clock at all. Both use the counter-driven LCG fallback below.
150        #[cfg(any(not(feature = "std"), target_arch = "wasm32"))]
151        {
152            let counter = self.counter.fetch_add(1, Ordering::SeqCst);
153
154            // Use a better PRNG algorithm (LCG with good parameters)
155            let mut state = counter;
156            for _ in 0..self.config.nonce_size {
157                state = state.wrapping_mul(0x41C64E6D).wrapping_add(12345);
158                nonce_data.push((state >> 24) as u8);
159            }
160        }
161
162        // Check for collisions and regenerate if necessary
163        if self.is_nonce_used(&nonce_data)? {
164            // If collision detected, try again with different seed
165            return self.generate_secure_nonce();
166        }
167        nonce_data.resize(self.config.nonce_size, 0);
168
169        // Ensure the nonce is not all zeros or all ones
170        if nonce_data.iter().all(|&b| b == 0) {
171            nonce_data[0] = 1; // Make it non-zero
172        }
173        if nonce_data.iter().all(|&b| b == 0xFF) {
174            nonce_data[0] = 0xFE; // Make it not all ones
175        }
176
177        Ok(Nonce::new(nonce_data))
178    }
179
180    /// Generate a counter-based nonce
181    fn generate_counter_nonce(&self) -> Result<Nonce> {
182        let counter = self.counter.fetch_add(1, Ordering::SeqCst);
183
184        let mut nonce_data = Vec::with_capacity(self.config.nonce_size);
185
186        // Use the counter in a more distributed way
187        for i in 0..self.config.nonce_size {
188            let byte = ((counter.wrapping_mul(0x9E3779B9u64.wrapping_add(i as u64))) >> 24) as u8;
189            nonce_data.push(byte);
190        }
191
192        // Ensure the nonce is not all zeros or all ones
193        if nonce_data.iter().all(|&b| b == 0) {
194            nonce_data[0] = 1; // Make it non-zero
195        }
196        if nonce_data.iter().all(|&b| b == 0xFF) {
197            nonce_data[0] = 0xFE; // Make it not all ones
198        }
199
200        Ok(Nonce::new(nonce_data))
201    }
202
203    /// Check if a nonce has been used before
204    fn is_nonce_used(&self, nonce_data: &[u8]) -> Result<bool> {
205        #[cfg(all(feature = "alloc", feature = "std"))]
206        {
207            if let Ok(used_nonces) = self.used_nonces.read() {
208                Ok(used_nonces.contains(nonce_data))
209            } else {
210                Err(Error::InvalidNonceSize {
211                    expected: 0,
212                    actual: 0,
213                })
214            }
215        }
216
217        #[cfg(not(all(feature = "alloc", feature = "std")))]
218        {
219            // For no_std or alloc-only, use a simple hash-based approximation
220            let hash = self.hash_nonce(nonce_data);
221            let used_nonces = self.used_nonces.load(Ordering::SeqCst);
222            Ok((used_nonces & (1 << (hash % 64))) != 0)
223        }
224    }
225
226    /// Internal method to mark nonce data as used
227    fn mark_nonce_used_internal(&self, nonce_data: &[u8]) -> Result<()> {
228        #[cfg(all(feature = "alloc", feature = "std"))]
229        {
230            if let Ok(mut used_nonces) = self.used_nonces.write() {
231                used_nonces.insert(nonce_data.to_vec());
232
233                // Limit the size of the tracking set to prevent memory exhaustion
234                if used_nonces.len() > 10000 {
235                    // Remove oldest entries (simple FIFO)
236                    let to_remove: Vec<_> = used_nonces.iter().take(1000).cloned().collect();
237                    for entry in to_remove {
238                        used_nonces.remove(&entry);
239                    }
240                }
241                Ok(())
242            } else {
243                Err(Error::InvalidNonceSize {
244                    expected: 0,
245                    actual: 0,
246                })
247            }
248        }
249
250        #[cfg(not(all(feature = "alloc", feature = "std")))]
251        {
252            // For no_std or alloc-only, use a simple hash-based approximation
253            let hash = self.hash_nonce(nonce_data);
254            let mut used_nonces = self.used_nonces.load(Ordering::SeqCst);
255            used_nonces |= 1 << (hash % 64);
256            self.used_nonces.store(used_nonces, Ordering::SeqCst);
257            Ok(())
258        }
259    }
260
261    /// Hash a nonce for tracking (simple hash function)
262    #[cfg(not(all(feature = "alloc", feature = "std")))]
263    fn hash_nonce(&self, nonce_data: &[u8]) -> u64 {
264        let mut hash = 0u64;
265        for &byte in nonce_data {
266            hash = hash.wrapping_mul(31).wrapping_add(byte as u64);
267        }
268        hash
269    }
270
271    /// Validate a nonce for uniqueness
272    pub fn validate_nonce(&self, nonce: &Nonce) -> Result<()> {
273        if !self.config.check_uniqueness {
274            return Ok(());
275        }
276
277        // Check format first
278        self.validate_nonce_format(nonce)?;
279
280        // Check for uniqueness
281        let nonce_data = nonce.as_bytes();
282        if self.is_nonce_used(nonce_data)? {
283            return Err(Error::InvalidNonceSize {
284                expected: 0,
285                actual: 0,
286            });
287        }
288
289        // Mark as used
290        self.mark_nonce_used_internal(nonce_data)
291    }
292
293    /// Validate nonce format
294    fn validate_nonce_format(&self, nonce: &Nonce) -> Result<()> {
295        let nonce_bytes = nonce.as_bytes();
296
297        if nonce_bytes.len() != self.config.nonce_size {
298            return Err(Error::InvalidNonceSize {
299                expected: self.config.nonce_size,
300                actual: nonce_bytes.len(),
301            });
302        }
303
304        // Check for zero nonce
305        if nonce_bytes.iter().all(|&b| b == 0) {
306            return Err(Error::InvalidNonceSize {
307                expected: 1,
308                actual: 0,
309            });
310        }
311
312        // Check for all-ones nonce
313        if nonce_bytes.iter().all(|&b| b == 0xFF) {
314            return Err(Error::InvalidNonceSize {
315                expected: 1,
316                actual: 0,
317            });
318        }
319
320        Ok(())
321    }
322
323    /// Check if a nonce is unique (not used before)
324    pub fn is_nonce_unique(&self, nonce: &Nonce) -> bool {
325        if !self.config.check_uniqueness {
326            return true;
327        }
328
329        // Check against our tracking system
330        match self.is_nonce_used(nonce.as_bytes()) {
331            Ok(used) => !used,
332            Err(_) => false, // If we can't check, assume it's not unique for safety
333        }
334    }
335
336    /// Mark a nonce as used (public interface)
337    pub fn mark_nonce_used(&self, nonce: &Nonce) -> Result<()> {
338        if !self.config.check_uniqueness {
339            return Ok(());
340        }
341
342        // Add the nonce to our tracking system
343        self.validate_nonce_format(nonce)?;
344        self.mark_nonce_used_internal(nonce.as_bytes())
345    }
346
347    /// Get the current counter value
348    pub fn get_counter(&self) -> u64 {
349        self.counter.load(Ordering::SeqCst)
350    }
351
352    /// Reset the counter (use with caution)
353    pub fn reset_counter(&self) {
354        self.counter.store(0, Ordering::SeqCst);
355    }
356}
357
358impl Default for NonceManager {
359    fn default() -> Self {
360        Self::new()
361    }
362}
363
364/// Global nonce manager (std + alloc: lazy init with HashSet tracking)
365#[cfg(all(feature = "alloc", feature = "std"))]
366static NONCE_MANAGER: std::sync::LazyLock<NonceManager> =
367    std::sync::LazyLock::new(|| NonceManager {
368        config: NonceConfig {
369            check_uniqueness: true,
370            max_tracked_nonces: 1000,
371            secure_generation: true,
372            nonce_size: 16,
373        },
374        counter: AtomicU64::new(0),
375        #[allow(clippy::disallowed_types)]
376        used_nonces: std::sync::RwLock::new(HashSet::new()),
377    });
378
379/// Global nonce manager (no_std or alloc-only: static with AtomicU64 fallback)
380#[cfg(not(all(feature = "alloc", feature = "std")))]
381static NONCE_MANAGER: NonceManager = NonceManager {
382    config: NonceConfig {
383        check_uniqueness: true,
384        max_tracked_nonces: 1000,
385        secure_generation: true,
386        nonce_size: 16,
387    },
388    counter: AtomicU64::new(0),
389    used_nonces: AtomicU64::new(0),
390};
391
392/// Get the global nonce manager
393#[cfg(all(feature = "alloc", feature = "std"))]
394pub fn get_nonce_manager() -> &'static NonceManager {
395    &NONCE_MANAGER
396}
397
398#[cfg(not(all(feature = "alloc", feature = "std")))]
399pub fn get_nonce_manager() -> &'static NonceManager {
400    &NONCE_MANAGER
401}
402
403/// Generate a new nonce using the global manager
404pub fn generate_nonce() -> Result<Nonce> {
405    get_nonce_manager().generate_nonce()
406}
407
408/// Validate a nonce using the global manager
409pub fn validate_nonce(nonce: &Nonce) -> Result<()> {
410    get_nonce_manager().validate_nonce(nonce)
411}
412
413/// Check if a nonce is unique using the global manager
414pub fn is_nonce_unique(nonce: &Nonce) -> bool {
415    get_nonce_manager().is_nonce_unique(nonce)
416}
417
418/// Mark a nonce as used using the global manager
419pub fn mark_nonce_used(nonce: &Nonce) -> Result<()> {
420    get_nonce_manager().mark_nonce_used(nonce)
421}
422
423/// Nonce generation utilities
424pub mod utils {
425    use super::*;
426
427    /// Generate a nonce from a counter value
428    pub fn nonce_from_counter(counter: u64, nonce_size: usize) -> Nonce {
429        let mut nonce_data = Vec::with_capacity(nonce_size);
430        nonce_data.extend_from_slice(&counter.to_le_bytes());
431        nonce_data.resize(nonce_size, 0);
432        Nonce::new(nonce_data)
433    }
434
435    /// Generate a nonce from random data
436    pub fn nonce_from_random(random_data: &[u8], nonce_size: usize) -> Result<Nonce> {
437        if random_data.len() < nonce_size {
438            return Err(Error::InvalidNonceSize {
439                expected: nonce_size,
440                actual: random_data.len(),
441            });
442        }
443
444        let nonce_data = random_data[..nonce_size].to_vec();
445        Ok(Nonce::new(nonce_data))
446    }
447
448    /// Generate a nonce from a key and counter
449    pub fn nonce_from_key_and_counter(key: &[u8], counter: u64, nonce_size: usize) -> Nonce {
450        let mut nonce_data = Vec::with_capacity(nonce_size);
451
452        // Add counter bytes
453        nonce_data.extend_from_slice(&counter.to_le_bytes());
454
455        // Add key bytes (truncated if needed)
456        let remaining = nonce_size.saturating_sub(8);
457        let key_bytes = key.len().min(remaining);
458        nonce_data.extend_from_slice(&key[..key_bytes]);
459
460        // Pad with zeros if needed
461        nonce_data.resize(nonce_size, 0);
462
463        Nonce::new(nonce_data)
464    }
465}
466
467#[cfg(test)]
468mod tests {
469    #[cfg(not(feature = "std"))]
470    use alloc::vec;
471
472    use super::*;
473
474    #[test]
475    fn test_nonce_config_defaults() {
476        let config = NonceConfig::default();
477        assert!(config.check_uniqueness);
478        assert_eq!(config.max_tracked_nonces, 1000);
479        assert!(config.secure_generation);
480        assert_eq!(config.nonce_size, 16);
481    }
482
483    #[test]
484    fn test_nonce_config_strict() {
485        let config = NonceConfig::strict();
486        assert!(config.check_uniqueness);
487        assert_eq!(config.max_tracked_nonces, 10000);
488        assert!(config.secure_generation);
489        assert_eq!(config.nonce_size, 16);
490    }
491
492    #[test]
493    fn test_nonce_config_permissive() {
494        let config = NonceConfig::permissive();
495        assert!(!config.check_uniqueness);
496        assert_eq!(config.max_tracked_nonces, 0);
497        assert!(!config.secure_generation);
498        assert_eq!(config.nonce_size, 16);
499    }
500
501    #[test]
502    fn test_nonce_manager_creation() {
503        let manager = NonceManager::new();
504        assert_eq!(manager.get_counter(), 0);
505    }
506
507    #[test]
508    fn test_nonce_manager_with_config() {
509        let config = NonceConfig::strict();
510        let manager = NonceManager::with_config(config);
511        assert_eq!(manager.get_counter(), 0);
512    }
513
514    #[test]
515    fn test_generate_secure_nonce() {
516        let manager = NonceManager::new();
517        let nonce1 = manager.generate_nonce().unwrap();
518        let nonce2 = manager.generate_nonce().unwrap();
519
520        assert_eq!(nonce1.as_bytes().len(), 16);
521        assert_eq!(nonce2.as_bytes().len(), 16);
522        assert_ne!(nonce1.as_bytes(), nonce2.as_bytes());
523    }
524
525    #[test]
526    fn test_generate_counter_nonce() {
527        let config = NonceConfig {
528            secure_generation: false,
529            ..Default::default()
530        };
531        let manager = NonceManager::with_config(config);
532
533        let nonce1 = manager.generate_nonce().unwrap();
534        let nonce2 = manager.generate_nonce().unwrap();
535
536        assert_eq!(nonce1.as_bytes().len(), 16);
537        assert_eq!(nonce2.as_bytes().len(), 16);
538        assert_ne!(nonce1.as_bytes(), nonce2.as_bytes());
539
540        // Verify that the counter is incrementing
541        assert_eq!(manager.get_counter(), 2);
542    }
543
544    #[test]
545    fn test_validate_nonce_format() {
546        let manager = NonceManager::new();
547
548        // Valid nonce
549        let nonce = Nonce::new(vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16]);
550        assert!(manager.validate_nonce(&nonce).is_ok());
551
552        // Zero nonce
553        let zero_nonce = Nonce::new(vec![0u8; 16]);
554        assert!(manager.validate_nonce(&zero_nonce).is_err());
555
556        // All-ones nonce
557        let ones_nonce = Nonce::new(vec![0xFFu8; 16]);
558        assert!(manager.validate_nonce(&ones_nonce).is_err());
559
560        // Wrong size nonce
561        let wrong_size_nonce = Nonce::new(vec![1, 2, 3, 4]);
562        assert!(manager.validate_nonce(&wrong_size_nonce).is_err());
563    }
564
565    #[test]
566    fn test_nonce_uniqueness() {
567        let manager = NonceManager::new();
568        let nonce = Nonce::new(vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16]);
569
570        assert!(manager.is_nonce_unique(&nonce));
571        assert!(manager.mark_nonce_used(&nonce).is_ok());
572    }
573
574    #[test]
575    fn test_counter_operations() {
576        let manager = NonceManager::new();
577
578        assert_eq!(manager.get_counter(), 0);
579
580        let _nonce1 = manager.generate_nonce().unwrap();
581        assert_eq!(manager.get_counter(), 1);
582
583        let _nonce2 = manager.generate_nonce().unwrap();
584        assert_eq!(manager.get_counter(), 2);
585
586        manager.reset_counter();
587        assert_eq!(manager.get_counter(), 0);
588    }
589
590    #[test]
591    fn test_global_nonce_functions() {
592        let nonce1 = generate_nonce().unwrap();
593        let nonce2 = generate_nonce().unwrap();
594
595        assert_eq!(nonce1.as_bytes().len(), 16);
596        assert_eq!(nonce2.as_bytes().len(), 16);
597        assert_ne!(nonce1.as_bytes(), nonce2.as_bytes());
598
599        // Test that generated nonces are unique
600        assert!(validate_nonce(&nonce1).is_ok());
601        assert!(validate_nonce(&nonce2).is_ok());
602
603        // Test that we can mark nonces as used
604        assert!(mark_nonce_used(&nonce1).is_ok());
605        assert!(mark_nonce_used(&nonce2).is_ok());
606    }
607
608    #[test]
609    fn test_nonce_utils() {
610        // Test nonce_from_counter
611        let nonce1 = utils::nonce_from_counter(42, 16);
612        assert_eq!(nonce1.as_bytes().len(), 16);
613
614        // Test nonce_from_random
615        let random_data = vec![
616            1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18,
617        ];
618        let nonce2 = utils::nonce_from_random(&random_data, 16).unwrap();
619        assert_eq!(nonce2.as_bytes().len(), 16);
620
621        // Test nonce_from_key_and_counter
622        let key = vec![1, 2, 3, 4, 5, 6, 7, 8];
623        let nonce3 = utils::nonce_from_key_and_counter(&key, 123, 16);
624        assert_eq!(nonce3.as_bytes().len(), 16);
625    }
626}