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