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::{AuthProtocol, PrivProtocol};
26use crate::error::{CryptoErrorKind, Error, Result};
27
28/// Privacy key for encryption/decryption operations.
29///
30/// Derives encryption keys from a password and engine ID using the same
31/// process as authentication keys, then uses the appropriate portion
32/// based on the privacy protocol.
33///
34/// # Security
35///
36/// Key material is automatically zeroed from memory when the key is dropped,
37/// using the `zeroize` crate. This provides defense-in-depth against memory
38/// scraping attacks.
39#[derive(Clone, Zeroize, ZeroizeOnDrop)]
40pub struct PrivKey {
41    /// The localized key bytes
42    key: Vec<u8>,
43    /// Privacy protocol
44    #[zeroize(skip)]
45    protocol: PrivProtocol,
46    /// Salt counter for generating unique IVs
47    /// For thread safety, each PrivKey instance gets its own counter
48    #[zeroize(skip)]
49    salt_counter: u64,
50}
51
52/// Thread-safe salt counter for shared use across multiple encryptions.
53pub struct SaltCounter(AtomicU64);
54
55impl SaltCounter {
56    /// Create a new salt counter initialized to a pseudo-random value.
57    pub fn new() -> Self {
58        // Initialize with current time for pseudo-randomness
59        let init = std::time::SystemTime::now()
60            .duration_since(std::time::UNIX_EPOCH)
61            .map(|d| d.as_nanos() as u64)
62            .unwrap_or(0);
63        Self(AtomicU64::new(init))
64    }
65
66    /// Get the next salt value and increment the counter.
67    pub fn next(&self) -> u64 {
68        self.0.fetch_add(1, Ordering::SeqCst)
69    }
70}
71
72impl Default for SaltCounter {
73    fn default() -> Self {
74        Self::new()
75    }
76}
77
78impl PrivKey {
79    /// Derive a privacy key from a password and engine ID.
80    ///
81    /// The key derivation uses the same algorithm as authentication keys
82    /// (RFC 3414 A.2), but the resulting key is used differently:
83    /// - DES: first 8 bytes = key, last 8 bytes = pre-IV
84    /// - AES: first 16/24/32 bytes = key (depending on AES variant)
85    ///
86    /// # Performance Note
87    ///
88    /// This method performs the full key derivation (~850μs for SHA-256). When
89    /// polling many engines with shared credentials, use [`MasterKey`](super::MasterKey)
90    /// and call [`PrivKey::from_master_key`] for each engine.
91    ///
92    /// # Auth/Priv Protocol Compatibility
93    ///
94    /// The authentication protocol must produce sufficient key material for
95    /// the privacy protocol. If not, a warning is logged and the key will
96    /// be shorter than required, leading to runtime panics during encryption.
97    ///
98    /// Use [`AuthProtocol::is_compatible_with`] to check compatibility:
99    ///
100    /// | Privacy Protocol | Required Auth Protocols |
101    /// |------------------|-------------------------|
102    /// | DES, AES-128     | Any (MD5+)             |
103    /// | AES-192          | SHA-224+               |
104    /// | AES-256          | SHA-256+               |
105    ///
106    /// # Panics
107    ///
108    /// Panics during encryption if the privacy protocol requires a longer key
109    /// than the authentication protocol provides.
110    pub fn from_password(
111        auth_protocol: AuthProtocol,
112        priv_protocol: PrivProtocol,
113        password: &[u8],
114        engine_id: &[u8],
115    ) -> Self {
116        use super::MasterKey;
117
118        let master = MasterKey::from_password(auth_protocol, password);
119        Self::from_master_key(&master, priv_protocol, engine_id)
120    }
121
122    /// Derive a privacy key with optional key extension.
123    ///
124    /// When `key_extension` is [`super::KeyExtension::Blumenthal`], this method
125    /// extends the localized key to the required length for the privacy protocol,
126    /// even when the authentication protocol produces insufficient key material.
127    ///
128    /// This enables combinations like SHA-1 + AES-256 for interoperability with
129    /// net-snmp and other implementations that support draft-blumenthal-aes-usm-04.
130    pub fn from_password_extended(
131        auth_protocol: AuthProtocol,
132        priv_protocol: PrivProtocol,
133        password: &[u8],
134        engine_id: &[u8],
135        key_extension: super::KeyExtension,
136    ) -> Self {
137        use super::{KeyExtension, MasterKey, auth::extend_key};
138
139        let master = MasterKey::from_password(auth_protocol, password);
140        let localized = master.localize(engine_id);
141        let key_bytes = localized.as_bytes();
142
143        let key = match key_extension {
144            KeyExtension::None => key_bytes.to_vec(),
145            KeyExtension::Blumenthal => {
146                extend_key(auth_protocol, key_bytes, priv_protocol.key_len())
147            }
148        };
149
150        Self {
151            key,
152            protocol: priv_protocol,
153            salt_counter: Self::init_salt(),
154        }
155    }
156
157    /// Derive a privacy key from a master key and engine ID.
158    ///
159    /// This is the efficient path when you have a cached [`MasterKey`](super::MasterKey).
160    /// The master key's auth protocol must be compatible with the privacy protocol.
161    ///
162    /// # Auth/Priv Protocol Compatibility
163    ///
164    /// The authentication protocol used for the master key must produce sufficient
165    /// key material for the privacy protocol. See [`AuthProtocol::is_compatible_with`].
166    ///
167    /// # Panics
168    ///
169    /// Panics during encryption if the privacy protocol requires a longer key
170    /// than the authentication protocol provides.
171    pub fn from_master_key(
172        master: &super::MasterKey,
173        priv_protocol: PrivProtocol,
174        engine_id: &[u8],
175    ) -> Self {
176        let auth_protocol = master.protocol();
177
178        // Check auth/priv protocol compatibility
179        if !auth_protocol.is_compatible_with(priv_protocol) {
180            tracing::warn!(
181                auth_protocol = ?auth_protocol,
182                priv_protocol = ?priv_protocol,
183                auth_key_len = auth_protocol.digest_len(),
184                required_key_len = priv_protocol.key_len(),
185                "authentication protocol produces insufficient key material for privacy protocol; \
186                 use SHA-224+ for AES-192, SHA-256+ for AES-256"
187            );
188        }
189
190        // Localize the master key (per RFC 3826 Section 1.2)
191        let localized = master.localize(engine_id);
192        let key = localized.as_bytes().to_vec();
193
194        Self {
195            key,
196            protocol: priv_protocol,
197            salt_counter: Self::init_salt(),
198        }
199    }
200
201    /// Create a privacy key from raw localized key bytes.
202    pub fn from_bytes(protocol: PrivProtocol, key: impl Into<Vec<u8>>) -> Self {
203        Self {
204            key: key.into(),
205            protocol,
206            salt_counter: Self::init_salt(),
207        }
208    }
209
210    /// Initialize salt with pseudo-random value.
211    fn init_salt() -> u64 {
212        std::time::SystemTime::now()
213            .duration_since(std::time::UNIX_EPOCH)
214            .map(|d| d.as_nanos() as u64)
215            .unwrap_or(0)
216    }
217
218    /// Get the privacy protocol.
219    pub fn protocol(&self) -> PrivProtocol {
220        self.protocol
221    }
222
223    /// Get the encryption key portion.
224    pub fn encryption_key(&self) -> &[u8] {
225        match self.protocol {
226            PrivProtocol::Des => &self.key[..8],
227            PrivProtocol::Aes128 => &self.key[..16],
228            PrivProtocol::Aes192 => &self.key[..24],
229            PrivProtocol::Aes256 => &self.key[..32],
230        }
231    }
232
233    /// Encrypt data and return (ciphertext, privParameters).
234    ///
235    /// # Arguments
236    /// * `plaintext` - The data to encrypt (typically the serialized ScopedPDU)
237    /// * `engine_boots` - The authoritative engine's boot count
238    /// * `engine_time` - The authoritative engine's time
239    /// * `salt_counter` - Optional shared salt counter; if None, uses internal counter
240    ///
241    /// # Returns
242    /// * `Ok((ciphertext, priv_params))` on success
243    /// * `Err` on encryption failure
244    pub fn encrypt(
245        &mut self,
246        plaintext: &[u8],
247        engine_boots: u32,
248        engine_time: u32,
249        salt_counter: Option<&SaltCounter>,
250    ) -> Result<(Bytes, Bytes)> {
251        let salt = salt_counter.map(|c| c.next()).unwrap_or_else(|| {
252            let s = self.salt_counter;
253            self.salt_counter = self.salt_counter.wrapping_add(1);
254            s
255        });
256
257        match self.protocol {
258            PrivProtocol::Des => self.encrypt_des(plaintext, engine_boots, salt),
259            PrivProtocol::Aes128 => {
260                self.encrypt_aes(plaintext, engine_boots, engine_time, salt, 16)
261            }
262            PrivProtocol::Aes192 => {
263                self.encrypt_aes(plaintext, engine_boots, engine_time, salt, 24)
264            }
265            PrivProtocol::Aes256 => {
266                self.encrypt_aes(plaintext, engine_boots, engine_time, salt, 32)
267            }
268        }
269    }
270
271    /// Decrypt data using the privParameters from the message.
272    ///
273    /// # Arguments
274    /// * `ciphertext` - The encrypted data
275    /// * `engine_boots` - The authoritative engine's boot count (from message)
276    /// * `engine_time` - The authoritative engine's time (from message)
277    /// * `priv_params` - The privParameters field from the message
278    ///
279    /// # Returns
280    /// * `Ok(plaintext)` on success
281    /// * `Err` on decryption failure
282    pub fn decrypt(
283        &self,
284        ciphertext: &[u8],
285        engine_boots: u32,
286        engine_time: u32,
287        priv_params: &[u8],
288    ) -> Result<Bytes> {
289        if priv_params.len() != 8 {
290            return Err(Error::decrypt(
291                None,
292                CryptoErrorKind::InvalidPrivParamsLength {
293                    expected: 8,
294                    actual: priv_params.len(),
295                },
296            ));
297        }
298
299        match self.protocol {
300            PrivProtocol::Des => self.decrypt_des(ciphertext, priv_params),
301            PrivProtocol::Aes128 | PrivProtocol::Aes192 | PrivProtocol::Aes256 => {
302                self.decrypt_aes(ciphertext, engine_boots, engine_time, priv_params)
303            }
304        }
305    }
306
307    /// DES-CBC encryption (RFC 3414 Section 8.1.1).
308    fn encrypt_des(
309        &self,
310        plaintext: &[u8],
311        engine_boots: u32,
312        salt_int: u64,
313    ) -> Result<(Bytes, Bytes)> {
314        use cbc::cipher::{BlockEncryptMut, KeyIvInit};
315        type DesCbc = cbc::Encryptor<des::Des>;
316
317        // DES key is first 8 bytes
318        let key = &self.key[..8];
319        // Pre-IV is last 8 bytes of 16-byte privKey
320        let pre_iv = &self.key[8..16];
321
322        // Salt = engineBoots (4 bytes MSB) || counter (4 bytes MSB)
323        // We use the lower 32 bits of salt_int as the counter
324        let mut salt = [0u8; 8];
325        salt[..4].copy_from_slice(&engine_boots.to_be_bytes());
326        salt[4..].copy_from_slice(&(salt_int as u32).to_be_bytes());
327
328        // IV = pre-IV XOR salt
329        let mut iv = [0u8; 8];
330        for i in 0..8 {
331            iv[i] = pre_iv[i] ^ salt[i];
332        }
333
334        // Pad plaintext to multiple of 8 bytes
335        let pad_len = (8 - (plaintext.len() % 8)) % 8;
336        let padded_len = plaintext.len() + if pad_len == 0 { 0 } else { pad_len };
337        // DES requires at least some padding if not aligned
338        let padded_len = if padded_len == plaintext.len() && !plaintext.len().is_multiple_of(8) {
339            plaintext.len() + (8 - plaintext.len() % 8)
340        } else {
341            padded_len
342        };
343
344        let mut buffer = vec![
345            0u8;
346            if padded_len > plaintext.len() {
347                padded_len
348            } else {
349                plaintext.len() + 8 - (plaintext.len() % 8)
350            }
351        ];
352        let padded_len = buffer.len();
353        buffer[..plaintext.len()].copy_from_slice(plaintext);
354
355        // Encrypt in-place
356        let cipher = DesCbc::new_from_slices(key, &iv)
357            .map_err(|_| Error::encrypt(None, CryptoErrorKind::InvalidKeyLength))?;
358
359        let ciphertext = cipher
360            .encrypt_padded_mut::<cbc::cipher::block_padding::NoPadding>(&mut buffer, padded_len)
361            .map_err(|_| Error::encrypt(None, CryptoErrorKind::CipherError))?;
362
363        Ok((
364            Bytes::copy_from_slice(ciphertext),
365            Bytes::copy_from_slice(&salt),
366        ))
367    }
368
369    /// DES-CBC decryption (RFC 3414 Section 8.1.1).
370    fn decrypt_des(&self, ciphertext: &[u8], priv_params: &[u8]) -> Result<Bytes> {
371        use cbc::cipher::{BlockDecryptMut, KeyIvInit};
372        type DesCbc = cbc::Decryptor<des::Des>;
373
374        if !ciphertext.len().is_multiple_of(8) {
375            return Err(Error::decrypt(
376                None,
377                CryptoErrorKind::InvalidCiphertextLength {
378                    length: ciphertext.len(),
379                    block_size: 8,
380                },
381            ));
382        }
383
384        // DES key is first 8 bytes
385        let key = &self.key[..8];
386        // Pre-IV is last 8 bytes of 16-byte privKey
387        let pre_iv = &self.key[8..16];
388
389        // Salt is the privParameters
390        let salt = priv_params;
391
392        // IV = pre-IV XOR salt
393        let mut iv = [0u8; 8];
394        for i in 0..8 {
395            iv[i] = pre_iv[i] ^ salt[i];
396        }
397
398        // Decrypt
399        let cipher = DesCbc::new_from_slices(key, &iv)
400            .map_err(|_| Error::decrypt(None, CryptoErrorKind::InvalidKeyLength))?;
401
402        let mut buffer = ciphertext.to_vec();
403        let plaintext = cipher
404            .decrypt_padded_mut::<cbc::cipher::block_padding::NoPadding>(&mut buffer)
405            .map_err(|_| Error::decrypt(None, CryptoErrorKind::CipherError))?;
406
407        Ok(Bytes::copy_from_slice(plaintext))
408    }
409
410    /// AES-CFB encryption (RFC 3826 Section 3.1).
411    fn encrypt_aes(
412        &self,
413        plaintext: &[u8],
414        engine_boots: u32,
415        engine_time: u32,
416        salt: u64,
417        key_len: usize,
418    ) -> Result<(Bytes, Bytes)> {
419        use aes::{Aes128, Aes192, Aes256};
420        use cfb_mode::cipher::{AsyncStreamCipher, KeyIvInit};
421
422        // AES key is first key_len bytes
423        let key = &self.key[..key_len];
424
425        // Salt as 8 bytes (big-endian)
426        let salt_bytes = salt.to_be_bytes();
427
428        // IV = engineBoots (4) || engineTime (4) || salt (8) = 16 bytes
429        // This is CONCATENATION, not XOR (unlike DES)
430        let mut iv = [0u8; 16];
431        iv[..4].copy_from_slice(&engine_boots.to_be_bytes());
432        iv[4..8].copy_from_slice(&engine_time.to_be_bytes());
433        iv[8..].copy_from_slice(&salt_bytes);
434
435        let mut buffer = plaintext.to_vec();
436
437        match key_len {
438            16 => {
439                type Aes128Cfb = cfb_mode::Encryptor<Aes128>;
440                let cipher = Aes128Cfb::new_from_slices(key, &iv)
441                    .map_err(|_| Error::encrypt(None, CryptoErrorKind::InvalidKeyLength))?;
442                cipher.encrypt(&mut buffer);
443            }
444            24 => {
445                type Aes192Cfb = cfb_mode::Encryptor<Aes192>;
446                let cipher = Aes192Cfb::new_from_slices(key, &iv)
447                    .map_err(|_| Error::encrypt(None, CryptoErrorKind::InvalidKeyLength))?;
448                cipher.encrypt(&mut buffer);
449            }
450            32 => {
451                type Aes256Cfb = cfb_mode::Encryptor<Aes256>;
452                let cipher = Aes256Cfb::new_from_slices(key, &iv)
453                    .map_err(|_| Error::encrypt(None, CryptoErrorKind::InvalidKeyLength))?;
454                cipher.encrypt(&mut buffer);
455            }
456            _ => {
457                return Err(Error::encrypt(None, CryptoErrorKind::UnsupportedProtocol));
458            }
459        }
460
461        Ok((Bytes::from(buffer), Bytes::copy_from_slice(&salt_bytes)))
462    }
463
464    /// AES-CFB decryption (RFC 3826 Section 3.1.4).
465    fn decrypt_aes(
466        &self,
467        ciphertext: &[u8],
468        engine_boots: u32,
469        engine_time: u32,
470        priv_params: &[u8],
471    ) -> Result<Bytes> {
472        use aes::{Aes128, Aes192, Aes256};
473        use cfb_mode::cipher::{AsyncStreamCipher, KeyIvInit};
474
475        let key_len = match self.protocol {
476            PrivProtocol::Aes128 => 16,
477            PrivProtocol::Aes192 => 24,
478            PrivProtocol::Aes256 => 32,
479            _ => unreachable!(),
480        };
481
482        // AES key is first key_len bytes
483        let key = &self.key[..key_len];
484
485        // IV = engineBoots (4) || engineTime (4) || salt (8) = 16 bytes
486        let mut iv = [0u8; 16];
487        iv[..4].copy_from_slice(&engine_boots.to_be_bytes());
488        iv[4..8].copy_from_slice(&engine_time.to_be_bytes());
489        iv[8..].copy_from_slice(priv_params);
490
491        let mut buffer = ciphertext.to_vec();
492
493        match key_len {
494            16 => {
495                type Aes128Cfb = cfb_mode::Decryptor<Aes128>;
496                let cipher = Aes128Cfb::new_from_slices(key, &iv)
497                    .map_err(|_| Error::decrypt(None, CryptoErrorKind::InvalidKeyLength))?;
498                cipher.decrypt(&mut buffer);
499            }
500            24 => {
501                type Aes192Cfb = cfb_mode::Decryptor<Aes192>;
502                let cipher = Aes192Cfb::new_from_slices(key, &iv)
503                    .map_err(|_| Error::decrypt(None, CryptoErrorKind::InvalidKeyLength))?;
504                cipher.decrypt(&mut buffer);
505            }
506            32 => {
507                type Aes256Cfb = cfb_mode::Decryptor<Aes256>;
508                let cipher = Aes256Cfb::new_from_slices(key, &iv)
509                    .map_err(|_| Error::decrypt(None, CryptoErrorKind::InvalidKeyLength))?;
510                cipher.decrypt(&mut buffer);
511            }
512            _ => {
513                return Err(Error::decrypt(None, CryptoErrorKind::UnsupportedProtocol));
514            }
515        }
516
517        Ok(Bytes::from(buffer))
518    }
519}
520
521impl std::fmt::Debug for PrivKey {
522    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
523        f.debug_struct("PrivKey")
524            .field("protocol", &self.protocol)
525            .field("key", &"[REDACTED]")
526            .finish()
527    }
528}
529
530#[cfg(test)]
531mod tests {
532    use super::*;
533    use crate::format::hex::decode as decode_hex;
534
535    #[test]
536    fn test_des_encrypt_decrypt_roundtrip() {
537        // Create a 16-byte key (8 for DES, 8 for pre-IV)
538        let key = vec![
539            0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, // DES key
540            0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, // pre-IV
541        ];
542        let mut priv_key = PrivKey::from_bytes(PrivProtocol::Des, key);
543
544        let plaintext = b"Hello, SNMPv3 World!";
545        let engine_boots = 100u32;
546        let engine_time = 12345u32;
547
548        let (ciphertext, priv_params) = priv_key
549            .encrypt(plaintext, engine_boots, engine_time, None)
550            .expect("encryption failed");
551
552        // Verify ciphertext is different from plaintext
553        assert_ne!(ciphertext.as_ref(), plaintext);
554        // Verify priv_params is 8 bytes
555        assert_eq!(priv_params.len(), 8);
556
557        // Decrypt
558        let decrypted = priv_key
559            .decrypt(&ciphertext, engine_boots, engine_time, &priv_params)
560            .expect("decryption failed");
561
562        // DES pads to 8-byte boundary, so decrypted may be longer
563        assert!(decrypted.len() >= plaintext.len());
564        assert_eq!(&decrypted[..plaintext.len()], plaintext);
565    }
566
567    #[test]
568    fn test_aes128_encrypt_decrypt_roundtrip() {
569        // Create a 16-byte key for AES-128
570        let key = vec![
571            0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e,
572            0x0f, 0x10,
573        ];
574        let mut priv_key = PrivKey::from_bytes(PrivProtocol::Aes128, key);
575
576        let plaintext = b"Hello, SNMPv3 AES World!";
577        let engine_boots = 200u32;
578        let engine_time = 54321u32;
579
580        let (ciphertext, priv_params) = priv_key
581            .encrypt(plaintext, engine_boots, engine_time, None)
582            .expect("encryption failed");
583
584        // Verify ciphertext is different from plaintext
585        assert_ne!(ciphertext.as_ref(), plaintext);
586        // Verify priv_params is 8 bytes (salt)
587        assert_eq!(priv_params.len(), 8);
588
589        // Decrypt
590        let decrypted = priv_key
591            .decrypt(&ciphertext, engine_boots, engine_time, &priv_params)
592            .expect("decryption failed");
593
594        // AES-CFB doesn't require padding, so lengths should match
595        assert_eq!(decrypted.len(), plaintext.len());
596        assert_eq!(decrypted.as_ref(), plaintext);
597    }
598
599    #[test]
600    fn test_des_invalid_ciphertext_length() {
601        let key = vec![0u8; 16];
602        let priv_key = PrivKey::from_bytes(PrivProtocol::Des, key);
603
604        // Ciphertext not multiple of 8
605        let ciphertext = [0u8; 13];
606        let priv_params = [0u8; 8];
607
608        let result = priv_key.decrypt(&ciphertext, 0, 0, &priv_params);
609        assert!(result.is_err());
610    }
611
612    #[test]
613    fn test_invalid_priv_params_length() {
614        let key = vec![0u8; 16];
615        let priv_key = PrivKey::from_bytes(PrivProtocol::Aes128, key);
616
617        // priv_params should be 8 bytes
618        let ciphertext = [0u8; 16];
619        let priv_params = [0u8; 4]; // Wrong length
620
621        let result = priv_key.decrypt(&ciphertext, 0, 0, &priv_params);
622        assert!(result.is_err());
623    }
624
625    #[test]
626    fn test_salt_counter() {
627        let counter = SaltCounter::new();
628        let s1 = counter.next();
629        let s2 = counter.next();
630        let s3 = counter.next();
631
632        // Each call should increment
633        assert_eq!(s2, s1.wrapping_add(1));
634        assert_eq!(s3, s2.wrapping_add(1));
635    }
636
637    #[test]
638    fn test_multiple_encryptions_different_salt() {
639        let key = vec![0u8; 16];
640        let mut priv_key = PrivKey::from_bytes(PrivProtocol::Aes128, key);
641
642        let plaintext = b"test data";
643
644        let (_, salt1) = priv_key.encrypt(plaintext, 0, 0, None).unwrap();
645        let (_, salt2) = priv_key.encrypt(plaintext, 0, 0, None).unwrap();
646
647        // Salts should be different for each encryption
648        assert_ne!(salt1, salt2);
649    }
650
651    #[test]
652    fn test_from_password() {
653        // Test that we can derive a privacy key from a password
654        let password = b"maplesyrup";
655        let engine_id = decode_hex("000000000000000000000002").unwrap();
656
657        let mut priv_key = PrivKey::from_password(
658            AuthProtocol::Sha1,
659            PrivProtocol::Aes128,
660            password,
661            &engine_id,
662        );
663
664        // Just verify we can encrypt/decrypt with the derived key
665        let plaintext = b"test message";
666        let (ciphertext, priv_params) = priv_key.encrypt(plaintext, 100, 200, None).unwrap();
667        let decrypted = priv_key
668            .decrypt(&ciphertext, 100, 200, &priv_params)
669            .unwrap();
670
671        assert_eq!(decrypted.as_ref(), plaintext);
672    }
673
674    #[test]
675    fn test_aes192_encrypt_decrypt_roundtrip() {
676        // Create a 24-byte key for AES-192
677        let key = vec![
678            0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e,
679            0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18,
680        ];
681        let mut priv_key = PrivKey::from_bytes(PrivProtocol::Aes192, key);
682
683        let plaintext = b"Hello, SNMPv3 AES-192 World!";
684        let engine_boots = 300u32;
685        let engine_time = 67890u32;
686
687        let (ciphertext, priv_params) = priv_key
688            .encrypt(plaintext, engine_boots, engine_time, None)
689            .expect("AES-192 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("AES-192 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    #[test]
707    fn test_aes256_encrypt_decrypt_roundtrip() {
708        // Create a 32-byte key for AES-256
709        let key = vec![
710            0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e,
711            0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c,
712            0x1d, 0x1e, 0x1f, 0x20,
713        ];
714        let mut priv_key = PrivKey::from_bytes(PrivProtocol::Aes256, key);
715
716        let plaintext = b"Hello, SNMPv3 AES-256 World!";
717        let engine_boots = 400u32;
718        let engine_time = 11111u32;
719
720        let (ciphertext, priv_params) = priv_key
721            .encrypt(plaintext, engine_boots, engine_time, None)
722            .expect("AES-256 encryption failed");
723
724        // Verify ciphertext is different from plaintext
725        assert_ne!(ciphertext.as_ref(), plaintext);
726        // Verify priv_params is 8 bytes (salt)
727        assert_eq!(priv_params.len(), 8);
728
729        // Decrypt
730        let decrypted = priv_key
731            .decrypt(&ciphertext, engine_boots, engine_time, &priv_params)
732            .expect("AES-256 decryption failed");
733
734        // AES-CFB doesn't require padding, so lengths should match
735        assert_eq!(decrypted.len(), plaintext.len());
736        assert_eq!(decrypted.as_ref(), plaintext);
737    }
738
739    #[test]
740    fn test_aes192_from_password() {
741        // For AES-192 (24-byte key), we need SHA-224 or higher auth protocol
742        let password = b"longpassword123";
743        let engine_id = decode_hex("80001f8880e9b104617361000000").unwrap();
744
745        let mut priv_key = PrivKey::from_password(
746            AuthProtocol::Sha256, // SHA-256 produces 32 bytes, enough for AES-192
747            PrivProtocol::Aes192,
748            password,
749            &engine_id,
750        );
751
752        let plaintext = b"test message for AES-192";
753        let (ciphertext, priv_params) = priv_key.encrypt(plaintext, 100, 200, None).unwrap();
754        let decrypted = priv_key
755            .decrypt(&ciphertext, 100, 200, &priv_params)
756            .unwrap();
757
758        assert_eq!(decrypted.as_ref(), plaintext);
759    }
760
761    #[test]
762    fn test_aes256_from_password() {
763        // For AES-256 (32-byte key), we need SHA-256 or higher auth protocol
764        let password = b"anotherlongpassword456";
765        let engine_id = decode_hex("80001f8880e9b104617361000000").unwrap();
766
767        let mut priv_key = PrivKey::from_password(
768            AuthProtocol::Sha256, // SHA-256 produces 32 bytes, exactly enough for AES-256
769            PrivProtocol::Aes256,
770            password,
771            &engine_id,
772        );
773
774        let plaintext = b"test message for AES-256";
775        let (ciphertext, priv_params) = priv_key.encrypt(plaintext, 100, 200, None).unwrap();
776        let decrypted = priv_key
777            .decrypt(&ciphertext, 100, 200, &priv_params)
778            .unwrap();
779
780        assert_eq!(decrypted.as_ref(), plaintext);
781    }
782
783    // ========================================================================
784    // Wrong Key Decryption Tests
785    //
786    // These tests verify that decryption with the wrong key produces garbage,
787    // not the original plaintext. Note: Stream ciphers like AES-CFB don't return
788    // errors on wrong-key decryption - they produce garbage. The authentication
789    // layer (HMAC) is what detects tampering/wrong keys in practice (RFC 3414).
790    // ========================================================================
791
792    #[test]
793    fn test_des_wrong_key_produces_garbage() {
794        // Correct 16-byte key
795        let correct_key = vec![
796            0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16,
797            0x17, 0x18,
798        ];
799        // Wrong key (different from correct key)
800        let wrong_key = vec![
801            0xFF, 0xFE, 0xFD, 0xFC, 0xFB, 0xFA, 0xF9, 0xF8, 0xE7, 0xE6, 0xE5, 0xE4, 0xE3, 0xE2,
802            0xE1, 0xE0,
803        ];
804
805        let mut correct_priv_key = PrivKey::from_bytes(PrivProtocol::Des, correct_key);
806        let wrong_priv_key = PrivKey::from_bytes(PrivProtocol::Des, wrong_key);
807
808        let plaintext = b"Secret SNMPv3 message data!";
809        let engine_boots = 100u32;
810        let engine_time = 12345u32;
811
812        // Encrypt with correct key
813        let (ciphertext, priv_params) = correct_priv_key
814            .encrypt(plaintext, engine_boots, engine_time, None)
815            .expect("encryption failed");
816
817        // Decrypt with wrong key - this will "succeed" but produce garbage
818        let wrong_decrypted = wrong_priv_key
819            .decrypt(&ciphertext, engine_boots, engine_time, &priv_params)
820            .expect("decryption should succeed cryptographically");
821
822        // Verify wrong key produces different output (not the original plaintext)
823        assert_ne!(
824            &wrong_decrypted[..plaintext.len()],
825            plaintext,
826            "wrong key should NOT produce the original plaintext"
827        );
828
829        // Verify correct key still works
830        let correct_decrypted = correct_priv_key
831            .decrypt(&ciphertext, engine_boots, engine_time, &priv_params)
832            .expect("correct key decryption failed");
833        assert_eq!(
834            &correct_decrypted[..plaintext.len()],
835            plaintext,
836            "correct key should produce the original plaintext"
837        );
838    }
839
840    #[test]
841    fn test_aes128_wrong_key_produces_garbage() {
842        let correct_key = vec![
843            0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e,
844            0x0f, 0x10,
845        ];
846        let wrong_key = vec![
847            0xFF, 0xFE, 0xFD, 0xFC, 0xFB, 0xFA, 0xF9, 0xF8, 0xF7, 0xF6, 0xF5, 0xF4, 0xF3, 0xF2,
848            0xF1, 0xF0,
849        ];
850
851        let mut correct_priv_key = PrivKey::from_bytes(PrivProtocol::Aes128, correct_key);
852        let wrong_priv_key = PrivKey::from_bytes(PrivProtocol::Aes128, wrong_key);
853
854        let plaintext = b"Secret AES-128 message data!";
855        let engine_boots = 200u32;
856        let engine_time = 54321u32;
857
858        // Encrypt with correct key
859        let (ciphertext, priv_params) = correct_priv_key
860            .encrypt(plaintext, engine_boots, engine_time, None)
861            .expect("encryption failed");
862
863        // Decrypt with wrong key
864        let wrong_decrypted = wrong_priv_key
865            .decrypt(&ciphertext, engine_boots, engine_time, &priv_params)
866            .expect("decryption should succeed cryptographically");
867
868        // Wrong key should produce garbage (not the original plaintext)
869        assert_ne!(
870            wrong_decrypted.as_ref(),
871            plaintext,
872            "wrong key should NOT produce the original plaintext"
873        );
874
875        // Correct key should work
876        let correct_decrypted = correct_priv_key
877            .decrypt(&ciphertext, engine_boots, engine_time, &priv_params)
878            .expect("correct key decryption failed");
879        assert_eq!(correct_decrypted.as_ref(), plaintext);
880    }
881
882    #[test]
883    fn test_aes192_wrong_key_produces_garbage() {
884        let correct_key = vec![
885            0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e,
886            0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18,
887        ];
888        let wrong_key = vec![
889            0xFF, 0xFE, 0xFD, 0xFC, 0xFB, 0xFA, 0xF9, 0xF8, 0xF7, 0xF6, 0xF5, 0xF4, 0xF3, 0xF2,
890            0xF1, 0xF0, 0xEF, 0xEE, 0xED, 0xEC, 0xEB, 0xEA, 0xE9, 0xE8,
891        ];
892
893        let mut correct_priv_key = PrivKey::from_bytes(PrivProtocol::Aes192, correct_key);
894        let wrong_priv_key = PrivKey::from_bytes(PrivProtocol::Aes192, wrong_key);
895
896        let plaintext = b"Secret AES-192 message data!";
897        let engine_boots = 300u32;
898        let engine_time = 67890u32;
899
900        let (ciphertext, priv_params) = correct_priv_key
901            .encrypt(plaintext, engine_boots, engine_time, None)
902            .expect("encryption failed");
903
904        let wrong_decrypted = wrong_priv_key
905            .decrypt(&ciphertext, engine_boots, engine_time, &priv_params)
906            .expect("decryption should succeed cryptographically");
907
908        assert_ne!(
909            wrong_decrypted.as_ref(),
910            plaintext,
911            "wrong key should NOT produce the original plaintext"
912        );
913    }
914
915    #[test]
916    fn test_aes256_wrong_key_produces_garbage() {
917        let correct_key = vec![
918            0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e,
919            0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c,
920            0x1d, 0x1e, 0x1f, 0x20,
921        ];
922        let wrong_key = vec![
923            0xFF, 0xFE, 0xFD, 0xFC, 0xFB, 0xFA, 0xF9, 0xF8, 0xF7, 0xF6, 0xF5, 0xF4, 0xF3, 0xF2,
924            0xF1, 0xF0, 0xEF, 0xEE, 0xED, 0xEC, 0xEB, 0xEA, 0xE9, 0xE8, 0xE7, 0xE6, 0xE5, 0xE4,
925            0xE3, 0xE2, 0xE1, 0xE0,
926        ];
927
928        let mut correct_priv_key = PrivKey::from_bytes(PrivProtocol::Aes256, correct_key);
929        let wrong_priv_key = PrivKey::from_bytes(PrivProtocol::Aes256, wrong_key);
930
931        let plaintext = b"Secret AES-256 message data!";
932        let engine_boots = 400u32;
933        let engine_time = 11111u32;
934
935        let (ciphertext, priv_params) = correct_priv_key
936            .encrypt(plaintext, engine_boots, engine_time, None)
937            .expect("encryption failed");
938
939        let wrong_decrypted = wrong_priv_key
940            .decrypt(&ciphertext, engine_boots, engine_time, &priv_params)
941            .expect("decryption should succeed cryptographically");
942
943        assert_ne!(
944            wrong_decrypted.as_ref(),
945            plaintext,
946            "wrong key should NOT produce the original plaintext"
947        );
948    }
949
950    #[test]
951    fn test_des_wrong_priv_params_produces_garbage() {
952        // Verify that even with the correct key, wrong priv_params (salt/IV)
953        // produces garbage. This tests the IV derivation logic.
954        let key = vec![
955            0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16,
956            0x17, 0x18,
957        ];
958
959        let mut priv_key = PrivKey::from_bytes(PrivProtocol::Des, key);
960
961        let plaintext = b"DES test message";
962        let engine_boots = 100u32;
963        let engine_time = 12345u32;
964
965        let (ciphertext, correct_priv_params) = priv_key
966            .encrypt(plaintext, engine_boots, engine_time, None)
967            .expect("encryption failed");
968
969        // Use wrong priv_params (different salt)
970        let wrong_priv_params = [0xFF, 0xEE, 0xDD, 0xCC, 0xBB, 0xAA, 0x99, 0x88];
971
972        let wrong_decrypted = priv_key
973            .decrypt(&ciphertext, engine_boots, engine_time, &wrong_priv_params)
974            .expect("decryption should succeed cryptographically");
975
976        // Wrong IV should produce garbage
977        assert_ne!(
978            &wrong_decrypted[..plaintext.len()],
979            plaintext,
980            "wrong priv_params should NOT produce the original plaintext"
981        );
982
983        // Correct priv_params should work
984        let correct_decrypted = priv_key
985            .decrypt(&ciphertext, engine_boots, engine_time, &correct_priv_params)
986            .expect("correct decryption failed");
987        assert_eq!(&correct_decrypted[..plaintext.len()], plaintext);
988    }
989
990    #[test]
991    fn test_aes_wrong_engine_time_produces_garbage() {
992        // For AES, the IV includes engine_boots and engine_time.
993        // Wrong values should produce garbage.
994        let key = vec![
995            0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e,
996            0x0f, 0x10,
997        ];
998
999        let mut priv_key = PrivKey::from_bytes(PrivProtocol::Aes128, key);
1000
1001        let plaintext = b"AES test message";
1002        let engine_boots = 200u32;
1003        let engine_time = 54321u32;
1004
1005        let (ciphertext, priv_params) = priv_key
1006            .encrypt(plaintext, engine_boots, engine_time, None)
1007            .expect("encryption failed");
1008
1009        // Decrypt with wrong engine_time (IV mismatch)
1010        let wrong_decrypted = priv_key
1011            .decrypt(&ciphertext, engine_boots, engine_time + 1, &priv_params)
1012            .expect("decryption should succeed cryptographically");
1013
1014        assert_ne!(
1015            wrong_decrypted.as_ref(),
1016            plaintext,
1017            "wrong engine_time should NOT produce the original plaintext"
1018        );
1019
1020        // Decrypt with wrong engine_boots (IV mismatch)
1021        let wrong_decrypted2 = priv_key
1022            .decrypt(&ciphertext, engine_boots + 1, engine_time, &priv_params)
1023            .expect("decryption should succeed cryptographically");
1024
1025        assert_ne!(
1026            wrong_decrypted2.as_ref(),
1027            plaintext,
1028            "wrong engine_boots should NOT produce the original plaintext"
1029        );
1030    }
1031}