Skip to main content

async_snmp/v3/
privacy.rs

1//! Privacy (encryption) protocols for `SNMPv3` (RFC 3414, RFC 3826).
2//!
3//! This module implements:
4//! - DES-CBC privacy (RFC 3414 Section 8)
5//! - AES-128-CFB privacy (RFC 3826)
6//! - AES-192-CFB privacy (draft/vendor extension, not RFC 3826)
7//! - AES-256-CFB privacy (draft/vendor extension, not RFC 3826)
8//!
9//! # Salt/IV Construction
10//!
11//! ## DES-CBC
12//! - Salt (privParameters): engineBoots (4 bytes) || counter (4 bytes) = 8 bytes
13//! - IV: pre-IV XOR salt (pre-IV is last 8 bytes of 16-byte privKey)
14//!
15//! ## AES-CFB-128
16//! - Salt (privParameters): 64-bit counter = 8 bytes
17//! - IV: engineBoots (4 bytes) || engineTime (4 bytes) || salt (8 bytes) = 16 bytes
18//!   (concatenation, NOT XOR)
19
20use std::sync::atomic::{AtomicU64, Ordering};
21
22use bytes::Bytes;
23use zeroize::{Zeroize, ZeroizeOnDrop};
24
25use super::crypto::{CryptoError, CryptoProvider};
26use super::{AuthProtocol, PrivProtocol};
27
28/// Error type for privacy (encryption/decryption) operations.
29///
30/// These errors indicate cryptographic failures. Callers should convert
31/// these to `Error::Auth` with appropriate target context.
32#[derive(Debug, Clone, PartialEq, Eq)]
33pub enum PrivacyError {
34    /// Invalid privParameters length (expected 8 bytes).
35    InvalidPrivParamsLength { expected: usize, actual: usize },
36    /// Ciphertext length not a multiple of block size.
37    InvalidCiphertextLength { length: usize, block_size: usize },
38    /// Cryptographic provider error (unsupported algorithm, invalid key, cipher failure).
39    Crypto(CryptoError),
40}
41
42impl From<CryptoError> for PrivacyError {
43    fn from(e: CryptoError) -> Self {
44        Self::Crypto(e)
45    }
46}
47
48impl std::fmt::Display for PrivacyError {
49    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
50        match self {
51            Self::InvalidPrivParamsLength { expected, actual } => {
52                write!(
53                    f,
54                    "invalid privParameters length: expected {expected}, got {actual}"
55                )
56            }
57            Self::InvalidCiphertextLength { length, block_size } => {
58                write!(
59                    f,
60                    "ciphertext length {length} not multiple of block size {block_size}"
61                )
62            }
63            Self::Crypto(e) => write!(f, "{e}"),
64        }
65    }
66}
67
68impl std::error::Error for PrivacyError {
69    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
70        match self {
71            Self::Crypto(e) => Some(e),
72            _ => None,
73        }
74    }
75}
76
77/// Result type for privacy operations.
78pub type PrivacyResult<T> = std::result::Result<T, PrivacyError>;
79
80/// Generate a random non-zero u64 for salt initialization.
81///
82/// Uses the OS cryptographic random source via `getrandom`.
83fn random_nonzero_u64() -> super::crypto::CryptoResult<u64> {
84    let mut buf = [0u8; 8];
85    loop {
86        getrandom::fill(&mut buf).map_err(|_| super::crypto::CryptoError::RandomSource)?;
87        let val = u64::from_ne_bytes(buf);
88        if val != 0 {
89            return Ok(val);
90        }
91        // Extremely unlikely (1 in 2^64), but loop if we got zero
92    }
93}
94
95/// Privacy key for encryption/decryption operations.
96///
97/// Derives encryption keys from a password and engine ID using the same
98/// process as authentication keys, then uses the appropriate portion
99/// based on the privacy protocol.
100///
101/// # Security
102///
103/// Key material is automatically zeroed from memory when the key is dropped,
104/// using the `zeroize` crate. This provides defense-in-depth against memory
105/// scraping attacks.
106#[derive(Zeroize, ZeroizeOnDrop)]
107pub struct PrivKey {
108    /// The localized key bytes
109    key: Vec<u8>,
110    /// Privacy protocol
111    #[zeroize(skip)]
112    protocol: PrivProtocol,
113    /// Salt counter for generating unique IVs.
114    /// Uses interior mutability so `encrypt()` can take &self.
115    #[zeroize(skip)]
116    salt_counter: AtomicU64,
117}
118
119/// Thread-safe salt counter for shared use across multiple encryptions.
120pub struct SaltCounter(AtomicU64);
121
122impl SaltCounter {
123    /// Create a new salt counter initialized from cryptographic randomness.
124    ///
125    /// # Panics
126    /// Panics if the OS random source is unavailable.
127    #[must_use]
128    pub fn new() -> Self {
129        Self(AtomicU64::new(
130            random_nonzero_u64().expect("OS random source unavailable"),
131        ))
132    }
133
134    /// Create a salt counter initialized to a specific value.
135    ///
136    /// This is primarily for testing purposes.
137    #[must_use]
138    pub fn from_value(value: u64) -> Self {
139        Self(AtomicU64::new(value))
140    }
141
142    /// Get the next salt value and increment the counter.
143    ///
144    /// This method never returns zero. Per net-snmp behavior, zero is skipped
145    /// on wraparound to avoid potential IV reuse issues.
146    ///
147    /// Uses the post-increment value as the salt. On wraparound (the single
148    /// thread that observes `old == u64::MAX`), it consumes the next counter
149    /// value via a fresh `fetch_add` rather than returning a fixed constant.
150    /// Returning a constant would duplicate the value produced by whichever
151    /// thread reads `old == 0`, causing IV reuse under concurrent access.
152    pub fn next(&self) -> u64 {
153        loop {
154            let old = self.0.fetch_add(1, Ordering::SeqCst);
155            let val = old.wrapping_add(1);
156            if val != 0 {
157                return val;
158            }
159            // Counter wrapped to zero (old == u64::MAX). Only one thread observes
160            // this per wrap; loop to take the next distinct counter value instead
161            // of returning a constant that another thread (old == 0) would also
162            // return.
163        }
164    }
165}
166
167impl Default for SaltCounter {
168    fn default() -> Self {
169        Self::new()
170    }
171}
172
173impl PrivKey {
174    /// Derive a privacy key from a password and engine ID.
175    ///
176    /// The key derivation uses the same algorithm as authentication keys
177    /// (RFC 3414 A.2), but the resulting key is used differently:
178    /// - DES: first 8 bytes = key, last 8 bytes = pre-IV
179    /// - 3DES: first 24 bytes = key, last 8 bytes = pre-IV
180    /// - AES: first 16/24/32 bytes = key (depending on AES variant)
181    ///
182    /// Key extension is automatically applied when needed based on the auth/priv
183    /// protocol combination:
184    ///
185    /// - AES-192/256 with SHA-1 or MD5: Blumenthal extension (draft-blumenthal-aes-usm-04)
186    /// - 3DES with SHA-1 or MD5: Reeder extension (draft-reeder-snmpv3-usm-3desede-00)
187    ///
188    /// # Performance Note
189    ///
190    /// This method performs the full key derivation (~850μs for SHA-256). When
191    /// polling many engines with shared credentials, use [`MasterKey`](super::MasterKey)
192    /// and call [`PrivKey::from_master_key`] for each engine.
193    ///
194    /// # Example
195    ///
196    /// ```rust
197    /// use async_snmp::{AuthProtocol, PrivProtocol, v3::PrivKey};
198    ///
199    /// let engine_id = [0x80, 0x00, 0x00, 0x00, 0x01, 0x02, 0x03, 0x04];
200    ///
201    /// // SHA-1 only produces 20 bytes, but AES-256 needs 32.
202    /// // Blumenthal extension is automatically applied.
203    /// let priv_key = PrivKey::from_password(
204    ///     AuthProtocol::Sha1,
205    ///     PrivProtocol::Aes256,
206    ///     b"password",
207    ///     &engine_id,
208    /// ).unwrap();
209    /// ```
210    pub fn from_password(
211        auth_protocol: AuthProtocol,
212        priv_protocol: PrivProtocol,
213        password: &[u8],
214        engine_id: &[u8],
215    ) -> super::crypto::CryptoResult<Self> {
216        use super::MasterKey;
217
218        let master = MasterKey::from_password(auth_protocol, password)?;
219        Self::from_master_key(&master, priv_protocol, engine_id)
220    }
221
222    /// Derive a privacy key from a master key and engine ID.
223    ///
224    /// This is the efficient path when you have a cached [`MasterKey`](super::MasterKey).
225    /// Key extension is automatically applied when needed based on the auth/priv
226    /// protocol combination:
227    ///
228    /// - AES-192/256 with SHA-1 or MD5: Blumenthal extension (draft-blumenthal-aes-usm-04)
229    /// - 3DES with SHA-1 or MD5: Reeder extension (draft-reeder-snmpv3-usm-3desede-00)
230    ///
231    /// # Example
232    ///
233    /// ```rust
234    /// use async_snmp::{AuthProtocol, MasterKey, PrivProtocol, v3::PrivKey};
235    ///
236    /// let master = MasterKey::from_password(AuthProtocol::Sha1, b"password").unwrap();
237    /// let engine_id = [0x80, 0x00, 0x00, 0x00, 0x01, 0x02, 0x03, 0x04];
238    ///
239    /// // SHA-1 only produces 20 bytes, but AES-256 needs 32.
240    /// // Blumenthal extension is automatically applied.
241    /// let priv_key = PrivKey::from_master_key(&master, PrivProtocol::Aes256, &engine_id).unwrap();
242    /// ```
243    pub fn from_master_key(
244        master: &super::MasterKey,
245        priv_protocol: PrivProtocol,
246        engine_id: &[u8],
247    ) -> super::crypto::CryptoResult<Self> {
248        use super::{
249            KeyExtension,
250            auth::{extend_key, extend_key_reeder},
251        };
252
253        let auth_protocol = master.protocol();
254        let key_extension = priv_protocol.key_extension_for(auth_protocol);
255
256        // Localize the master key (per RFC 3826 Section 1.2)
257        let localized = master.localize(engine_id)?;
258        let key_bytes = localized.as_bytes();
259
260        let key = match key_extension {
261            KeyExtension::None => key_bytes.to_vec(),
262            KeyExtension::Blumenthal => {
263                extend_key(auth_protocol, key_bytes, priv_protocol.key_len())?
264            }
265            KeyExtension::Reeder => {
266                extend_key_reeder(auth_protocol, key_bytes, engine_id, priv_protocol.key_len())?
267            }
268        };
269
270        Ok(Self {
271            key,
272            protocol: priv_protocol,
273            salt_counter: Self::init_salt()?,
274        })
275    }
276
277    /// Create a privacy key from raw localized key bytes.
278    ///
279    /// `key` must be at least `protocol.key_len()` octets; shorter keys would
280    /// later panic when the encryption/decryption routines slice into them, so
281    /// this returns `Err(CryptoError::InvalidKeyLength)` instead. A key longer
282    /// than `protocol.key_len()` is accepted (per RFC 3826 Section 3.1.2, the
283    /// localized key length is "at least" the required size); the extra
284    /// trailing bytes are simply unused.
285    pub fn from_bytes(
286        protocol: PrivProtocol,
287        key: impl Into<Vec<u8>>,
288    ) -> super::crypto::CryptoResult<Self> {
289        let key = key.into();
290        if key.len() < protocol.key_len() {
291            return Err(CryptoError::InvalidKeyLength);
292        }
293        Ok(Self {
294            key,
295            protocol,
296            salt_counter: Self::init_salt()?,
297        })
298    }
299
300    /// Initialize salt counter from cryptographic randomness.
301    ///
302    /// Never returns zero to avoid IV reuse issues on wraparound.
303    fn init_salt() -> super::crypto::CryptoResult<AtomicU64> {
304        Ok(AtomicU64::new(random_nonzero_u64()?))
305    }
306
307    /// Get the privacy protocol.
308    pub fn protocol(&self) -> PrivProtocol {
309        self.protocol
310    }
311
312    /// Get the encryption key portion.
313    pub fn encryption_key(&self) -> &[u8] {
314        match self.protocol {
315            PrivProtocol::Des => &self.key[..8],
316            PrivProtocol::Des3 => &self.key[..24],
317            PrivProtocol::Aes128 => &self.key[..16],
318            PrivProtocol::Aes192 => &self.key[..24],
319            PrivProtocol::Aes256 => &self.key[..32],
320        }
321    }
322
323    /// Encrypt data and return (ciphertext, privParameters).
324    ///
325    /// # Arguments
326    /// * `plaintext` - The data to encrypt (typically the serialized `ScopedPDU`)
327    /// * `engine_boots` - The authoritative engine's boot count
328    /// * `engine_time` - The authoritative engine's time
329    /// * `salt_counter` - Optional shared salt counter; if None, uses internal counter
330    ///
331    /// # Returns
332    /// * `Ok((ciphertext, priv_params))` on success
333    /// * `Err` on encryption failure
334    pub fn encrypt(
335        &self,
336        plaintext: &[u8],
337        engine_boots: u32,
338        engine_time: u32,
339        salt_counter: Option<&SaltCounter>,
340    ) -> PrivacyResult<(Bytes, Bytes)> {
341        let salt = salt_counter.map_or_else(
342            || {
343                // Fetch the current value, then increment. Skip zero.
344                let val = self.salt_counter.fetch_add(1, Ordering::Relaxed);
345                if val != 0 {
346                    return val;
347                }
348                // Counter was zero (initial or wrapped). Fetch the next value.
349                self.salt_counter.fetch_add(1, Ordering::Relaxed)
350            },
351            SaltCounter::next,
352        );
353
354        match self.protocol {
355            PrivProtocol::Des => self.encrypt_des(plaintext, engine_boots, salt),
356            PrivProtocol::Des3 => self.encrypt_des3(plaintext, engine_boots, salt),
357            PrivProtocol::Aes128 => {
358                self.encrypt_aes(plaintext, engine_boots, engine_time, salt, 16)
359            }
360            PrivProtocol::Aes192 => {
361                self.encrypt_aes(plaintext, engine_boots, engine_time, salt, 24)
362            }
363            PrivProtocol::Aes256 => {
364                self.encrypt_aes(plaintext, engine_boots, engine_time, salt, 32)
365            }
366        }
367    }
368
369    /// Decrypt data using the privParameters from the message.
370    ///
371    /// # Arguments
372    /// * `ciphertext` - The encrypted data
373    /// * `engine_boots` - The authoritative engine's boot count (from message)
374    /// * `engine_time` - The authoritative engine's time (from message)
375    /// * `priv_params` - The privParameters field from the message
376    ///
377    /// # Returns
378    /// * `Ok(plaintext)` on success
379    /// * `Err` on decryption failure
380    pub fn decrypt(
381        &self,
382        ciphertext: &[u8],
383        engine_boots: u32,
384        engine_time: u32,
385        priv_params: &[u8],
386    ) -> PrivacyResult<Bytes> {
387        if priv_params.len() != 8 {
388            tracing::debug!(target: "async_snmp::crypto", { expected = 8, actual = priv_params.len() }, "invalid privParameters length");
389            return Err(PrivacyError::InvalidPrivParamsLength {
390                expected: 8,
391                actual: priv_params.len(),
392            });
393        }
394
395        match self.protocol {
396            PrivProtocol::Des => self.decrypt_des(ciphertext, priv_params),
397            PrivProtocol::Des3 => self.decrypt_des3(ciphertext, priv_params),
398            PrivProtocol::Aes128 | PrivProtocol::Aes192 | PrivProtocol::Aes256 => {
399                self.decrypt_aes(ciphertext, engine_boots, engine_time, priv_params)
400            }
401        }
402    }
403
404    /// DES-CBC encryption (RFC 3414 Section 8.1.1).
405    fn encrypt_des(
406        &self,
407        plaintext: &[u8],
408        engine_boots: u32,
409        salt_int: u64,
410    ) -> PrivacyResult<(Bytes, Bytes)> {
411        // DES key is first 8 bytes
412        let key = &self.key[..8];
413        // Pre-IV is last 8 bytes of 16-byte privKey
414        let pre_iv = &self.key[8..16];
415
416        // Salt = engineBoots (4 bytes MSB) || counter (4 bytes MSB)
417        // We use the lower 32 bits of salt_int as the counter
418        let mut salt = [0u8; 8];
419        salt[..4].copy_from_slice(&engine_boots.to_be_bytes());
420        salt[4..].copy_from_slice(&(salt_int as u32).to_be_bytes());
421
422        // IV = pre-IV XOR salt
423        let mut iv = [0u8; 8];
424        for i in 0..8 {
425            iv[i] = pre_iv[i] ^ salt[i];
426        }
427
428        let mut buffer = plaintext.to_vec();
429        super::crypto::provider().encrypt(PrivProtocol::Des, key, &iv, &mut buffer)?;
430
431        Ok((Bytes::from(buffer), Bytes::copy_from_slice(&salt)))
432    }
433
434    /// DES-CBC decryption (RFC 3414 Section 8.1.1).
435    fn decrypt_des(&self, ciphertext: &[u8], priv_params: &[u8]) -> PrivacyResult<Bytes> {
436        if !ciphertext.len().is_multiple_of(8) {
437            tracing::debug!(target: "async_snmp::crypto", { length = ciphertext.len(), block_size = 8 }, "DES decryption failed: invalid ciphertext length");
438            return Err(PrivacyError::InvalidCiphertextLength {
439                length: ciphertext.len(),
440                block_size: 8,
441            });
442        }
443
444        // DES key is first 8 bytes
445        let key = &self.key[..8];
446        // Pre-IV is last 8 bytes of 16-byte privKey
447        let pre_iv = &self.key[8..16];
448
449        // Salt is the privParameters
450        let salt = priv_params;
451
452        // IV = pre-IV XOR salt
453        let mut iv = [0u8; 8];
454        for i in 0..8 {
455            iv[i] = pre_iv[i] ^ salt[i];
456        }
457
458        let mut buffer = ciphertext.to_vec();
459        super::crypto::provider().decrypt(PrivProtocol::Des, key, &iv, &mut buffer)?;
460
461        Ok(Bytes::from(buffer))
462    }
463
464    /// 3DES-EDE CBC encryption (draft-reeder-snmpv3-usm-3desede-00 Section 5.1.1.2).
465    fn encrypt_des3(
466        &self,
467        plaintext: &[u8],
468        engine_boots: u32,
469        salt_int: u64,
470    ) -> PrivacyResult<(Bytes, Bytes)> {
471        // 3DES key is first 24 bytes (K1, K2, K3)
472        let key = &self.key[..24];
473        // Pre-IV is bytes 24-31 of the 32-byte privKey
474        let pre_iv = &self.key[24..32];
475
476        // Salt = engineBoots (4 bytes MSB) || counter (4 bytes MSB)
477        let mut salt = [0u8; 8];
478        salt[..4].copy_from_slice(&engine_boots.to_be_bytes());
479        salt[4..].copy_from_slice(&(salt_int as u32).to_be_bytes());
480
481        // IV = pre-IV XOR salt
482        let mut iv = [0u8; 8];
483        for i in 0..8 {
484            iv[i] = pre_iv[i] ^ salt[i];
485        }
486
487        let mut buffer = plaintext.to_vec();
488        super::crypto::provider().encrypt(PrivProtocol::Des3, key, &iv, &mut buffer)?;
489
490        Ok((Bytes::from(buffer), Bytes::copy_from_slice(&salt)))
491    }
492
493    /// 3DES-EDE CBC decryption (draft-reeder-snmpv3-usm-3desede-00 Section 5.1.1.3).
494    fn decrypt_des3(&self, ciphertext: &[u8], priv_params: &[u8]) -> PrivacyResult<Bytes> {
495        if !ciphertext.len().is_multiple_of(8) {
496            tracing::debug!(target: "async_snmp::crypto", { length = ciphertext.len(), block_size = 8 }, "3DES decryption failed: invalid ciphertext length");
497            return Err(PrivacyError::InvalidCiphertextLength {
498                length: ciphertext.len(),
499                block_size: 8,
500            });
501        }
502
503        // 3DES key is first 24 bytes (K1, K2, K3)
504        let key = &self.key[..24];
505        // Pre-IV is bytes 24-31 of the 32-byte privKey
506        let pre_iv = &self.key[24..32];
507
508        // Salt is the privParameters
509        let salt = priv_params;
510
511        // IV = pre-IV XOR salt
512        let mut iv = [0u8; 8];
513        for i in 0..8 {
514            iv[i] = pre_iv[i] ^ salt[i];
515        }
516
517        let mut buffer = ciphertext.to_vec();
518        super::crypto::provider().decrypt(PrivProtocol::Des3, key, &iv, &mut buffer)?;
519
520        Ok(Bytes::from(buffer))
521    }
522
523    /// AES-CFB encryption (RFC 3826 Section 3.1).
524    fn encrypt_aes(
525        &self,
526        plaintext: &[u8],
527        engine_boots: u32,
528        engine_time: u32,
529        salt: u64,
530        key_len: usize,
531    ) -> PrivacyResult<(Bytes, Bytes)> {
532        // AES key is first key_len bytes
533        let key = &self.key[..key_len];
534
535        // Salt as 8 bytes (big-endian)
536        let salt_bytes = salt.to_be_bytes();
537
538        // IV = engineBoots (4) || engineTime (4) || salt (8) = 16 bytes
539        // This is CONCATENATION, not XOR (unlike DES)
540        let mut iv = [0u8; 16];
541        iv[..4].copy_from_slice(&engine_boots.to_be_bytes());
542        iv[4..8].copy_from_slice(&engine_time.to_be_bytes());
543        iv[8..].copy_from_slice(&salt_bytes);
544
545        let mut buffer = plaintext.to_vec();
546        super::crypto::provider().encrypt(self.protocol, key, &iv, &mut buffer)?;
547
548        Ok((Bytes::from(buffer), Bytes::copy_from_slice(&salt_bytes)))
549    }
550
551    /// AES-CFB decryption (RFC 3826 Section 3.1.4).
552    fn decrypt_aes(
553        &self,
554        ciphertext: &[u8],
555        engine_boots: u32,
556        engine_time: u32,
557        priv_params: &[u8],
558    ) -> PrivacyResult<Bytes> {
559        let key_len = match self.protocol {
560            PrivProtocol::Aes128 => 16,
561            PrivProtocol::Aes192 => 24,
562            PrivProtocol::Aes256 => 32,
563            _ => unreachable!(),
564        };
565
566        // AES key is first key_len bytes
567        let key = &self.key[..key_len];
568
569        // IV = engineBoots (4) || engineTime (4) || salt (8) = 16 bytes
570        let mut iv = [0u8; 16];
571        iv[..4].copy_from_slice(&engine_boots.to_be_bytes());
572        iv[4..8].copy_from_slice(&engine_time.to_be_bytes());
573        iv[8..].copy_from_slice(priv_params);
574
575        let mut buffer = ciphertext.to_vec();
576        super::crypto::provider().decrypt(self.protocol, key, &iv, &mut buffer)?;
577
578        Ok(Bytes::from(buffer))
579    }
580}
581
582impl std::fmt::Debug for PrivKey {
583    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
584        f.debug_struct("PrivKey")
585            .field("protocol", &self.protocol)
586            .field("key", &"[REDACTED]")
587            .field("salt_counter", &"[REDACTED]")
588            .finish()
589    }
590}
591
592impl Clone for PrivKey {
593    fn clone(&self) -> Self {
594        Self {
595            key: self.key.clone(),
596            protocol: self.protocol,
597            // Fresh counter so the clone does not replay the same salt sequence.
598            salt_counter: Self::init_salt().expect("OS random source unavailable"),
599        }
600    }
601}
602
603#[cfg(test)]
604mod tests {
605    use super::*;
606    use crate::format::hex::decode as decode_hex;
607
608    #[cfg(feature = "crypto-rustcrypto")]
609    #[test]
610    fn test_des_encrypt_decrypt_roundtrip() {
611        // Create a 16-byte key (8 for DES, 8 for pre-IV)
612        let key = vec![
613            0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, // DES key
614            0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, // pre-IV
615        ];
616        let priv_key = PrivKey::from_bytes(PrivProtocol::Des, key).unwrap();
617
618        let plaintext = b"Hello, SNMPv3 World!";
619        let engine_boots = 100u32;
620        let engine_time = 12345u32;
621
622        let (ciphertext, priv_params) = priv_key
623            .encrypt(plaintext, engine_boots, engine_time, None)
624            .expect("encryption failed");
625
626        // Verify ciphertext is different from plaintext
627        assert_ne!(ciphertext.as_ref(), plaintext);
628        // Verify priv_params is 8 bytes
629        assert_eq!(priv_params.len(), 8);
630
631        // Decrypt
632        let decrypted = priv_key
633            .decrypt(&ciphertext, engine_boots, engine_time, &priv_params)
634            .expect("decryption failed");
635
636        // DES pads to 8-byte boundary, so decrypted may be longer
637        assert!(decrypted.len() >= plaintext.len());
638        assert_eq!(&decrypted[..plaintext.len()], plaintext);
639    }
640
641    #[cfg(feature = "crypto-rustcrypto")]
642    #[test]
643    fn test_des3_encrypt_decrypt_roundtrip() {
644        // Create a 32-byte key (24 for 3DES, 8 for pre-IV)
645        let key = vec![
646            0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, // K1
647            0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, // K2
648            0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, // K3
649            0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, // pre-IV
650        ];
651        let priv_key = PrivKey::from_bytes(PrivProtocol::Des3, key).unwrap();
652
653        let plaintext = b"Hello, SNMPv3 World with 3DES!";
654        let engine_boots = 100u32;
655        let engine_time = 12345u32;
656
657        let (ciphertext, priv_params) = priv_key
658            .encrypt(plaintext, engine_boots, engine_time, None)
659            .expect("encryption failed");
660
661        // Verify ciphertext is different from plaintext
662        assert_ne!(ciphertext.as_ref(), plaintext);
663        // Verify priv_params is 8 bytes
664        assert_eq!(priv_params.len(), 8);
665
666        // Decrypt
667        let decrypted = priv_key
668            .decrypt(&ciphertext, engine_boots, engine_time, &priv_params)
669            .expect("decryption failed");
670
671        // 3DES pads to 8-byte boundary, so decrypted may be longer
672        assert!(decrypted.len() >= plaintext.len());
673        assert_eq!(&decrypted[..plaintext.len()], plaintext);
674    }
675
676    #[test]
677    fn test_aes128_encrypt_decrypt_roundtrip() {
678        // Create a 16-byte key for AES-128
679        let key = vec![
680            0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e,
681            0x0f, 0x10,
682        ];
683        let priv_key = PrivKey::from_bytes(PrivProtocol::Aes128, key).unwrap();
684
685        let plaintext = b"Hello, SNMPv3 AES World!";
686        let engine_boots = 200u32;
687        let engine_time = 54321u32;
688
689        let (ciphertext, priv_params) = priv_key
690            .encrypt(plaintext, engine_boots, engine_time, None)
691            .expect("encryption failed");
692
693        // Verify ciphertext is different from plaintext
694        assert_ne!(ciphertext.as_ref(), plaintext);
695        // Verify priv_params is 8 bytes (salt)
696        assert_eq!(priv_params.len(), 8);
697
698        // Decrypt
699        let decrypted = priv_key
700            .decrypt(&ciphertext, engine_boots, engine_time, &priv_params)
701            .expect("decryption failed");
702
703        // AES-CFB doesn't require padding, so lengths should match
704        assert_eq!(decrypted.len(), plaintext.len());
705        assert_eq!(decrypted.as_ref(), plaintext);
706    }
707
708    #[cfg(feature = "crypto-rustcrypto")]
709    #[test]
710    fn test_des_invalid_ciphertext_length() {
711        let key = vec![0u8; 16];
712        let priv_key = PrivKey::from_bytes(PrivProtocol::Des, key).unwrap();
713
714        // Ciphertext not multiple of 8
715        let ciphertext = [0u8; 13];
716        let priv_params = [0u8; 8];
717
718        let result = priv_key.decrypt(&ciphertext, 0, 0, &priv_params);
719        assert!(result.is_err());
720    }
721
722    #[test]
723    fn test_invalid_priv_params_length() {
724        let key = vec![0u8; 16];
725        let priv_key = PrivKey::from_bytes(PrivProtocol::Aes128, key).unwrap();
726
727        // priv_params should be 8 bytes
728        let ciphertext = [0u8; 16];
729        let priv_params = [0u8; 4]; // Wrong length
730
731        let result = priv_key.decrypt(&ciphertext, 0, 0, &priv_params);
732        assert!(result.is_err());
733    }
734
735    #[test]
736    fn test_from_bytes_rejects_undersized_key() {
737        // Des requires 16 octets (8 key + 8 pre-IV); 4 is far too short.
738        // Previously this succeeded and a later encrypt() would panic on the slice.
739        let result = PrivKey::from_bytes(PrivProtocol::Des, vec![0u8; 4]);
740        assert!(matches!(result, Err(CryptoError::InvalidKeyLength)));
741    }
742
743    #[test]
744    fn test_from_bytes_accepts_exact_length_key() {
745        let priv_key = PrivKey::from_bytes(PrivProtocol::Des, vec![0u8; 16]);
746        assert!(priv_key.is_ok());
747
748        let priv_key = PrivKey::from_bytes(PrivProtocol::Aes128, vec![0u8; 16]);
749        assert!(priv_key.is_ok());
750    }
751
752    #[test]
753    fn test_from_bytes_accepts_oversized_key() {
754        // RFC 3826 Section 3.1.2 specifies the localized key is ">= " the required
755        // length; downstream slices only ever take a `[..N]` prefix, so extra
756        // trailing bytes are unused but harmless.
757        let priv_key = PrivKey::from_bytes(PrivProtocol::Des, vec![0u8; 20]).unwrap();
758        // Encrypting should not panic now that the key is validated as long enough.
759        let _ = priv_key.encrypt(b"data", 0, 0, None);
760    }
761
762    #[test]
763    fn test_from_bytes_key_len_boundary() {
764        let des_len = PrivProtocol::Des.key_len();
765        assert!(PrivKey::from_bytes(PrivProtocol::Des, vec![0u8; des_len - 1]).is_err());
766        assert!(PrivKey::from_bytes(PrivProtocol::Des, vec![0u8; des_len]).is_ok());
767
768        let aes256_len = PrivProtocol::Aes256.key_len();
769        assert!(PrivKey::from_bytes(PrivProtocol::Aes256, vec![0u8; aes256_len - 1]).is_err());
770        assert!(PrivKey::from_bytes(PrivProtocol::Aes256, vec![0u8; aes256_len]).is_ok());
771    }
772
773    #[test]
774    fn test_salt_counter() {
775        let counter = SaltCounter::new();
776        let s1 = counter.next();
777        let s2 = counter.next();
778        let s3 = counter.next();
779
780        // Each call should increment
781        assert_eq!(s2, s1.wrapping_add(1));
782        assert_eq!(s3, s2.wrapping_add(1));
783    }
784
785    /// Test that `SaltCounter` never returns zero.
786    ///
787    /// Per net-snmp behavior (snmpusm.c:1319-1320), zero salt values should be
788    /// skipped to avoid potential IV reuse issues on wraparound.
789    #[test]
790    fn test_salt_counter_skips_zero() {
791        // Create a counter initialized to u64::MAX - 1 so the next call wraps through MAX.
792        // next() returns post-increment, so:
793        //   call 1: old=MAX-1, val=MAX, returns MAX
794        //   call 2: old=MAX,   val=0 (wrapped), skips 0, returns 1
795        //   call 3: old=1,     val=2, returns 2
796        let counter = SaltCounter::from_value(u64::MAX - 1);
797
798        let s1 = counter.next();
799        assert_eq!(s1, u64::MAX);
800
801        // This call wraps to zero; should skip and return 1
802        let s2 = counter.next();
803        assert_ne!(s2, 0, "SaltCounter should never return zero");
804        assert_eq!(s2, 1, "SaltCounter should skip 0 and return 1");
805
806        // Subsequent calls should continue normally
807        let s3 = counter.next();
808        assert_eq!(s3, 2);
809    }
810
811    /// Test that the wraparound path yields distinct, nonzero values.
812    ///
813    /// Regression: the wrap path previously returned a fixed `1`, which could
814    /// duplicate the value produced by whichever thread read the counter as `0`.
815    /// Driving several calls across the wrap boundary must produce no duplicate
816    /// and no zero.
817    #[test]
818    fn test_salt_counter_wrap_no_duplicate() {
819        use std::collections::HashSet;
820
821        let counter = SaltCounter::from_value(u64::MAX - 2);
822        let mut seen = HashSet::new();
823        // Sequence crosses u64::MAX and the wrap-to-zero boundary.
824        for _ in 0..6 {
825            let v = counter.next();
826            assert_ne!(v, 0, "SaltCounter must never return zero");
827            assert!(seen.insert(v), "SaltCounter emitted duplicate: {v}");
828        }
829    }
830
831    /// Test that `PrivKey`'s internal salt counter never produces zero.
832    ///
833    /// When using the internal counter (not a shared `SaltCounter`), the salt
834    /// should also skip zero on wraparound.
835    #[test]
836    fn test_priv_key_internal_salt_skips_zero() {
837        let key = vec![0u8; 16];
838        let priv_key = PrivKey::from_bytes(PrivProtocol::Aes128, key).unwrap();
839
840        // Set the internal counter to u64::MAX
841        priv_key.salt_counter.store(u64::MAX, Ordering::Relaxed);
842
843        let plaintext = b"test";
844
845        // First encryption uses u64::MAX
846        let (_, salt1) = priv_key.encrypt(plaintext, 0, 0, None).unwrap();
847        assert_eq!(
848            u64::from_be_bytes(salt1.as_ref().try_into().unwrap()),
849            u64::MAX
850        );
851
852        // Second encryption should skip 0 and use 1
853        let (_, salt2) = priv_key.encrypt(plaintext, 0, 0, None).unwrap();
854        let salt2_value = u64::from_be_bytes(salt2.as_ref().try_into().unwrap());
855        assert_ne!(salt2_value, 0, "Salt should never be zero");
856        assert_eq!(salt2_value, 1, "Salt should skip 0 and be 1");
857
858        // Third encryption should use 2
859        let (_, salt3) = priv_key.encrypt(plaintext, 0, 0, None).unwrap();
860        let salt3_value = u64::from_be_bytes(salt3.as_ref().try_into().unwrap());
861        assert_eq!(salt3_value, 2);
862    }
863
864    #[test]
865    fn test_multiple_encryptions_different_salt() {
866        let key = vec![0u8; 16];
867        let priv_key = PrivKey::from_bytes(PrivProtocol::Aes128, key).unwrap();
868
869        let plaintext = b"test data";
870
871        let (_, salt1) = priv_key.encrypt(plaintext, 0, 0, None).unwrap();
872        let (_, salt2) = priv_key.encrypt(plaintext, 0, 0, None).unwrap();
873
874        // Salts should be different for each encryption
875        assert_ne!(salt1, salt2);
876    }
877
878    #[test]
879    fn test_from_password() {
880        // Test that we can derive a privacy key from a password
881        let password = b"maplesyrup";
882        let engine_id = decode_hex("000000000000000000000002").unwrap();
883
884        let priv_key = PrivKey::from_password(
885            AuthProtocol::Sha1,
886            PrivProtocol::Aes128,
887            password,
888            &engine_id,
889        )
890        .unwrap();
891
892        // Just verify we can encrypt/decrypt with the derived key
893        let plaintext = b"test message";
894        let (ciphertext, priv_params) = priv_key.encrypt(plaintext, 100, 200, None).unwrap();
895        let decrypted = priv_key
896            .decrypt(&ciphertext, 100, 200, &priv_params)
897            .unwrap();
898
899        assert_eq!(decrypted.as_ref(), plaintext);
900    }
901
902    #[test]
903    fn test_aes192_encrypt_decrypt_roundtrip() {
904        // Create a 24-byte key for AES-192
905        let key = vec![
906            0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e,
907            0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18,
908        ];
909        let priv_key = PrivKey::from_bytes(PrivProtocol::Aes192, key).unwrap();
910
911        let plaintext = b"Hello, SNMPv3 AES-192 World!";
912        let engine_boots = 300u32;
913        let engine_time = 67890u32;
914
915        let (ciphertext, priv_params) = priv_key
916            .encrypt(plaintext, engine_boots, engine_time, None)
917            .expect("AES-192 encryption failed");
918
919        // Verify ciphertext is different from plaintext
920        assert_ne!(ciphertext.as_ref(), plaintext);
921        // Verify priv_params is 8 bytes (salt)
922        assert_eq!(priv_params.len(), 8);
923
924        // Decrypt
925        let decrypted = priv_key
926            .decrypt(&ciphertext, engine_boots, engine_time, &priv_params)
927            .expect("AES-192 decryption failed");
928
929        // AES-CFB doesn't require padding, so lengths should match
930        assert_eq!(decrypted.len(), plaintext.len());
931        assert_eq!(decrypted.as_ref(), plaintext);
932    }
933
934    #[test]
935    fn test_aes256_encrypt_decrypt_roundtrip() {
936        // Create a 32-byte key for AES-256
937        let key = vec![
938            0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e,
939            0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c,
940            0x1d, 0x1e, 0x1f, 0x20,
941        ];
942        let priv_key = PrivKey::from_bytes(PrivProtocol::Aes256, key).unwrap();
943
944        let plaintext = b"Hello, SNMPv3 AES-256 World!";
945        let engine_boots = 400u32;
946        let engine_time = 11111u32;
947
948        let (ciphertext, priv_params) = priv_key
949            .encrypt(plaintext, engine_boots, engine_time, None)
950            .expect("AES-256 encryption failed");
951
952        // Verify ciphertext is different from plaintext
953        assert_ne!(ciphertext.as_ref(), plaintext);
954        // Verify priv_params is 8 bytes (salt)
955        assert_eq!(priv_params.len(), 8);
956
957        // Decrypt
958        let decrypted = priv_key
959            .decrypt(&ciphertext, engine_boots, engine_time, &priv_params)
960            .expect("AES-256 decryption failed");
961
962        // AES-CFB doesn't require padding, so lengths should match
963        assert_eq!(decrypted.len(), plaintext.len());
964        assert_eq!(decrypted.as_ref(), plaintext);
965    }
966
967    #[test]
968    fn test_aes192_from_password() {
969        // For AES-192 (24-byte key), we need SHA-224 or higher auth protocol
970        let password = b"longpassword123";
971        let engine_id = decode_hex("80001f8880e9b104617361000000").unwrap();
972
973        let priv_key = PrivKey::from_password(
974            AuthProtocol::Sha256, // SHA-256 produces 32 bytes, enough for AES-192
975            PrivProtocol::Aes192,
976            password,
977            &engine_id,
978        )
979        .unwrap();
980
981        let plaintext = b"test message for AES-192";
982        let (ciphertext, priv_params) = priv_key.encrypt(plaintext, 100, 200, None).unwrap();
983        let decrypted = priv_key
984            .decrypt(&ciphertext, 100, 200, &priv_params)
985            .unwrap();
986
987        assert_eq!(decrypted.as_ref(), plaintext);
988    }
989
990    #[test]
991    fn test_aes256_from_password() {
992        // For AES-256 (32-byte key), we need SHA-256 or higher auth protocol
993        let password = b"anotherlongpassword456";
994        let engine_id = decode_hex("80001f8880e9b104617361000000").unwrap();
995
996        let priv_key = PrivKey::from_password(
997            AuthProtocol::Sha256, // SHA-256 produces 32 bytes, exactly enough for AES-256
998            PrivProtocol::Aes256,
999            password,
1000            &engine_id,
1001        )
1002        .unwrap();
1003
1004        let plaintext = b"test message for AES-256";
1005        let (ciphertext, priv_params) = priv_key.encrypt(plaintext, 100, 200, None).unwrap();
1006        let decrypted = priv_key
1007            .decrypt(&ciphertext, 100, 200, &priv_params)
1008            .unwrap();
1009
1010        assert_eq!(decrypted.as_ref(), plaintext);
1011    }
1012
1013    // ========================================================================
1014    // Wrong Key Decryption Tests
1015    //
1016    // These tests verify that decryption with the wrong key produces garbage,
1017    // not the original plaintext. Note: Stream ciphers like AES-CFB don't return
1018    // errors on wrong-key decryption - they produce garbage. The authentication
1019    // layer (HMAC) is what detects tampering/wrong keys in practice (RFC 3414).
1020    // ========================================================================
1021
1022    #[cfg(feature = "crypto-rustcrypto")]
1023    #[test]
1024    fn test_des_wrong_key_produces_garbage() {
1025        // Correct 16-byte key
1026        let correct_key = vec![
1027            0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16,
1028            0x17, 0x18,
1029        ];
1030        // Wrong key (different from correct key)
1031        let wrong_key = vec![
1032            0xFF, 0xFE, 0xFD, 0xFC, 0xFB, 0xFA, 0xF9, 0xF8, 0xE7, 0xE6, 0xE5, 0xE4, 0xE3, 0xE2,
1033            0xE1, 0xE0,
1034        ];
1035
1036        let correct_priv_key = PrivKey::from_bytes(PrivProtocol::Des, correct_key).unwrap();
1037        let wrong_priv_key = PrivKey::from_bytes(PrivProtocol::Des, wrong_key).unwrap();
1038
1039        let plaintext = b"Secret SNMPv3 message data!";
1040        let engine_boots = 100u32;
1041        let engine_time = 12345u32;
1042
1043        // Encrypt with correct key
1044        let (ciphertext, priv_params) = correct_priv_key
1045            .encrypt(plaintext, engine_boots, engine_time, None)
1046            .expect("encryption failed");
1047
1048        // Decrypt with wrong key - this will "succeed" but produce garbage
1049        let wrong_decrypted = wrong_priv_key
1050            .decrypt(&ciphertext, engine_boots, engine_time, &priv_params)
1051            .expect("decryption should succeed cryptographically");
1052
1053        // Verify wrong key produces different output (not the original plaintext)
1054        assert_ne!(
1055            &wrong_decrypted[..plaintext.len()],
1056            plaintext,
1057            "wrong key should NOT produce the original plaintext"
1058        );
1059
1060        // Verify correct key still works
1061        let correct_decrypted = correct_priv_key
1062            .decrypt(&ciphertext, engine_boots, engine_time, &priv_params)
1063            .expect("correct key decryption failed");
1064        assert_eq!(
1065            &correct_decrypted[..plaintext.len()],
1066            plaintext,
1067            "correct key should produce the original plaintext"
1068        );
1069    }
1070
1071    #[test]
1072    fn test_aes128_wrong_key_produces_garbage() {
1073        let correct_key = vec![
1074            0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e,
1075            0x0f, 0x10,
1076        ];
1077        let wrong_key = vec![
1078            0xFF, 0xFE, 0xFD, 0xFC, 0xFB, 0xFA, 0xF9, 0xF8, 0xF7, 0xF6, 0xF5, 0xF4, 0xF3, 0xF2,
1079            0xF1, 0xF0,
1080        ];
1081
1082        let correct_priv_key = PrivKey::from_bytes(PrivProtocol::Aes128, correct_key).unwrap();
1083        let wrong_priv_key = PrivKey::from_bytes(PrivProtocol::Aes128, wrong_key).unwrap();
1084
1085        let plaintext = b"Secret AES-128 message data!";
1086        let engine_boots = 200u32;
1087        let engine_time = 54321u32;
1088
1089        // Encrypt with correct key
1090        let (ciphertext, priv_params) = correct_priv_key
1091            .encrypt(plaintext, engine_boots, engine_time, None)
1092            .expect("encryption failed");
1093
1094        // Decrypt with wrong key
1095        let wrong_decrypted = wrong_priv_key
1096            .decrypt(&ciphertext, engine_boots, engine_time, &priv_params)
1097            .expect("decryption should succeed cryptographically");
1098
1099        // Wrong key should produce garbage (not the original plaintext)
1100        assert_ne!(
1101            wrong_decrypted.as_ref(),
1102            plaintext,
1103            "wrong key should NOT produce the original plaintext"
1104        );
1105
1106        // Correct key should work
1107        let correct_decrypted = correct_priv_key
1108            .decrypt(&ciphertext, engine_boots, engine_time, &priv_params)
1109            .expect("correct key decryption failed");
1110        assert_eq!(correct_decrypted.as_ref(), plaintext);
1111    }
1112
1113    #[test]
1114    fn test_aes192_wrong_key_produces_garbage() {
1115        let correct_key = vec![
1116            0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e,
1117            0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18,
1118        ];
1119        let wrong_key = vec![
1120            0xFF, 0xFE, 0xFD, 0xFC, 0xFB, 0xFA, 0xF9, 0xF8, 0xF7, 0xF6, 0xF5, 0xF4, 0xF3, 0xF2,
1121            0xF1, 0xF0, 0xEF, 0xEE, 0xED, 0xEC, 0xEB, 0xEA, 0xE9, 0xE8,
1122        ];
1123
1124        let correct_priv_key = PrivKey::from_bytes(PrivProtocol::Aes192, correct_key).unwrap();
1125        let wrong_priv_key = PrivKey::from_bytes(PrivProtocol::Aes192, wrong_key).unwrap();
1126
1127        let plaintext = b"Secret AES-192 message data!";
1128        let engine_boots = 300u32;
1129        let engine_time = 67890u32;
1130
1131        let (ciphertext, priv_params) = correct_priv_key
1132            .encrypt(plaintext, engine_boots, engine_time, None)
1133            .expect("encryption failed");
1134
1135        let wrong_decrypted = wrong_priv_key
1136            .decrypt(&ciphertext, engine_boots, engine_time, &priv_params)
1137            .expect("decryption should succeed cryptographically");
1138
1139        assert_ne!(
1140            wrong_decrypted.as_ref(),
1141            plaintext,
1142            "wrong key should NOT produce the original plaintext"
1143        );
1144    }
1145
1146    #[test]
1147    fn test_aes256_wrong_key_produces_garbage() {
1148        let correct_key = vec![
1149            0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e,
1150            0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c,
1151            0x1d, 0x1e, 0x1f, 0x20,
1152        ];
1153        let wrong_key = vec![
1154            0xFF, 0xFE, 0xFD, 0xFC, 0xFB, 0xFA, 0xF9, 0xF8, 0xF7, 0xF6, 0xF5, 0xF4, 0xF3, 0xF2,
1155            0xF1, 0xF0, 0xEF, 0xEE, 0xED, 0xEC, 0xEB, 0xEA, 0xE9, 0xE8, 0xE7, 0xE6, 0xE5, 0xE4,
1156            0xE3, 0xE2, 0xE1, 0xE0,
1157        ];
1158
1159        let correct_priv_key = PrivKey::from_bytes(PrivProtocol::Aes256, correct_key).unwrap();
1160        let wrong_priv_key = PrivKey::from_bytes(PrivProtocol::Aes256, wrong_key).unwrap();
1161
1162        let plaintext = b"Secret AES-256 message data!";
1163        let engine_boots = 400u32;
1164        let engine_time = 11111u32;
1165
1166        let (ciphertext, priv_params) = correct_priv_key
1167            .encrypt(plaintext, engine_boots, engine_time, None)
1168            .expect("encryption failed");
1169
1170        let wrong_decrypted = wrong_priv_key
1171            .decrypt(&ciphertext, engine_boots, engine_time, &priv_params)
1172            .expect("decryption should succeed cryptographically");
1173
1174        assert_ne!(
1175            wrong_decrypted.as_ref(),
1176            plaintext,
1177            "wrong key should NOT produce the original plaintext"
1178        );
1179    }
1180
1181    #[cfg(feature = "crypto-rustcrypto")]
1182    #[test]
1183    fn test_des_wrong_priv_params_produces_garbage() {
1184        // Verify that even with the correct key, wrong priv_params (salt/IV)
1185        // produces garbage. This tests the IV derivation logic.
1186        let key = vec![
1187            0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16,
1188            0x17, 0x18,
1189        ];
1190
1191        let priv_key = PrivKey::from_bytes(PrivProtocol::Des, key).unwrap();
1192
1193        let plaintext = b"DES test message";
1194        let engine_boots = 100u32;
1195        let engine_time = 12345u32;
1196
1197        let (ciphertext, correct_priv_params) = priv_key
1198            .encrypt(plaintext, engine_boots, engine_time, None)
1199            .expect("encryption failed");
1200
1201        // Use wrong priv_params (different salt)
1202        let wrong_priv_params = [0xFF, 0xEE, 0xDD, 0xCC, 0xBB, 0xAA, 0x99, 0x88];
1203
1204        let wrong_decrypted = priv_key
1205            .decrypt(&ciphertext, engine_boots, engine_time, &wrong_priv_params)
1206            .expect("decryption should succeed cryptographically");
1207
1208        // Wrong IV should produce garbage
1209        assert_ne!(
1210            &wrong_decrypted[..plaintext.len()],
1211            plaintext,
1212            "wrong priv_params should NOT produce the original plaintext"
1213        );
1214
1215        // Correct priv_params should work
1216        let correct_decrypted = priv_key
1217            .decrypt(&ciphertext, engine_boots, engine_time, &correct_priv_params)
1218            .expect("correct decryption failed");
1219        assert_eq!(&correct_decrypted[..plaintext.len()], plaintext);
1220    }
1221
1222    /// Test the DES salt/IV composition against RFC 3414 Section 8.1.1.1.
1223    ///
1224    /// Asserts that the returned `privParameters` is exactly `engineBoots (4 bytes,
1225    /// big-endian) || counter (4 bytes, big-endian)` for a controlled `SaltCounter`
1226    /// value, and that the CBC IV used is `pre-IV XOR salt` by round-tripping through
1227    /// `decrypt` and by independently recomputing the expected IV.
1228    #[cfg(feature = "crypto-rustcrypto")]
1229    #[test]
1230    fn test_des_salt_and_iv_composition() {
1231        // Distinctive 16-byte key: bytes 0..8 = DES key, 8..16 = pre-IV.
1232        let key = vec![
1233            0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, // DES key
1234            0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF, 0x11, 0x22, // pre-IV
1235        ];
1236        let pre_iv = [0xAAu8, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF, 0x11, 0x22];
1237        let priv_key = PrivKey::from_bytes(PrivProtocol::Des, key).unwrap();
1238
1239        // SaltCounter::next() returns fetch_add(1)'s pre-increment value plus 1
1240        // (post-increment), so from_value(0x1000).next() == 0x1001.
1241        let salt_counter = SaltCounter::from_value(0x1000);
1242        let expected_salt_value: u32 = 0x1001;
1243
1244        let engine_boots: u32 = 0x1234_5678;
1245        let engine_time: u32 = 999;
1246        let plaintext = b"RFC 3414 8.1.1.1 salt/IV composition test";
1247
1248        let (ciphertext, priv_params) = priv_key
1249            .encrypt(plaintext, engine_boots, engine_time, Some(&salt_counter))
1250            .expect("encryption failed");
1251
1252        // 1. Salt composition: privParameters = engineBoots || counter.
1253        assert_eq!(priv_params.len(), 8);
1254        assert_eq!(&priv_params[..4], &engine_boots.to_be_bytes());
1255        assert_eq!(&priv_params[4..8], &expected_salt_value.to_be_bytes());
1256
1257        // 2. IV = pre-IV XOR salt: independently recompute and confirm it is
1258        // non-trivial (the salt actually XORed in, not a passthrough).
1259        let mut expected_iv = [0u8; 8];
1260        for i in 0..8 {
1261            expected_iv[i] = pre_iv[i] ^ priv_params[i];
1262        }
1263        assert_ne!(
1264            expected_iv, pre_iv,
1265            "salt XOR must change the IV relative to the raw pre-IV"
1266        );
1267
1268        // 3. Round-trip proof: decrypt-side IV reconstruction (pre-IV XOR
1269        // priv_params) must match the encrypt-side IV, recovering the plaintext.
1270        let decrypted = priv_key
1271            .decrypt(&ciphertext, engine_boots, engine_time, &priv_params)
1272            .expect("decryption failed");
1273        assert_eq!(&decrypted[..plaintext.len()], plaintext);
1274    }
1275
1276    /// Test that `SaltCounter` never emits duplicate salts under concurrent access.
1277    ///
1278    /// This is a regression test for the two-fetch_add race where two threads
1279    /// could both return 1 after a wraparound left the counter at 0.
1280    #[test]
1281    fn test_salt_counter_no_duplicates_concurrent() {
1282        use std::collections::HashSet;
1283        use std::sync::{Arc, Mutex};
1284        use std::thread;
1285
1286        let counter = Arc::new(SaltCounter::new());
1287        let results = Arc::new(Mutex::new(HashSet::new()));
1288        let iterations = 10_000usize;
1289        let threads = 8usize;
1290
1291        let handles: Vec<_> = (0..threads)
1292            .map(|_| {
1293                let counter = Arc::clone(&counter);
1294                let results = Arc::clone(&results);
1295                thread::spawn(move || {
1296                    for _ in 0..iterations {
1297                        let salt = counter.next();
1298                        assert_ne!(salt, 0, "SaltCounter must never return zero");
1299                        let mut set = results.lock().unwrap();
1300                        assert!(set.insert(salt), "SaltCounter emitted duplicate: {salt}");
1301                    }
1302                })
1303            })
1304            .collect();
1305
1306        for h in handles {
1307            h.join().expect("thread panicked");
1308        }
1309    }
1310
1311    /// Test that a cloned `PrivKey` starts with an independent salt counter.
1312    ///
1313    /// This is a regression test for derive(Clone) copying the `salt_counter`
1314    /// field, which caused clones to emit identical salts for their first encryptions.
1315    #[test]
1316    fn test_priv_key_clone_independent_salts() {
1317        let key = vec![0u8; 16];
1318        let original = PrivKey::from_bytes(PrivProtocol::Aes128, key).unwrap();
1319        let cloned = original.clone();
1320
1321        let plaintext = b"test";
1322
1323        // Encrypt once with each key; the priv_params (salt) must differ.
1324        let (_, salt_orig) = original.encrypt(plaintext, 0, 0, None).unwrap();
1325        let (_, salt_clone) = cloned.encrypt(plaintext, 0, 0, None).unwrap();
1326
1327        assert_ne!(
1328            salt_orig, salt_clone,
1329            "cloned PrivKey must start with an independent salt counter"
1330        );
1331    }
1332
1333    #[test]
1334    fn test_aes_wrong_engine_time_produces_garbage() {
1335        // For AES, the IV includes engine_boots and engine_time.
1336        // Wrong values should produce garbage.
1337        let key = vec![
1338            0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e,
1339            0x0f, 0x10,
1340        ];
1341
1342        let priv_key = PrivKey::from_bytes(PrivProtocol::Aes128, key).unwrap();
1343
1344        let plaintext = b"AES test message";
1345        let engine_boots = 200u32;
1346        let engine_time = 54321u32;
1347
1348        let (ciphertext, priv_params) = priv_key
1349            .encrypt(plaintext, engine_boots, engine_time, None)
1350            .expect("encryption failed");
1351
1352        // Decrypt with wrong engine_time (IV mismatch)
1353        let wrong_decrypted = priv_key
1354            .decrypt(&ciphertext, engine_boots, engine_time + 1, &priv_params)
1355            .expect("decryption should succeed cryptographically");
1356
1357        assert_ne!(
1358            wrong_decrypted.as_ref(),
1359            plaintext,
1360            "wrong engine_time should NOT produce the original plaintext"
1361        );
1362
1363        // Decrypt with wrong engine_boots (IV mismatch)
1364        let wrong_decrypted2 = priv_key
1365            .decrypt(&ciphertext, engine_boots + 1, engine_time, &priv_params)
1366            .expect("decryption should succeed cryptographically");
1367
1368        assert_ne!(
1369            wrong_decrypted2.as_ref(),
1370            plaintext,
1371            "wrong engine_boots should NOT produce the original plaintext"
1372        );
1373    }
1374}