1use std::sync::atomic::{AtomicU64, Ordering};
21
22use bytes::Bytes;
23use zeroize::{Zeroize, ZeroizeOnDrop};
24
25use super::crypto::{CryptoError, CryptoProvider};
26use super::{AuthProtocol, PrivProtocol};
27
28#[derive(Debug, Clone, PartialEq, Eq)]
33pub enum PrivacyError {
34 InvalidPrivParamsLength { expected: usize, actual: usize },
36 InvalidCiphertextLength { length: usize, block_size: usize },
38 Crypto(CryptoError),
40}
41
42impl From<CryptoError> for PrivacyError {
43 fn from(e: CryptoError) -> Self {
44 Self::Crypto(e)
45 }
46}
47
48impl std::fmt::Display for PrivacyError {
49 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
50 match self {
51 Self::InvalidPrivParamsLength { expected, actual } => {
52 write!(
53 f,
54 "invalid privParameters length: expected {expected}, got {actual}"
55 )
56 }
57 Self::InvalidCiphertextLength { length, block_size } => {
58 write!(
59 f,
60 "ciphertext length {length} not multiple of block size {block_size}"
61 )
62 }
63 Self::Crypto(e) => write!(f, "{e}"),
64 }
65 }
66}
67
68impl std::error::Error for PrivacyError {
69 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
70 match self {
71 Self::Crypto(e) => Some(e),
72 _ => None,
73 }
74 }
75}
76
77pub type PrivacyResult<T> = std::result::Result<T, PrivacyError>;
79
80fn random_nonzero_u64() -> super::crypto::CryptoResult<u64> {
84 let mut buf = [0u8; 8];
85 loop {
86 getrandom::fill(&mut buf).map_err(|_| super::crypto::CryptoError::RandomSource)?;
87 let val = u64::from_ne_bytes(buf);
88 if val != 0 {
89 return Ok(val);
90 }
91 }
93}
94
95#[derive(Zeroize, ZeroizeOnDrop)]
107pub struct PrivKey {
108 key: Vec<u8>,
110 #[zeroize(skip)]
112 protocol: PrivProtocol,
113 #[zeroize(skip)]
116 salt_counter: AtomicU64,
117}
118
119pub struct SaltCounter(AtomicU64);
121
122impl SaltCounter {
123 #[must_use]
128 pub fn new() -> Self {
129 Self(AtomicU64::new(
130 random_nonzero_u64().expect("OS random source unavailable"),
131 ))
132 }
133
134 #[must_use]
138 pub fn from_value(value: u64) -> Self {
139 Self(AtomicU64::new(value))
140 }
141
142 pub fn next(&self) -> u64 {
151 let old = self.0.fetch_add(1, Ordering::SeqCst);
152 let val = old.wrapping_add(1);
153 if val != 0 {
154 return val;
155 }
156 let _ = self
159 .0
160 .compare_exchange(0, 1, Ordering::SeqCst, Ordering::SeqCst);
161 1
162 }
163}
164
165impl Default for SaltCounter {
166 fn default() -> Self {
167 Self::new()
168 }
169}
170
171impl PrivKey {
172 pub fn from_password(
209 auth_protocol: AuthProtocol,
210 priv_protocol: PrivProtocol,
211 password: &[u8],
212 engine_id: &[u8],
213 ) -> super::crypto::CryptoResult<Self> {
214 use super::MasterKey;
215
216 let master = MasterKey::from_password(auth_protocol, password)?;
217 Self::from_master_key(&master, priv_protocol, engine_id)
218 }
219
220 pub fn from_master_key(
242 master: &super::MasterKey,
243 priv_protocol: PrivProtocol,
244 engine_id: &[u8],
245 ) -> super::crypto::CryptoResult<Self> {
246 use super::{
247 KeyExtension,
248 auth::{extend_key, extend_key_reeder},
249 };
250
251 let auth_protocol = master.protocol();
252 let key_extension = priv_protocol.key_extension_for(auth_protocol);
253
254 let localized = master.localize(engine_id)?;
256 let key_bytes = localized.as_bytes();
257
258 let key = match key_extension {
259 KeyExtension::None => key_bytes.to_vec(),
260 KeyExtension::Blumenthal => {
261 extend_key(auth_protocol, key_bytes, priv_protocol.key_len())?
262 }
263 KeyExtension::Reeder => {
264 extend_key_reeder(auth_protocol, key_bytes, engine_id, priv_protocol.key_len())?
265 }
266 };
267
268 Ok(Self {
269 key,
270 protocol: priv_protocol,
271 salt_counter: Self::init_salt()?,
272 })
273 }
274
275 pub fn from_bytes(
284 protocol: PrivProtocol,
285 key: impl Into<Vec<u8>>,
286 ) -> super::crypto::CryptoResult<Self> {
287 let key = key.into();
288 if key.len() < protocol.key_len() {
289 return Err(CryptoError::InvalidKeyLength);
290 }
291 Ok(Self {
292 key,
293 protocol,
294 salt_counter: Self::init_salt()?,
295 })
296 }
297
298 fn init_salt() -> super::crypto::CryptoResult<AtomicU64> {
302 Ok(AtomicU64::new(random_nonzero_u64()?))
303 }
304
305 pub fn protocol(&self) -> PrivProtocol {
307 self.protocol
308 }
309
310 pub fn encryption_key(&self) -> &[u8] {
312 match self.protocol {
313 PrivProtocol::Des => &self.key[..8],
314 PrivProtocol::Des3 => &self.key[..24],
315 PrivProtocol::Aes128 => &self.key[..16],
316 PrivProtocol::Aes192 => &self.key[..24],
317 PrivProtocol::Aes256 => &self.key[..32],
318 }
319 }
320
321 pub fn encrypt(
333 &self,
334 plaintext: &[u8],
335 engine_boots: u32,
336 engine_time: u32,
337 salt_counter: Option<&SaltCounter>,
338 ) -> PrivacyResult<(Bytes, Bytes)> {
339 let salt = salt_counter.map_or_else(
340 || {
341 let val = self.salt_counter.fetch_add(1, Ordering::Relaxed);
343 if val != 0 {
344 return val;
345 }
346 self.salt_counter.fetch_add(1, Ordering::Relaxed)
348 },
349 SaltCounter::next,
350 );
351
352 match self.protocol {
353 PrivProtocol::Des => self.encrypt_des(plaintext, engine_boots, salt),
354 PrivProtocol::Des3 => self.encrypt_des3(plaintext, engine_boots, salt),
355 PrivProtocol::Aes128 => {
356 self.encrypt_aes(plaintext, engine_boots, engine_time, salt, 16)
357 }
358 PrivProtocol::Aes192 => {
359 self.encrypt_aes(plaintext, engine_boots, engine_time, salt, 24)
360 }
361 PrivProtocol::Aes256 => {
362 self.encrypt_aes(plaintext, engine_boots, engine_time, salt, 32)
363 }
364 }
365 }
366
367 pub fn decrypt(
379 &self,
380 ciphertext: &[u8],
381 engine_boots: u32,
382 engine_time: u32,
383 priv_params: &[u8],
384 ) -> PrivacyResult<Bytes> {
385 if priv_params.len() != 8 {
386 tracing::debug!(target: "async_snmp::crypto", { expected = 8, actual = priv_params.len() }, "invalid privParameters length");
387 return Err(PrivacyError::InvalidPrivParamsLength {
388 expected: 8,
389 actual: priv_params.len(),
390 });
391 }
392
393 match self.protocol {
394 PrivProtocol::Des => self.decrypt_des(ciphertext, priv_params),
395 PrivProtocol::Des3 => self.decrypt_des3(ciphertext, priv_params),
396 PrivProtocol::Aes128 | PrivProtocol::Aes192 | PrivProtocol::Aes256 => {
397 self.decrypt_aes(ciphertext, engine_boots, engine_time, priv_params)
398 }
399 }
400 }
401
402 fn encrypt_des(
404 &self,
405 plaintext: &[u8],
406 engine_boots: u32,
407 salt_int: u64,
408 ) -> PrivacyResult<(Bytes, Bytes)> {
409 let key = &self.key[..8];
411 let pre_iv = &self.key[8..16];
413
414 let mut salt = [0u8; 8];
417 salt[..4].copy_from_slice(&engine_boots.to_be_bytes());
418 salt[4..].copy_from_slice(&(salt_int as u32).to_be_bytes());
419
420 let mut iv = [0u8; 8];
422 for i in 0..8 {
423 iv[i] = pre_iv[i] ^ salt[i];
424 }
425
426 let mut buffer = plaintext.to_vec();
427 super::crypto::provider().encrypt(PrivProtocol::Des, key, &iv, &mut buffer)?;
428
429 Ok((Bytes::from(buffer), Bytes::copy_from_slice(&salt)))
430 }
431
432 fn decrypt_des(&self, ciphertext: &[u8], priv_params: &[u8]) -> PrivacyResult<Bytes> {
434 if !ciphertext.len().is_multiple_of(8) {
435 tracing::debug!(target: "async_snmp::crypto", { length = ciphertext.len(), block_size = 8 }, "DES decryption failed: invalid ciphertext length");
436 return Err(PrivacyError::InvalidCiphertextLength {
437 length: ciphertext.len(),
438 block_size: 8,
439 });
440 }
441
442 let key = &self.key[..8];
444 let pre_iv = &self.key[8..16];
446
447 let salt = priv_params;
449
450 let mut iv = [0u8; 8];
452 for i in 0..8 {
453 iv[i] = pre_iv[i] ^ salt[i];
454 }
455
456 let mut buffer = ciphertext.to_vec();
457 super::crypto::provider().decrypt(PrivProtocol::Des, key, &iv, &mut buffer)?;
458
459 Ok(Bytes::from(buffer))
460 }
461
462 fn encrypt_des3(
464 &self,
465 plaintext: &[u8],
466 engine_boots: u32,
467 salt_int: u64,
468 ) -> PrivacyResult<(Bytes, Bytes)> {
469 let key = &self.key[..24];
471 let pre_iv = &self.key[24..32];
473
474 let mut salt = [0u8; 8];
476 salt[..4].copy_from_slice(&engine_boots.to_be_bytes());
477 salt[4..].copy_from_slice(&(salt_int as u32).to_be_bytes());
478
479 let mut iv = [0u8; 8];
481 for i in 0..8 {
482 iv[i] = pre_iv[i] ^ salt[i];
483 }
484
485 let mut buffer = plaintext.to_vec();
486 super::crypto::provider().encrypt(PrivProtocol::Des3, key, &iv, &mut buffer)?;
487
488 Ok((Bytes::from(buffer), Bytes::copy_from_slice(&salt)))
489 }
490
491 fn decrypt_des3(&self, ciphertext: &[u8], priv_params: &[u8]) -> PrivacyResult<Bytes> {
493 if !ciphertext.len().is_multiple_of(8) {
494 tracing::debug!(target: "async_snmp::crypto", { length = ciphertext.len(), block_size = 8 }, "3DES decryption failed: invalid ciphertext length");
495 return Err(PrivacyError::InvalidCiphertextLength {
496 length: ciphertext.len(),
497 block_size: 8,
498 });
499 }
500
501 let key = &self.key[..24];
503 let pre_iv = &self.key[24..32];
505
506 let salt = priv_params;
508
509 let mut iv = [0u8; 8];
511 for i in 0..8 {
512 iv[i] = pre_iv[i] ^ salt[i];
513 }
514
515 let mut buffer = ciphertext.to_vec();
516 super::crypto::provider().decrypt(PrivProtocol::Des3, key, &iv, &mut buffer)?;
517
518 Ok(Bytes::from(buffer))
519 }
520
521 fn encrypt_aes(
523 &self,
524 plaintext: &[u8],
525 engine_boots: u32,
526 engine_time: u32,
527 salt: u64,
528 key_len: usize,
529 ) -> PrivacyResult<(Bytes, Bytes)> {
530 let key = &self.key[..key_len];
532
533 let salt_bytes = salt.to_be_bytes();
535
536 let mut iv = [0u8; 16];
539 iv[..4].copy_from_slice(&engine_boots.to_be_bytes());
540 iv[4..8].copy_from_slice(&engine_time.to_be_bytes());
541 iv[8..].copy_from_slice(&salt_bytes);
542
543 let mut buffer = plaintext.to_vec();
544 super::crypto::provider().encrypt(self.protocol, key, &iv, &mut buffer)?;
545
546 Ok((Bytes::from(buffer), Bytes::copy_from_slice(&salt_bytes)))
547 }
548
549 fn decrypt_aes(
551 &self,
552 ciphertext: &[u8],
553 engine_boots: u32,
554 engine_time: u32,
555 priv_params: &[u8],
556 ) -> PrivacyResult<Bytes> {
557 let key_len = match self.protocol {
558 PrivProtocol::Aes128 => 16,
559 PrivProtocol::Aes192 => 24,
560 PrivProtocol::Aes256 => 32,
561 _ => unreachable!(),
562 };
563
564 let key = &self.key[..key_len];
566
567 let mut iv = [0u8; 16];
569 iv[..4].copy_from_slice(&engine_boots.to_be_bytes());
570 iv[4..8].copy_from_slice(&engine_time.to_be_bytes());
571 iv[8..].copy_from_slice(priv_params);
572
573 let mut buffer = ciphertext.to_vec();
574 super::crypto::provider().decrypt(self.protocol, key, &iv, &mut buffer)?;
575
576 Ok(Bytes::from(buffer))
577 }
578}
579
580impl std::fmt::Debug for PrivKey {
581 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
582 f.debug_struct("PrivKey")
583 .field("protocol", &self.protocol)
584 .field("key", &"[REDACTED]")
585 .field("salt_counter", &"[REDACTED]")
586 .finish()
587 }
588}
589
590impl Clone for PrivKey {
591 fn clone(&self) -> Self {
592 Self {
593 key: self.key.clone(),
594 protocol: self.protocol,
595 salt_counter: Self::init_salt().expect("OS random source unavailable"),
597 }
598 }
599}
600
601#[cfg(test)]
602mod tests {
603 use super::*;
604 use crate::format::hex::decode as decode_hex;
605
606 #[cfg(feature = "crypto-rustcrypto")]
607 #[test]
608 fn test_des_encrypt_decrypt_roundtrip() {
609 let key = vec![
611 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, ];
614 let priv_key = PrivKey::from_bytes(PrivProtocol::Des, key).unwrap();
615
616 let plaintext = b"Hello, SNMPv3 World!";
617 let engine_boots = 100u32;
618 let engine_time = 12345u32;
619
620 let (ciphertext, priv_params) = priv_key
621 .encrypt(plaintext, engine_boots, engine_time, None)
622 .expect("encryption failed");
623
624 assert_ne!(ciphertext.as_ref(), plaintext);
626 assert_eq!(priv_params.len(), 8);
628
629 let decrypted = priv_key
631 .decrypt(&ciphertext, engine_boots, engine_time, &priv_params)
632 .expect("decryption failed");
633
634 assert!(decrypted.len() >= plaintext.len());
636 assert_eq!(&decrypted[..plaintext.len()], plaintext);
637 }
638
639 #[cfg(feature = "crypto-rustcrypto")]
640 #[test]
641 fn test_des3_encrypt_decrypt_roundtrip() {
642 let key = vec![
644 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, ];
649 let priv_key = PrivKey::from_bytes(PrivProtocol::Des3, key).unwrap();
650
651 let plaintext = b"Hello, SNMPv3 World with 3DES!";
652 let engine_boots = 100u32;
653 let engine_time = 12345u32;
654
655 let (ciphertext, priv_params) = priv_key
656 .encrypt(plaintext, engine_boots, engine_time, None)
657 .expect("encryption failed");
658
659 assert_ne!(ciphertext.as_ref(), plaintext);
661 assert_eq!(priv_params.len(), 8);
663
664 let decrypted = priv_key
666 .decrypt(&ciphertext, engine_boots, engine_time, &priv_params)
667 .expect("decryption failed");
668
669 assert!(decrypted.len() >= plaintext.len());
671 assert_eq!(&decrypted[..plaintext.len()], plaintext);
672 }
673
674 #[test]
675 fn test_aes128_encrypt_decrypt_roundtrip() {
676 let key = vec![
678 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e,
679 0x0f, 0x10,
680 ];
681 let priv_key = PrivKey::from_bytes(PrivProtocol::Aes128, key).unwrap();
682
683 let plaintext = b"Hello, SNMPv3 AES World!";
684 let engine_boots = 200u32;
685 let engine_time = 54321u32;
686
687 let (ciphertext, priv_params) = priv_key
688 .encrypt(plaintext, engine_boots, engine_time, None)
689 .expect("encryption failed");
690
691 assert_ne!(ciphertext.as_ref(), plaintext);
693 assert_eq!(priv_params.len(), 8);
695
696 let decrypted = priv_key
698 .decrypt(&ciphertext, engine_boots, engine_time, &priv_params)
699 .expect("decryption failed");
700
701 assert_eq!(decrypted.len(), plaintext.len());
703 assert_eq!(decrypted.as_ref(), plaintext);
704 }
705
706 #[cfg(feature = "crypto-rustcrypto")]
707 #[test]
708 fn test_des_invalid_ciphertext_length() {
709 let key = vec![0u8; 16];
710 let priv_key = PrivKey::from_bytes(PrivProtocol::Des, key).unwrap();
711
712 let ciphertext = [0u8; 13];
714 let priv_params = [0u8; 8];
715
716 let result = priv_key.decrypt(&ciphertext, 0, 0, &priv_params);
717 assert!(result.is_err());
718 }
719
720 #[test]
721 fn test_invalid_priv_params_length() {
722 let key = vec![0u8; 16];
723 let priv_key = PrivKey::from_bytes(PrivProtocol::Aes128, key).unwrap();
724
725 let ciphertext = [0u8; 16];
727 let priv_params = [0u8; 4]; let result = priv_key.decrypt(&ciphertext, 0, 0, &priv_params);
730 assert!(result.is_err());
731 }
732
733 #[test]
734 fn test_from_bytes_rejects_undersized_key() {
735 let result = PrivKey::from_bytes(PrivProtocol::Des, vec![0u8; 4]);
738 assert!(matches!(result, Err(CryptoError::InvalidKeyLength)));
739 }
740
741 #[test]
742 fn test_from_bytes_accepts_exact_length_key() {
743 let priv_key = PrivKey::from_bytes(PrivProtocol::Des, vec![0u8; 16]);
744 assert!(priv_key.is_ok());
745
746 let priv_key = PrivKey::from_bytes(PrivProtocol::Aes128, vec![0u8; 16]);
747 assert!(priv_key.is_ok());
748 }
749
750 #[test]
751 fn test_from_bytes_accepts_oversized_key() {
752 let priv_key = PrivKey::from_bytes(PrivProtocol::Des, vec![0u8; 20]).unwrap();
756 let _ = priv_key.encrypt(b"data", 0, 0, None);
758 }
759
760 #[test]
761 fn test_from_bytes_key_len_boundary() {
762 let des_len = PrivProtocol::Des.key_len();
763 assert!(PrivKey::from_bytes(PrivProtocol::Des, vec![0u8; des_len - 1]).is_err());
764 assert!(PrivKey::from_bytes(PrivProtocol::Des, vec![0u8; des_len]).is_ok());
765
766 let aes256_len = PrivProtocol::Aes256.key_len();
767 assert!(PrivKey::from_bytes(PrivProtocol::Aes256, vec![0u8; aes256_len - 1]).is_err());
768 assert!(PrivKey::from_bytes(PrivProtocol::Aes256, vec![0u8; aes256_len]).is_ok());
769 }
770
771 #[test]
772 fn test_salt_counter() {
773 let counter = SaltCounter::new();
774 let s1 = counter.next();
775 let s2 = counter.next();
776 let s3 = counter.next();
777
778 assert_eq!(s2, s1.wrapping_add(1));
780 assert_eq!(s3, s2.wrapping_add(1));
781 }
782
783 #[test]
788 fn test_salt_counter_skips_zero() {
789 let counter = SaltCounter::from_value(u64::MAX - 1);
795
796 let s1 = counter.next();
797 assert_eq!(s1, u64::MAX);
798
799 let s2 = counter.next();
801 assert_ne!(s2, 0, "SaltCounter should never return zero");
802 assert_eq!(s2, 1, "SaltCounter should skip 0 and return 1");
803
804 let s3 = counter.next();
806 assert_eq!(s3, 2);
807 }
808
809 #[test]
814 fn test_priv_key_internal_salt_skips_zero() {
815 let key = vec![0u8; 16];
816 let priv_key = PrivKey::from_bytes(PrivProtocol::Aes128, key).unwrap();
817
818 priv_key.salt_counter.store(u64::MAX, Ordering::Relaxed);
820
821 let plaintext = b"test";
822
823 let (_, salt1) = priv_key.encrypt(plaintext, 0, 0, None).unwrap();
825 assert_eq!(
826 u64::from_be_bytes(salt1.as_ref().try_into().unwrap()),
827 u64::MAX
828 );
829
830 let (_, salt2) = priv_key.encrypt(plaintext, 0, 0, None).unwrap();
832 let salt2_value = u64::from_be_bytes(salt2.as_ref().try_into().unwrap());
833 assert_ne!(salt2_value, 0, "Salt should never be zero");
834 assert_eq!(salt2_value, 1, "Salt should skip 0 and be 1");
835
836 let (_, salt3) = priv_key.encrypt(plaintext, 0, 0, None).unwrap();
838 let salt3_value = u64::from_be_bytes(salt3.as_ref().try_into().unwrap());
839 assert_eq!(salt3_value, 2);
840 }
841
842 #[test]
843 fn test_multiple_encryptions_different_salt() {
844 let key = vec![0u8; 16];
845 let priv_key = PrivKey::from_bytes(PrivProtocol::Aes128, key).unwrap();
846
847 let plaintext = b"test data";
848
849 let (_, salt1) = priv_key.encrypt(plaintext, 0, 0, None).unwrap();
850 let (_, salt2) = priv_key.encrypt(plaintext, 0, 0, None).unwrap();
851
852 assert_ne!(salt1, salt2);
854 }
855
856 #[test]
857 fn test_from_password() {
858 let password = b"maplesyrup";
860 let engine_id = decode_hex("000000000000000000000002").unwrap();
861
862 let priv_key = PrivKey::from_password(
863 AuthProtocol::Sha1,
864 PrivProtocol::Aes128,
865 password,
866 &engine_id,
867 )
868 .unwrap();
869
870 let plaintext = b"test message";
872 let (ciphertext, priv_params) = priv_key.encrypt(plaintext, 100, 200, None).unwrap();
873 let decrypted = priv_key
874 .decrypt(&ciphertext, 100, 200, &priv_params)
875 .unwrap();
876
877 assert_eq!(decrypted.as_ref(), plaintext);
878 }
879
880 #[test]
881 fn test_aes192_encrypt_decrypt_roundtrip() {
882 let key = vec![
884 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e,
885 0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18,
886 ];
887 let priv_key = PrivKey::from_bytes(PrivProtocol::Aes192, key).unwrap();
888
889 let plaintext = b"Hello, SNMPv3 AES-192 World!";
890 let engine_boots = 300u32;
891 let engine_time = 67890u32;
892
893 let (ciphertext, priv_params) = priv_key
894 .encrypt(plaintext, engine_boots, engine_time, None)
895 .expect("AES-192 encryption failed");
896
897 assert_ne!(ciphertext.as_ref(), plaintext);
899 assert_eq!(priv_params.len(), 8);
901
902 let decrypted = priv_key
904 .decrypt(&ciphertext, engine_boots, engine_time, &priv_params)
905 .expect("AES-192 decryption failed");
906
907 assert_eq!(decrypted.len(), plaintext.len());
909 assert_eq!(decrypted.as_ref(), plaintext);
910 }
911
912 #[test]
913 fn test_aes256_encrypt_decrypt_roundtrip() {
914 let key = vec![
916 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e,
917 0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c,
918 0x1d, 0x1e, 0x1f, 0x20,
919 ];
920 let priv_key = PrivKey::from_bytes(PrivProtocol::Aes256, key).unwrap();
921
922 let plaintext = b"Hello, SNMPv3 AES-256 World!";
923 let engine_boots = 400u32;
924 let engine_time = 11111u32;
925
926 let (ciphertext, priv_params) = priv_key
927 .encrypt(plaintext, engine_boots, engine_time, None)
928 .expect("AES-256 encryption failed");
929
930 assert_ne!(ciphertext.as_ref(), plaintext);
932 assert_eq!(priv_params.len(), 8);
934
935 let decrypted = priv_key
937 .decrypt(&ciphertext, engine_boots, engine_time, &priv_params)
938 .expect("AES-256 decryption failed");
939
940 assert_eq!(decrypted.len(), plaintext.len());
942 assert_eq!(decrypted.as_ref(), plaintext);
943 }
944
945 #[test]
946 fn test_aes192_from_password() {
947 let password = b"longpassword123";
949 let engine_id = decode_hex("80001f8880e9b104617361000000").unwrap();
950
951 let priv_key = PrivKey::from_password(
952 AuthProtocol::Sha256, PrivProtocol::Aes192,
954 password,
955 &engine_id,
956 )
957 .unwrap();
958
959 let plaintext = b"test message for AES-192";
960 let (ciphertext, priv_params) = priv_key.encrypt(plaintext, 100, 200, None).unwrap();
961 let decrypted = priv_key
962 .decrypt(&ciphertext, 100, 200, &priv_params)
963 .unwrap();
964
965 assert_eq!(decrypted.as_ref(), plaintext);
966 }
967
968 #[test]
969 fn test_aes256_from_password() {
970 let password = b"anotherlongpassword456";
972 let engine_id = decode_hex("80001f8880e9b104617361000000").unwrap();
973
974 let priv_key = PrivKey::from_password(
975 AuthProtocol::Sha256, PrivProtocol::Aes256,
977 password,
978 &engine_id,
979 )
980 .unwrap();
981
982 let plaintext = b"test message for AES-256";
983 let (ciphertext, priv_params) = priv_key.encrypt(plaintext, 100, 200, None).unwrap();
984 let decrypted = priv_key
985 .decrypt(&ciphertext, 100, 200, &priv_params)
986 .unwrap();
987
988 assert_eq!(decrypted.as_ref(), plaintext);
989 }
990
991 #[cfg(feature = "crypto-rustcrypto")]
1001 #[test]
1002 fn test_des_wrong_key_produces_garbage() {
1003 let correct_key = vec![
1005 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16,
1006 0x17, 0x18,
1007 ];
1008 let wrong_key = vec![
1010 0xFF, 0xFE, 0xFD, 0xFC, 0xFB, 0xFA, 0xF9, 0xF8, 0xE7, 0xE6, 0xE5, 0xE4, 0xE3, 0xE2,
1011 0xE1, 0xE0,
1012 ];
1013
1014 let correct_priv_key = PrivKey::from_bytes(PrivProtocol::Des, correct_key).unwrap();
1015 let wrong_priv_key = PrivKey::from_bytes(PrivProtocol::Des, wrong_key).unwrap();
1016
1017 let plaintext = b"Secret SNMPv3 message data!";
1018 let engine_boots = 100u32;
1019 let engine_time = 12345u32;
1020
1021 let (ciphertext, priv_params) = correct_priv_key
1023 .encrypt(plaintext, engine_boots, engine_time, None)
1024 .expect("encryption failed");
1025
1026 let wrong_decrypted = wrong_priv_key
1028 .decrypt(&ciphertext, engine_boots, engine_time, &priv_params)
1029 .expect("decryption should succeed cryptographically");
1030
1031 assert_ne!(
1033 &wrong_decrypted[..plaintext.len()],
1034 plaintext,
1035 "wrong key should NOT produce the original plaintext"
1036 );
1037
1038 let correct_decrypted = correct_priv_key
1040 .decrypt(&ciphertext, engine_boots, engine_time, &priv_params)
1041 .expect("correct key decryption failed");
1042 assert_eq!(
1043 &correct_decrypted[..plaintext.len()],
1044 plaintext,
1045 "correct key should produce the original plaintext"
1046 );
1047 }
1048
1049 #[test]
1050 fn test_aes128_wrong_key_produces_garbage() {
1051 let correct_key = vec![
1052 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e,
1053 0x0f, 0x10,
1054 ];
1055 let wrong_key = vec![
1056 0xFF, 0xFE, 0xFD, 0xFC, 0xFB, 0xFA, 0xF9, 0xF8, 0xF7, 0xF6, 0xF5, 0xF4, 0xF3, 0xF2,
1057 0xF1, 0xF0,
1058 ];
1059
1060 let correct_priv_key = PrivKey::from_bytes(PrivProtocol::Aes128, correct_key).unwrap();
1061 let wrong_priv_key = PrivKey::from_bytes(PrivProtocol::Aes128, wrong_key).unwrap();
1062
1063 let plaintext = b"Secret AES-128 message data!";
1064 let engine_boots = 200u32;
1065 let engine_time = 54321u32;
1066
1067 let (ciphertext, priv_params) = correct_priv_key
1069 .encrypt(plaintext, engine_boots, engine_time, None)
1070 .expect("encryption failed");
1071
1072 let wrong_decrypted = wrong_priv_key
1074 .decrypt(&ciphertext, engine_boots, engine_time, &priv_params)
1075 .expect("decryption should succeed cryptographically");
1076
1077 assert_ne!(
1079 wrong_decrypted.as_ref(),
1080 plaintext,
1081 "wrong key should NOT produce the original plaintext"
1082 );
1083
1084 let correct_decrypted = correct_priv_key
1086 .decrypt(&ciphertext, engine_boots, engine_time, &priv_params)
1087 .expect("correct key decryption failed");
1088 assert_eq!(correct_decrypted.as_ref(), plaintext);
1089 }
1090
1091 #[test]
1092 fn test_aes192_wrong_key_produces_garbage() {
1093 let correct_key = vec![
1094 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e,
1095 0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18,
1096 ];
1097 let wrong_key = vec![
1098 0xFF, 0xFE, 0xFD, 0xFC, 0xFB, 0xFA, 0xF9, 0xF8, 0xF7, 0xF6, 0xF5, 0xF4, 0xF3, 0xF2,
1099 0xF1, 0xF0, 0xEF, 0xEE, 0xED, 0xEC, 0xEB, 0xEA, 0xE9, 0xE8,
1100 ];
1101
1102 let correct_priv_key = PrivKey::from_bytes(PrivProtocol::Aes192, correct_key).unwrap();
1103 let wrong_priv_key = PrivKey::from_bytes(PrivProtocol::Aes192, wrong_key).unwrap();
1104
1105 let plaintext = b"Secret AES-192 message data!";
1106 let engine_boots = 300u32;
1107 let engine_time = 67890u32;
1108
1109 let (ciphertext, priv_params) = correct_priv_key
1110 .encrypt(plaintext, engine_boots, engine_time, None)
1111 .expect("encryption failed");
1112
1113 let wrong_decrypted = wrong_priv_key
1114 .decrypt(&ciphertext, engine_boots, engine_time, &priv_params)
1115 .expect("decryption should succeed cryptographically");
1116
1117 assert_ne!(
1118 wrong_decrypted.as_ref(),
1119 plaintext,
1120 "wrong key should NOT produce the original plaintext"
1121 );
1122 }
1123
1124 #[test]
1125 fn test_aes256_wrong_key_produces_garbage() {
1126 let correct_key = vec![
1127 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e,
1128 0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c,
1129 0x1d, 0x1e, 0x1f, 0x20,
1130 ];
1131 let wrong_key = vec![
1132 0xFF, 0xFE, 0xFD, 0xFC, 0xFB, 0xFA, 0xF9, 0xF8, 0xF7, 0xF6, 0xF5, 0xF4, 0xF3, 0xF2,
1133 0xF1, 0xF0, 0xEF, 0xEE, 0xED, 0xEC, 0xEB, 0xEA, 0xE9, 0xE8, 0xE7, 0xE6, 0xE5, 0xE4,
1134 0xE3, 0xE2, 0xE1, 0xE0,
1135 ];
1136
1137 let correct_priv_key = PrivKey::from_bytes(PrivProtocol::Aes256, correct_key).unwrap();
1138 let wrong_priv_key = PrivKey::from_bytes(PrivProtocol::Aes256, wrong_key).unwrap();
1139
1140 let plaintext = b"Secret AES-256 message data!";
1141 let engine_boots = 400u32;
1142 let engine_time = 11111u32;
1143
1144 let (ciphertext, priv_params) = correct_priv_key
1145 .encrypt(plaintext, engine_boots, engine_time, None)
1146 .expect("encryption failed");
1147
1148 let wrong_decrypted = wrong_priv_key
1149 .decrypt(&ciphertext, engine_boots, engine_time, &priv_params)
1150 .expect("decryption should succeed cryptographically");
1151
1152 assert_ne!(
1153 wrong_decrypted.as_ref(),
1154 plaintext,
1155 "wrong key should NOT produce the original plaintext"
1156 );
1157 }
1158
1159 #[cfg(feature = "crypto-rustcrypto")]
1160 #[test]
1161 fn test_des_wrong_priv_params_produces_garbage() {
1162 let key = vec![
1165 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16,
1166 0x17, 0x18,
1167 ];
1168
1169 let priv_key = PrivKey::from_bytes(PrivProtocol::Des, key).unwrap();
1170
1171 let plaintext = b"DES test message";
1172 let engine_boots = 100u32;
1173 let engine_time = 12345u32;
1174
1175 let (ciphertext, correct_priv_params) = priv_key
1176 .encrypt(plaintext, engine_boots, engine_time, None)
1177 .expect("encryption failed");
1178
1179 let wrong_priv_params = [0xFF, 0xEE, 0xDD, 0xCC, 0xBB, 0xAA, 0x99, 0x88];
1181
1182 let wrong_decrypted = priv_key
1183 .decrypt(&ciphertext, engine_boots, engine_time, &wrong_priv_params)
1184 .expect("decryption should succeed cryptographically");
1185
1186 assert_ne!(
1188 &wrong_decrypted[..plaintext.len()],
1189 plaintext,
1190 "wrong priv_params should NOT produce the original plaintext"
1191 );
1192
1193 let correct_decrypted = priv_key
1195 .decrypt(&ciphertext, engine_boots, engine_time, &correct_priv_params)
1196 .expect("correct decryption failed");
1197 assert_eq!(&correct_decrypted[..plaintext.len()], plaintext);
1198 }
1199
1200 #[cfg(feature = "crypto-rustcrypto")]
1207 #[test]
1208 fn test_des_salt_and_iv_composition() {
1209 let key = vec![
1211 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF, 0x11, 0x22, ];
1214 let pre_iv = [0xAAu8, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF, 0x11, 0x22];
1215 let priv_key = PrivKey::from_bytes(PrivProtocol::Des, key).unwrap();
1216
1217 let salt_counter = SaltCounter::from_value(0x1000);
1220 let expected_salt_value: u32 = 0x1001;
1221
1222 let engine_boots: u32 = 0x1234_5678;
1223 let engine_time: u32 = 999;
1224 let plaintext = b"RFC 3414 8.1.1.1 salt/IV composition test";
1225
1226 let (ciphertext, priv_params) = priv_key
1227 .encrypt(plaintext, engine_boots, engine_time, Some(&salt_counter))
1228 .expect("encryption failed");
1229
1230 assert_eq!(priv_params.len(), 8);
1232 assert_eq!(&priv_params[..4], &engine_boots.to_be_bytes());
1233 assert_eq!(&priv_params[4..8], &expected_salt_value.to_be_bytes());
1234
1235 let mut expected_iv = [0u8; 8];
1238 for i in 0..8 {
1239 expected_iv[i] = pre_iv[i] ^ priv_params[i];
1240 }
1241 assert_ne!(
1242 expected_iv, pre_iv,
1243 "salt XOR must change the IV relative to the raw pre-IV"
1244 );
1245
1246 let decrypted = priv_key
1249 .decrypt(&ciphertext, engine_boots, engine_time, &priv_params)
1250 .expect("decryption failed");
1251 assert_eq!(&decrypted[..plaintext.len()], plaintext);
1252 }
1253
1254 #[test]
1259 fn test_salt_counter_no_duplicates_concurrent() {
1260 use std::collections::HashSet;
1261 use std::sync::{Arc, Mutex};
1262 use std::thread;
1263
1264 let counter = Arc::new(SaltCounter::new());
1265 let results = Arc::new(Mutex::new(HashSet::new()));
1266 let iterations = 10_000usize;
1267 let threads = 8usize;
1268
1269 let handles: Vec<_> = (0..threads)
1270 .map(|_| {
1271 let counter = Arc::clone(&counter);
1272 let results = Arc::clone(&results);
1273 thread::spawn(move || {
1274 for _ in 0..iterations {
1275 let salt = counter.next();
1276 assert_ne!(salt, 0, "SaltCounter must never return zero");
1277 let mut set = results.lock().unwrap();
1278 assert!(set.insert(salt), "SaltCounter emitted duplicate: {salt}");
1279 }
1280 })
1281 })
1282 .collect();
1283
1284 for h in handles {
1285 h.join().expect("thread panicked");
1286 }
1287 }
1288
1289 #[test]
1294 fn test_priv_key_clone_independent_salts() {
1295 let key = vec![0u8; 16];
1296 let original = PrivKey::from_bytes(PrivProtocol::Aes128, key).unwrap();
1297 let cloned = original.clone();
1298
1299 let plaintext = b"test";
1300
1301 let (_, salt_orig) = original.encrypt(plaintext, 0, 0, None).unwrap();
1303 let (_, salt_clone) = cloned.encrypt(plaintext, 0, 0, None).unwrap();
1304
1305 assert_ne!(
1306 salt_orig, salt_clone,
1307 "cloned PrivKey must start with an independent salt counter"
1308 );
1309 }
1310
1311 #[test]
1312 fn test_aes_wrong_engine_time_produces_garbage() {
1313 let key = vec![
1316 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e,
1317 0x0f, 0x10,
1318 ];
1319
1320 let priv_key = PrivKey::from_bytes(PrivProtocol::Aes128, key).unwrap();
1321
1322 let plaintext = b"AES test message";
1323 let engine_boots = 200u32;
1324 let engine_time = 54321u32;
1325
1326 let (ciphertext, priv_params) = priv_key
1327 .encrypt(plaintext, engine_boots, engine_time, None)
1328 .expect("encryption failed");
1329
1330 let wrong_decrypted = priv_key
1332 .decrypt(&ciphertext, engine_boots, engine_time + 1, &priv_params)
1333 .expect("decryption should succeed cryptographically");
1334
1335 assert_ne!(
1336 wrong_decrypted.as_ref(),
1337 plaintext,
1338 "wrong engine_time should NOT produce the original plaintext"
1339 );
1340
1341 let wrong_decrypted2 = priv_key
1343 .decrypt(&ciphertext, engine_boots + 1, engine_time, &priv_params)
1344 .expect("decryption should succeed cryptographically");
1345
1346 assert_ne!(
1347 wrong_decrypted2.as_ref(),
1348 plaintext,
1349 "wrong engine_boots should NOT produce the original plaintext"
1350 );
1351 }
1352}