1use std::sync::atomic::{AtomicU64, Ordering};
21
22use bytes::Bytes;
23use zeroize::{Zeroize, ZeroizeOnDrop};
24
25use super::{AuthProtocol, PrivProtocol};
26
27#[derive(Debug, Clone, Copy, PartialEq, Eq)]
32pub enum PrivacyError {
33 InvalidPrivParamsLength { expected: usize, actual: usize },
35 InvalidCiphertextLength { length: usize, block_size: usize },
37 InvalidKeyLength,
39 CipherError,
41 UnsupportedProtocol,
43}
44
45impl std::fmt::Display for PrivacyError {
46 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
47 match self {
48 Self::InvalidPrivParamsLength { expected, actual } => {
49 write!(
50 f,
51 "invalid privParameters length: expected {}, got {}",
52 expected, actual
53 )
54 }
55 Self::InvalidCiphertextLength { length, block_size } => {
56 write!(
57 f,
58 "ciphertext length {} not multiple of block size {}",
59 length, block_size
60 )
61 }
62 Self::InvalidKeyLength => write!(f, "invalid key length"),
63 Self::CipherError => write!(f, "cipher operation failed"),
64 Self::UnsupportedProtocol => write!(f, "unsupported privacy protocol"),
65 }
66 }
67}
68
69impl std::error::Error for PrivacyError {}
70
71pub type PrivacyResult<T> = std::result::Result<T, PrivacyError>;
73
74fn random_nonzero_u64() -> u64 {
78 let mut buf = [0u8; 8];
79 loop {
80 getrandom::fill(&mut buf).expect("getrandom failed");
81 let val = u64::from_ne_bytes(buf);
82 if val != 0 {
83 return val;
84 }
85 }
87}
88
89#[derive(Zeroize, ZeroizeOnDrop)]
101pub struct PrivKey {
102 key: Vec<u8>,
104 #[zeroize(skip)]
106 protocol: PrivProtocol,
107 #[zeroize(skip)]
110 salt_counter: AtomicU64,
111}
112
113pub struct SaltCounter(AtomicU64);
115
116impl SaltCounter {
117 pub fn new() -> Self {
119 Self(AtomicU64::new(random_nonzero_u64()))
120 }
121
122 pub fn from_value(value: u64) -> Self {
126 Self(AtomicU64::new(value))
127 }
128
129 pub fn next(&self) -> u64 {
138 let old = self.0.fetch_add(1, Ordering::SeqCst);
139 let val = old.wrapping_add(1);
140 if val != 0 {
141 return val;
142 }
143 let _ = self
146 .0
147 .compare_exchange(0, 1, Ordering::SeqCst, Ordering::SeqCst);
148 1
149 }
150}
151
152impl Default for SaltCounter {
153 fn default() -> Self {
154 Self::new()
155 }
156}
157
158impl PrivKey {
159 pub fn from_password(
196 auth_protocol: AuthProtocol,
197 priv_protocol: PrivProtocol,
198 password: &[u8],
199 engine_id: &[u8],
200 ) -> Self {
201 use super::MasterKey;
202
203 let master = MasterKey::from_password(auth_protocol, password);
204 Self::from_master_key(&master, priv_protocol, engine_id)
205 }
206
207 pub fn from_master_key(
229 master: &super::MasterKey,
230 priv_protocol: PrivProtocol,
231 engine_id: &[u8],
232 ) -> Self {
233 use super::{
234 KeyExtension,
235 auth::{extend_key, extend_key_reeder},
236 };
237
238 let auth_protocol = master.protocol();
239 let key_extension = priv_protocol.key_extension_for(auth_protocol);
240
241 let localized = master.localize(engine_id);
243 let key_bytes = localized.as_bytes();
244
245 let key = match key_extension {
246 KeyExtension::None => key_bytes.to_vec(),
247 KeyExtension::Blumenthal => {
248 extend_key(auth_protocol, key_bytes, priv_protocol.key_len())
249 }
250 KeyExtension::Reeder => {
251 extend_key_reeder(auth_protocol, key_bytes, engine_id, priv_protocol.key_len())
252 }
253 };
254
255 Self {
256 key,
257 protocol: priv_protocol,
258 salt_counter: Self::init_salt(),
259 }
260 }
261
262 pub fn from_bytes(protocol: PrivProtocol, key: impl Into<Vec<u8>>) -> Self {
264 Self {
265 key: key.into(),
266 protocol,
267 salt_counter: Self::init_salt(),
268 }
269 }
270
271 fn init_salt() -> AtomicU64 {
275 AtomicU64::new(random_nonzero_u64())
276 }
277
278 pub fn protocol(&self) -> PrivProtocol {
280 self.protocol
281 }
282
283 pub fn encryption_key(&self) -> &[u8] {
285 match self.protocol {
286 PrivProtocol::Des => &self.key[..8],
287 PrivProtocol::Des3 => &self.key[..24],
288 PrivProtocol::Aes128 => &self.key[..16],
289 PrivProtocol::Aes192 => &self.key[..24],
290 PrivProtocol::Aes256 => &self.key[..32],
291 }
292 }
293
294 pub fn encrypt(
306 &self,
307 plaintext: &[u8],
308 engine_boots: u32,
309 engine_time: u32,
310 salt_counter: Option<&SaltCounter>,
311 ) -> PrivacyResult<(Bytes, Bytes)> {
312 let salt = salt_counter.map(|c| c.next()).unwrap_or_else(|| {
313 let val = self.salt_counter.fetch_add(1, Ordering::Relaxed);
315 if val != 0 {
316 return val;
317 }
318 self.salt_counter.fetch_add(1, Ordering::Relaxed)
320 });
321
322 match self.protocol {
323 PrivProtocol::Des => self.encrypt_des(plaintext, engine_boots, salt),
324 PrivProtocol::Des3 => self.encrypt_des3(plaintext, engine_boots, salt),
325 PrivProtocol::Aes128 => {
326 self.encrypt_aes(plaintext, engine_boots, engine_time, salt, 16)
327 }
328 PrivProtocol::Aes192 => {
329 self.encrypt_aes(plaintext, engine_boots, engine_time, salt, 24)
330 }
331 PrivProtocol::Aes256 => {
332 self.encrypt_aes(plaintext, engine_boots, engine_time, salt, 32)
333 }
334 }
335 }
336
337 pub fn decrypt(
349 &self,
350 ciphertext: &[u8],
351 engine_boots: u32,
352 engine_time: u32,
353 priv_params: &[u8],
354 ) -> PrivacyResult<Bytes> {
355 if priv_params.len() != 8 {
356 tracing::debug!(target: "async_snmp::crypto", { expected = 8, actual = priv_params.len() }, "invalid privParameters length");
357 return Err(PrivacyError::InvalidPrivParamsLength {
358 expected: 8,
359 actual: priv_params.len(),
360 });
361 }
362
363 match self.protocol {
364 PrivProtocol::Des => self.decrypt_des(ciphertext, priv_params),
365 PrivProtocol::Des3 => self.decrypt_des3(ciphertext, priv_params),
366 PrivProtocol::Aes128 | PrivProtocol::Aes192 | PrivProtocol::Aes256 => {
367 self.decrypt_aes(ciphertext, engine_boots, engine_time, priv_params)
368 }
369 }
370 }
371
372 fn encrypt_des(
374 &self,
375 plaintext: &[u8],
376 engine_boots: u32,
377 salt_int: u64,
378 ) -> PrivacyResult<(Bytes, Bytes)> {
379 use cbc::cipher::{BlockEncryptMut, KeyIvInit};
380 type DesCbc = cbc::Encryptor<des::Des>;
381
382 let key = &self.key[..8];
384 let pre_iv = &self.key[8..16];
386
387 let mut salt = [0u8; 8];
390 salt[..4].copy_from_slice(&engine_boots.to_be_bytes());
391 salt[4..].copy_from_slice(&(salt_int as u32).to_be_bytes());
392
393 let mut iv = [0u8; 8];
395 for i in 0..8 {
396 iv[i] = pre_iv[i] ^ salt[i];
397 }
398
399 let padded_len = plaintext.len().next_multiple_of(8);
401 let mut buffer = vec![0u8; padded_len];
402 buffer[..plaintext.len()].copy_from_slice(plaintext);
403
404 let cipher = DesCbc::new_from_slices(key, &iv).map_err(|_| {
406 tracing::debug!(target: "async_snmp::crypto", "DES encryption failed: invalid key length");
407 PrivacyError::InvalidKeyLength
408 })?;
409
410 let ciphertext = cipher
411 .encrypt_padded_mut::<cbc::cipher::block_padding::NoPadding>(&mut buffer, padded_len)
412 .map_err(|_| {
413 tracing::debug!(target: "async_snmp::crypto", "DES encryption failed: cipher error");
414 PrivacyError::CipherError
415 })?;
416
417 Ok((
418 Bytes::copy_from_slice(ciphertext),
419 Bytes::copy_from_slice(&salt),
420 ))
421 }
422
423 fn decrypt_des(&self, ciphertext: &[u8], priv_params: &[u8]) -> PrivacyResult<Bytes> {
425 use cbc::cipher::{BlockDecryptMut, KeyIvInit};
426 type DesCbc = cbc::Decryptor<des::Des>;
427
428 if !ciphertext.len().is_multiple_of(8) {
429 tracing::debug!(target: "async_snmp::crypto", { length = ciphertext.len(), block_size = 8 }, "DES decryption failed: invalid ciphertext length");
430 return Err(PrivacyError::InvalidCiphertextLength {
431 length: ciphertext.len(),
432 block_size: 8,
433 });
434 }
435
436 let key = &self.key[..8];
438 let pre_iv = &self.key[8..16];
440
441 let salt = priv_params;
443
444 let mut iv = [0u8; 8];
446 for i in 0..8 {
447 iv[i] = pre_iv[i] ^ salt[i];
448 }
449
450 let cipher = DesCbc::new_from_slices(key, &iv).map_err(|_| {
452 tracing::debug!(target: "async_snmp::crypto", "DES decryption failed: invalid key length");
453 PrivacyError::InvalidKeyLength
454 })?;
455
456 let mut buffer = ciphertext.to_vec();
457 let plaintext = cipher
458 .decrypt_padded_mut::<cbc::cipher::block_padding::NoPadding>(&mut buffer)
459 .map_err(|_| {
460 tracing::debug!(target: "async_snmp::crypto", "DES decryption failed: cipher error");
461 PrivacyError::CipherError
462 })?;
463
464 Ok(Bytes::copy_from_slice(plaintext))
465 }
466
467 fn encrypt_des3(
469 &self,
470 plaintext: &[u8],
471 engine_boots: u32,
472 salt_int: u64,
473 ) -> PrivacyResult<(Bytes, Bytes)> {
474 use cbc::cipher::{BlockEncryptMut, KeyIvInit};
475 type Des3Cbc = cbc::Encryptor<des::TdesEde3>;
476
477 let key = &self.key[..24];
479 let pre_iv = &self.key[24..32];
481
482 let mut salt = [0u8; 8];
484 salt[..4].copy_from_slice(&engine_boots.to_be_bytes());
485 salt[4..].copy_from_slice(&(salt_int as u32).to_be_bytes());
486
487 let mut iv = [0u8; 8];
489 for i in 0..8 {
490 iv[i] = pre_iv[i] ^ salt[i];
491 }
492
493 let padded_len = plaintext.len().next_multiple_of(8);
495 let mut buffer = vec![0u8; padded_len];
496 buffer[..plaintext.len()].copy_from_slice(plaintext);
497
498 let cipher = Des3Cbc::new_from_slices(key, &iv).map_err(|_| {
500 tracing::debug!(target: "async_snmp::crypto", "3DES encryption failed: invalid key length");
501 PrivacyError::InvalidKeyLength
502 })?;
503
504 let ciphertext = cipher
505 .encrypt_padded_mut::<cbc::cipher::block_padding::NoPadding>(&mut buffer, padded_len)
506 .map_err(|_| {
507 tracing::debug!(target: "async_snmp::crypto", "3DES encryption failed: cipher error");
508 PrivacyError::CipherError
509 })?;
510
511 Ok((
512 Bytes::copy_from_slice(ciphertext),
513 Bytes::copy_from_slice(&salt),
514 ))
515 }
516
517 fn decrypt_des3(&self, ciphertext: &[u8], priv_params: &[u8]) -> PrivacyResult<Bytes> {
519 use cbc::cipher::{BlockDecryptMut, KeyIvInit};
520 type Des3Cbc = cbc::Decryptor<des::TdesEde3>;
521
522 if !ciphertext.len().is_multiple_of(8) {
523 tracing::debug!(target: "async_snmp::crypto", { length = ciphertext.len(), block_size = 8 }, "3DES decryption failed: invalid ciphertext length");
524 return Err(PrivacyError::InvalidCiphertextLength {
525 length: ciphertext.len(),
526 block_size: 8,
527 });
528 }
529
530 let key = &self.key[..24];
532 let pre_iv = &self.key[24..32];
534
535 let salt = priv_params;
537
538 let mut iv = [0u8; 8];
540 for i in 0..8 {
541 iv[i] = pre_iv[i] ^ salt[i];
542 }
543
544 let cipher = Des3Cbc::new_from_slices(key, &iv).map_err(|_| {
546 tracing::debug!(target: "async_snmp::crypto", "3DES decryption failed: invalid key length");
547 PrivacyError::InvalidKeyLength
548 })?;
549
550 let mut buffer = ciphertext.to_vec();
551 let plaintext = cipher
552 .decrypt_padded_mut::<cbc::cipher::block_padding::NoPadding>(&mut buffer)
553 .map_err(|_| {
554 tracing::debug!(target: "async_snmp::crypto", "3DES decryption failed: cipher error");
555 PrivacyError::CipherError
556 })?;
557
558 Ok(Bytes::copy_from_slice(plaintext))
559 }
560
561 fn encrypt_aes(
563 &self,
564 plaintext: &[u8],
565 engine_boots: u32,
566 engine_time: u32,
567 salt: u64,
568 key_len: usize,
569 ) -> PrivacyResult<(Bytes, Bytes)> {
570 let key = &self.key[..key_len];
572
573 let salt_bytes = salt.to_be_bytes();
575
576 let mut iv = [0u8; 16];
579 iv[..4].copy_from_slice(&engine_boots.to_be_bytes());
580 iv[4..8].copy_from_slice(&engine_time.to_be_bytes());
581 iv[8..].copy_from_slice(&salt_bytes);
582
583 let mut buffer = plaintext.to_vec();
584 aes_cfb_encrypt(key, &iv, &mut buffer)?;
585
586 Ok((Bytes::from(buffer), Bytes::copy_from_slice(&salt_bytes)))
587 }
588
589 fn decrypt_aes(
591 &self,
592 ciphertext: &[u8],
593 engine_boots: u32,
594 engine_time: u32,
595 priv_params: &[u8],
596 ) -> PrivacyResult<Bytes> {
597 let key_len = match self.protocol {
598 PrivProtocol::Aes128 => 16,
599 PrivProtocol::Aes192 => 24,
600 PrivProtocol::Aes256 => 32,
601 _ => unreachable!(),
602 };
603
604 let key = &self.key[..key_len];
606
607 let mut iv = [0u8; 16];
609 iv[..4].copy_from_slice(&engine_boots.to_be_bytes());
610 iv[4..8].copy_from_slice(&engine_time.to_be_bytes());
611 iv[8..].copy_from_slice(priv_params);
612
613 let mut buffer = ciphertext.to_vec();
614 aes_cfb_decrypt(key, &iv, &mut buffer)?;
615
616 Ok(Bytes::from(buffer))
617 }
618}
619
620fn aes_cfb_encrypt(key: &[u8], iv: &[u8; 16], buffer: &mut [u8]) -> PrivacyResult<()> {
624 use aes::{Aes128, Aes192, Aes256};
625 use cfb_mode::cipher::{AsyncStreamCipher, KeyIvInit};
626
627 match key.len() {
628 16 => {
629 type Aes128Cfb = cfb_mode::Encryptor<Aes128>;
630 let cipher = Aes128Cfb::new_from_slices(key, iv).map_err(|_| {
631 tracing::debug!(target: "async_snmp::crypto", "AES-128 encryption failed: invalid key length");
632 PrivacyError::InvalidKeyLength
633 })?;
634 cipher.encrypt(buffer);
635 }
636 24 => {
637 type Aes192Cfb = cfb_mode::Encryptor<Aes192>;
638 let cipher = Aes192Cfb::new_from_slices(key, iv).map_err(|_| {
639 tracing::debug!(target: "async_snmp::crypto", "AES-192 encryption failed: invalid key length");
640 PrivacyError::InvalidKeyLength
641 })?;
642 cipher.encrypt(buffer);
643 }
644 32 => {
645 type Aes256Cfb = cfb_mode::Encryptor<Aes256>;
646 let cipher = Aes256Cfb::new_from_slices(key, iv).map_err(|_| {
647 tracing::debug!(target: "async_snmp::crypto", "AES-256 encryption failed: invalid key length");
648 PrivacyError::InvalidKeyLength
649 })?;
650 cipher.encrypt(buffer);
651 }
652 key_len => {
653 tracing::debug!(target: "async_snmp::crypto", { key_len }, "AES encryption failed: unsupported key length");
654 return Err(PrivacyError::UnsupportedProtocol);
655 }
656 }
657 Ok(())
658}
659
660fn aes_cfb_decrypt(key: &[u8], iv: &[u8; 16], buffer: &mut [u8]) -> PrivacyResult<()> {
664 use aes::{Aes128, Aes192, Aes256};
665 use cfb_mode::cipher::{AsyncStreamCipher, KeyIvInit};
666
667 match key.len() {
668 16 => {
669 type Aes128Cfb = cfb_mode::Decryptor<Aes128>;
670 let cipher = Aes128Cfb::new_from_slices(key, iv).map_err(|_| {
671 tracing::debug!(target: "async_snmp::crypto", "AES-128 decryption failed: invalid key length");
672 PrivacyError::InvalidKeyLength
673 })?;
674 cipher.decrypt(buffer);
675 }
676 24 => {
677 type Aes192Cfb = cfb_mode::Decryptor<Aes192>;
678 let cipher = Aes192Cfb::new_from_slices(key, iv).map_err(|_| {
679 tracing::debug!(target: "async_snmp::crypto", "AES-192 decryption failed: invalid key length");
680 PrivacyError::InvalidKeyLength
681 })?;
682 cipher.decrypt(buffer);
683 }
684 32 => {
685 type Aes256Cfb = cfb_mode::Decryptor<Aes256>;
686 let cipher = Aes256Cfb::new_from_slices(key, iv).map_err(|_| {
687 tracing::debug!(target: "async_snmp::crypto", "AES-256 decryption failed: invalid key length");
688 PrivacyError::InvalidKeyLength
689 })?;
690 cipher.decrypt(buffer);
691 }
692 key_len => {
693 tracing::debug!(target: "async_snmp::crypto", { key_len }, "AES decryption failed: unsupported key length");
694 return Err(PrivacyError::UnsupportedProtocol);
695 }
696 }
697 Ok(())
698}
699
700impl std::fmt::Debug for PrivKey {
701 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
702 f.debug_struct("PrivKey")
703 .field("protocol", &self.protocol)
704 .field("key", &"[REDACTED]")
705 .finish()
706 }
707}
708
709impl Clone for PrivKey {
710 fn clone(&self) -> Self {
711 Self {
712 key: self.key.clone(),
713 protocol: self.protocol,
714 salt_counter: Self::init_salt(),
716 }
717 }
718}
719
720#[cfg(test)]
721mod tests {
722 use super::*;
723 use crate::format::hex::decode as decode_hex;
724
725 #[test]
726 fn test_des_encrypt_decrypt_roundtrip() {
727 let key = vec![
729 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, ];
732 let priv_key = PrivKey::from_bytes(PrivProtocol::Des, key);
733
734 let plaintext = b"Hello, SNMPv3 World!";
735 let engine_boots = 100u32;
736 let engine_time = 12345u32;
737
738 let (ciphertext, priv_params) = priv_key
739 .encrypt(plaintext, engine_boots, engine_time, None)
740 .expect("encryption failed");
741
742 assert_ne!(ciphertext.as_ref(), plaintext);
744 assert_eq!(priv_params.len(), 8);
746
747 let decrypted = priv_key
749 .decrypt(&ciphertext, engine_boots, engine_time, &priv_params)
750 .expect("decryption failed");
751
752 assert!(decrypted.len() >= plaintext.len());
754 assert_eq!(&decrypted[..plaintext.len()], plaintext);
755 }
756
757 #[test]
758 fn test_des3_encrypt_decrypt_roundtrip() {
759 let key = vec![
761 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, ];
766 let priv_key = PrivKey::from_bytes(PrivProtocol::Des3, key);
767
768 let plaintext = b"Hello, SNMPv3 World with 3DES!";
769 let engine_boots = 100u32;
770 let engine_time = 12345u32;
771
772 let (ciphertext, priv_params) = priv_key
773 .encrypt(plaintext, engine_boots, engine_time, None)
774 .expect("encryption failed");
775
776 assert_ne!(ciphertext.as_ref(), plaintext);
778 assert_eq!(priv_params.len(), 8);
780
781 let decrypted = priv_key
783 .decrypt(&ciphertext, engine_boots, engine_time, &priv_params)
784 .expect("decryption failed");
785
786 assert!(decrypted.len() >= plaintext.len());
788 assert_eq!(&decrypted[..plaintext.len()], plaintext);
789 }
790
791 #[test]
792 fn test_aes128_encrypt_decrypt_roundtrip() {
793 let key = vec![
795 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e,
796 0x0f, 0x10,
797 ];
798 let priv_key = PrivKey::from_bytes(PrivProtocol::Aes128, key);
799
800 let plaintext = b"Hello, SNMPv3 AES World!";
801 let engine_boots = 200u32;
802 let engine_time = 54321u32;
803
804 let (ciphertext, priv_params) = priv_key
805 .encrypt(plaintext, engine_boots, engine_time, None)
806 .expect("encryption failed");
807
808 assert_ne!(ciphertext.as_ref(), plaintext);
810 assert_eq!(priv_params.len(), 8);
812
813 let decrypted = priv_key
815 .decrypt(&ciphertext, engine_boots, engine_time, &priv_params)
816 .expect("decryption failed");
817
818 assert_eq!(decrypted.len(), plaintext.len());
820 assert_eq!(decrypted.as_ref(), plaintext);
821 }
822
823 #[test]
824 fn test_des_invalid_ciphertext_length() {
825 let key = vec![0u8; 16];
826 let priv_key = PrivKey::from_bytes(PrivProtocol::Des, key);
827
828 let ciphertext = [0u8; 13];
830 let priv_params = [0u8; 8];
831
832 let result = priv_key.decrypt(&ciphertext, 0, 0, &priv_params);
833 assert!(result.is_err());
834 }
835
836 #[test]
837 fn test_invalid_priv_params_length() {
838 let key = vec![0u8; 16];
839 let priv_key = PrivKey::from_bytes(PrivProtocol::Aes128, key);
840
841 let ciphertext = [0u8; 16];
843 let priv_params = [0u8; 4]; let result = priv_key.decrypt(&ciphertext, 0, 0, &priv_params);
846 assert!(result.is_err());
847 }
848
849 #[test]
850 fn test_salt_counter() {
851 let counter = SaltCounter::new();
852 let s1 = counter.next();
853 let s2 = counter.next();
854 let s3 = counter.next();
855
856 assert_eq!(s2, s1.wrapping_add(1));
858 assert_eq!(s3, s2.wrapping_add(1));
859 }
860
861 #[test]
866 fn test_salt_counter_skips_zero() {
867 let counter = SaltCounter::from_value(u64::MAX - 1);
873
874 let s1 = counter.next();
875 assert_eq!(s1, u64::MAX);
876
877 let s2 = counter.next();
879 assert_ne!(s2, 0, "SaltCounter should never return zero");
880 assert_eq!(s2, 1, "SaltCounter should skip 0 and return 1");
881
882 let s3 = counter.next();
884 assert_eq!(s3, 2);
885 }
886
887 #[test]
892 fn test_priv_key_internal_salt_skips_zero() {
893 let key = vec![0u8; 16];
894 let priv_key = PrivKey::from_bytes(PrivProtocol::Aes128, key);
895
896 priv_key.salt_counter.store(u64::MAX, Ordering::Relaxed);
898
899 let plaintext = b"test";
900
901 let (_, salt1) = priv_key.encrypt(plaintext, 0, 0, None).unwrap();
903 assert_eq!(
904 u64::from_be_bytes(salt1.as_ref().try_into().unwrap()),
905 u64::MAX
906 );
907
908 let (_, salt2) = priv_key.encrypt(plaintext, 0, 0, None).unwrap();
910 let salt2_value = u64::from_be_bytes(salt2.as_ref().try_into().unwrap());
911 assert_ne!(salt2_value, 0, "Salt should never be zero");
912 assert_eq!(salt2_value, 1, "Salt should skip 0 and be 1");
913
914 let (_, salt3) = priv_key.encrypt(plaintext, 0, 0, None).unwrap();
916 let salt3_value = u64::from_be_bytes(salt3.as_ref().try_into().unwrap());
917 assert_eq!(salt3_value, 2);
918 }
919
920 #[test]
921 fn test_multiple_encryptions_different_salt() {
922 let key = vec![0u8; 16];
923 let priv_key = PrivKey::from_bytes(PrivProtocol::Aes128, key);
924
925 let plaintext = b"test data";
926
927 let (_, salt1) = priv_key.encrypt(plaintext, 0, 0, None).unwrap();
928 let (_, salt2) = priv_key.encrypt(plaintext, 0, 0, None).unwrap();
929
930 assert_ne!(salt1, salt2);
932 }
933
934 #[test]
935 fn test_from_password() {
936 let password = b"maplesyrup";
938 let engine_id = decode_hex("000000000000000000000002").unwrap();
939
940 let priv_key = PrivKey::from_password(
941 AuthProtocol::Sha1,
942 PrivProtocol::Aes128,
943 password,
944 &engine_id,
945 );
946
947 let plaintext = b"test message";
949 let (ciphertext, priv_params) = priv_key.encrypt(plaintext, 100, 200, None).unwrap();
950 let decrypted = priv_key
951 .decrypt(&ciphertext, 100, 200, &priv_params)
952 .unwrap();
953
954 assert_eq!(decrypted.as_ref(), plaintext);
955 }
956
957 #[test]
958 fn test_aes192_encrypt_decrypt_roundtrip() {
959 let key = vec![
961 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e,
962 0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18,
963 ];
964 let priv_key = PrivKey::from_bytes(PrivProtocol::Aes192, key);
965
966 let plaintext = b"Hello, SNMPv3 AES-192 World!";
967 let engine_boots = 300u32;
968 let engine_time = 67890u32;
969
970 let (ciphertext, priv_params) = priv_key
971 .encrypt(plaintext, engine_boots, engine_time, None)
972 .expect("AES-192 encryption failed");
973
974 assert_ne!(ciphertext.as_ref(), plaintext);
976 assert_eq!(priv_params.len(), 8);
978
979 let decrypted = priv_key
981 .decrypt(&ciphertext, engine_boots, engine_time, &priv_params)
982 .expect("AES-192 decryption failed");
983
984 assert_eq!(decrypted.len(), plaintext.len());
986 assert_eq!(decrypted.as_ref(), plaintext);
987 }
988
989 #[test]
990 fn test_aes256_encrypt_decrypt_roundtrip() {
991 let key = vec![
993 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e,
994 0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c,
995 0x1d, 0x1e, 0x1f, 0x20,
996 ];
997 let priv_key = PrivKey::from_bytes(PrivProtocol::Aes256, key);
998
999 let plaintext = b"Hello, SNMPv3 AES-256 World!";
1000 let engine_boots = 400u32;
1001 let engine_time = 11111u32;
1002
1003 let (ciphertext, priv_params) = priv_key
1004 .encrypt(plaintext, engine_boots, engine_time, None)
1005 .expect("AES-256 encryption failed");
1006
1007 assert_ne!(ciphertext.as_ref(), plaintext);
1009 assert_eq!(priv_params.len(), 8);
1011
1012 let decrypted = priv_key
1014 .decrypt(&ciphertext, engine_boots, engine_time, &priv_params)
1015 .expect("AES-256 decryption failed");
1016
1017 assert_eq!(decrypted.len(), plaintext.len());
1019 assert_eq!(decrypted.as_ref(), plaintext);
1020 }
1021
1022 #[test]
1023 fn test_aes192_from_password() {
1024 let password = b"longpassword123";
1026 let engine_id = decode_hex("80001f8880e9b104617361000000").unwrap();
1027
1028 let priv_key = PrivKey::from_password(
1029 AuthProtocol::Sha256, PrivProtocol::Aes192,
1031 password,
1032 &engine_id,
1033 );
1034
1035 let plaintext = b"test message for AES-192";
1036 let (ciphertext, priv_params) = priv_key.encrypt(plaintext, 100, 200, None).unwrap();
1037 let decrypted = priv_key
1038 .decrypt(&ciphertext, 100, 200, &priv_params)
1039 .unwrap();
1040
1041 assert_eq!(decrypted.as_ref(), plaintext);
1042 }
1043
1044 #[test]
1045 fn test_aes256_from_password() {
1046 let password = b"anotherlongpassword456";
1048 let engine_id = decode_hex("80001f8880e9b104617361000000").unwrap();
1049
1050 let priv_key = PrivKey::from_password(
1051 AuthProtocol::Sha256, PrivProtocol::Aes256,
1053 password,
1054 &engine_id,
1055 );
1056
1057 let plaintext = b"test message for AES-256";
1058 let (ciphertext, priv_params) = priv_key.encrypt(plaintext, 100, 200, None).unwrap();
1059 let decrypted = priv_key
1060 .decrypt(&ciphertext, 100, 200, &priv_params)
1061 .unwrap();
1062
1063 assert_eq!(decrypted.as_ref(), plaintext);
1064 }
1065
1066 #[test]
1076 fn test_des_wrong_key_produces_garbage() {
1077 let correct_key = vec![
1079 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16,
1080 0x17, 0x18,
1081 ];
1082 let wrong_key = vec![
1084 0xFF, 0xFE, 0xFD, 0xFC, 0xFB, 0xFA, 0xF9, 0xF8, 0xE7, 0xE6, 0xE5, 0xE4, 0xE3, 0xE2,
1085 0xE1, 0xE0,
1086 ];
1087
1088 let correct_priv_key = PrivKey::from_bytes(PrivProtocol::Des, correct_key);
1089 let wrong_priv_key = PrivKey::from_bytes(PrivProtocol::Des, wrong_key);
1090
1091 let plaintext = b"Secret SNMPv3 message data!";
1092 let engine_boots = 100u32;
1093 let engine_time = 12345u32;
1094
1095 let (ciphertext, priv_params) = correct_priv_key
1097 .encrypt(plaintext, engine_boots, engine_time, None)
1098 .expect("encryption failed");
1099
1100 let wrong_decrypted = wrong_priv_key
1102 .decrypt(&ciphertext, engine_boots, engine_time, &priv_params)
1103 .expect("decryption should succeed cryptographically");
1104
1105 assert_ne!(
1107 &wrong_decrypted[..plaintext.len()],
1108 plaintext,
1109 "wrong key should NOT produce the original plaintext"
1110 );
1111
1112 let correct_decrypted = correct_priv_key
1114 .decrypt(&ciphertext, engine_boots, engine_time, &priv_params)
1115 .expect("correct key decryption failed");
1116 assert_eq!(
1117 &correct_decrypted[..plaintext.len()],
1118 plaintext,
1119 "correct key should produce the original plaintext"
1120 );
1121 }
1122
1123 #[test]
1124 fn test_aes128_wrong_key_produces_garbage() {
1125 let correct_key = vec![
1126 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e,
1127 0x0f, 0x10,
1128 ];
1129 let wrong_key = vec![
1130 0xFF, 0xFE, 0xFD, 0xFC, 0xFB, 0xFA, 0xF9, 0xF8, 0xF7, 0xF6, 0xF5, 0xF4, 0xF3, 0xF2,
1131 0xF1, 0xF0,
1132 ];
1133
1134 let correct_priv_key = PrivKey::from_bytes(PrivProtocol::Aes128, correct_key);
1135 let wrong_priv_key = PrivKey::from_bytes(PrivProtocol::Aes128, wrong_key);
1136
1137 let plaintext = b"Secret AES-128 message data!";
1138 let engine_boots = 200u32;
1139 let engine_time = 54321u32;
1140
1141 let (ciphertext, priv_params) = correct_priv_key
1143 .encrypt(plaintext, engine_boots, engine_time, None)
1144 .expect("encryption failed");
1145
1146 let wrong_decrypted = wrong_priv_key
1148 .decrypt(&ciphertext, engine_boots, engine_time, &priv_params)
1149 .expect("decryption should succeed cryptographically");
1150
1151 assert_ne!(
1153 wrong_decrypted.as_ref(),
1154 plaintext,
1155 "wrong key should NOT produce the original plaintext"
1156 );
1157
1158 let correct_decrypted = correct_priv_key
1160 .decrypt(&ciphertext, engine_boots, engine_time, &priv_params)
1161 .expect("correct key decryption failed");
1162 assert_eq!(correct_decrypted.as_ref(), plaintext);
1163 }
1164
1165 #[test]
1166 fn test_aes192_wrong_key_produces_garbage() {
1167 let correct_key = vec![
1168 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e,
1169 0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18,
1170 ];
1171 let wrong_key = vec![
1172 0xFF, 0xFE, 0xFD, 0xFC, 0xFB, 0xFA, 0xF9, 0xF8, 0xF7, 0xF6, 0xF5, 0xF4, 0xF3, 0xF2,
1173 0xF1, 0xF0, 0xEF, 0xEE, 0xED, 0xEC, 0xEB, 0xEA, 0xE9, 0xE8,
1174 ];
1175
1176 let correct_priv_key = PrivKey::from_bytes(PrivProtocol::Aes192, correct_key);
1177 let wrong_priv_key = PrivKey::from_bytes(PrivProtocol::Aes192, wrong_key);
1178
1179 let plaintext = b"Secret AES-192 message data!";
1180 let engine_boots = 300u32;
1181 let engine_time = 67890u32;
1182
1183 let (ciphertext, priv_params) = correct_priv_key
1184 .encrypt(plaintext, engine_boots, engine_time, None)
1185 .expect("encryption failed");
1186
1187 let wrong_decrypted = wrong_priv_key
1188 .decrypt(&ciphertext, engine_boots, engine_time, &priv_params)
1189 .expect("decryption should succeed cryptographically");
1190
1191 assert_ne!(
1192 wrong_decrypted.as_ref(),
1193 plaintext,
1194 "wrong key should NOT produce the original plaintext"
1195 );
1196 }
1197
1198 #[test]
1199 fn test_aes256_wrong_key_produces_garbage() {
1200 let correct_key = vec![
1201 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e,
1202 0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c,
1203 0x1d, 0x1e, 0x1f, 0x20,
1204 ];
1205 let wrong_key = vec![
1206 0xFF, 0xFE, 0xFD, 0xFC, 0xFB, 0xFA, 0xF9, 0xF8, 0xF7, 0xF6, 0xF5, 0xF4, 0xF3, 0xF2,
1207 0xF1, 0xF0, 0xEF, 0xEE, 0xED, 0xEC, 0xEB, 0xEA, 0xE9, 0xE8, 0xE7, 0xE6, 0xE5, 0xE4,
1208 0xE3, 0xE2, 0xE1, 0xE0,
1209 ];
1210
1211 let correct_priv_key = PrivKey::from_bytes(PrivProtocol::Aes256, correct_key);
1212 let wrong_priv_key = PrivKey::from_bytes(PrivProtocol::Aes256, wrong_key);
1213
1214 let plaintext = b"Secret AES-256 message data!";
1215 let engine_boots = 400u32;
1216 let engine_time = 11111u32;
1217
1218 let (ciphertext, priv_params) = correct_priv_key
1219 .encrypt(plaintext, engine_boots, engine_time, None)
1220 .expect("encryption failed");
1221
1222 let wrong_decrypted = wrong_priv_key
1223 .decrypt(&ciphertext, engine_boots, engine_time, &priv_params)
1224 .expect("decryption should succeed cryptographically");
1225
1226 assert_ne!(
1227 wrong_decrypted.as_ref(),
1228 plaintext,
1229 "wrong key should NOT produce the original plaintext"
1230 );
1231 }
1232
1233 #[test]
1234 fn test_des_wrong_priv_params_produces_garbage() {
1235 let key = vec![
1238 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16,
1239 0x17, 0x18,
1240 ];
1241
1242 let priv_key = PrivKey::from_bytes(PrivProtocol::Des, key);
1243
1244 let plaintext = b"DES test message";
1245 let engine_boots = 100u32;
1246 let engine_time = 12345u32;
1247
1248 let (ciphertext, correct_priv_params) = priv_key
1249 .encrypt(plaintext, engine_boots, engine_time, None)
1250 .expect("encryption failed");
1251
1252 let wrong_priv_params = [0xFF, 0xEE, 0xDD, 0xCC, 0xBB, 0xAA, 0x99, 0x88];
1254
1255 let wrong_decrypted = priv_key
1256 .decrypt(&ciphertext, engine_boots, engine_time, &wrong_priv_params)
1257 .expect("decryption should succeed cryptographically");
1258
1259 assert_ne!(
1261 &wrong_decrypted[..plaintext.len()],
1262 plaintext,
1263 "wrong priv_params should NOT produce the original plaintext"
1264 );
1265
1266 let correct_decrypted = priv_key
1268 .decrypt(&ciphertext, engine_boots, engine_time, &correct_priv_params)
1269 .expect("correct decryption failed");
1270 assert_eq!(&correct_decrypted[..plaintext.len()], plaintext);
1271 }
1272
1273 #[test]
1278 fn test_salt_counter_no_duplicates_concurrent() {
1279 use std::collections::HashSet;
1280 use std::sync::{Arc, Mutex};
1281 use std::thread;
1282
1283 let counter = Arc::new(SaltCounter::new());
1284 let results = Arc::new(Mutex::new(HashSet::new()));
1285 let iterations = 10_000usize;
1286 let threads = 8usize;
1287
1288 let handles: Vec<_> = (0..threads)
1289 .map(|_| {
1290 let counter = Arc::clone(&counter);
1291 let results = Arc::clone(&results);
1292 thread::spawn(move || {
1293 for _ in 0..iterations {
1294 let salt = counter.next();
1295 assert_ne!(salt, 0, "SaltCounter must never return zero");
1296 let mut set = results.lock().unwrap();
1297 assert!(set.insert(salt), "SaltCounter emitted duplicate: {salt}");
1298 }
1299 })
1300 })
1301 .collect();
1302
1303 for h in handles {
1304 h.join().expect("thread panicked");
1305 }
1306 }
1307
1308 #[test]
1313 fn test_priv_key_clone_independent_salts() {
1314 let key = vec![0u8; 16];
1315 let original = PrivKey::from_bytes(PrivProtocol::Aes128, key);
1316 let cloned = original.clone();
1317
1318 let plaintext = b"test";
1319
1320 let (_, salt_orig) = original.encrypt(plaintext, 0, 0, None).unwrap();
1322 let (_, salt_clone) = cloned.encrypt(plaintext, 0, 0, None).unwrap();
1323
1324 assert_ne!(
1325 salt_orig, salt_clone,
1326 "cloned PrivKey must start with an independent salt counter"
1327 );
1328 }
1329
1330 #[test]
1331 fn test_aes_wrong_engine_time_produces_garbage() {
1332 let key = vec![
1335 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e,
1336 0x0f, 0x10,
1337 ];
1338
1339 let priv_key = PrivKey::from_bytes(PrivProtocol::Aes128, key);
1340
1341 let plaintext = b"AES test message";
1342 let engine_boots = 200u32;
1343 let engine_time = 54321u32;
1344
1345 let (ciphertext, priv_params) = priv_key
1346 .encrypt(plaintext, engine_boots, engine_time, None)
1347 .expect("encryption failed");
1348
1349 let wrong_decrypted = priv_key
1351 .decrypt(&ciphertext, engine_boots, engine_time + 1, &priv_params)
1352 .expect("decryption should succeed cryptographically");
1353
1354 assert_ne!(
1355 wrong_decrypted.as_ref(),
1356 plaintext,
1357 "wrong engine_time should NOT produce the original plaintext"
1358 );
1359
1360 let wrong_decrypted2 = priv_key
1362 .decrypt(&ciphertext, engine_boots + 1, engine_time, &priv_params)
1363 .expect("decryption should succeed cryptographically");
1364
1365 assert_ne!(
1366 wrong_decrypted2.as_ref(),
1367 plaintext,
1368 "wrong engine_boots should NOT produce the original plaintext"
1369 );
1370 }
1371}