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