use std::sync::atomic::{AtomicU64, Ordering};
use bytes::Bytes;
use zeroize::{Zeroize, ZeroizeOnDrop};
use super::crypto::{CryptoError, CryptoProvider};
use super::{AuthProtocol, PrivProtocol};
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum PrivacyError {
InvalidPrivParamsLength { expected: usize, actual: usize },
InvalidCiphertextLength { length: usize, block_size: usize },
Crypto(CryptoError),
}
impl From<CryptoError> for PrivacyError {
fn from(e: CryptoError) -> Self {
Self::Crypto(e)
}
}
impl std::fmt::Display for PrivacyError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::InvalidPrivParamsLength { expected, actual } => {
write!(
f,
"invalid privParameters length: expected {}, got {}",
expected, actual
)
}
Self::InvalidCiphertextLength { length, block_size } => {
write!(
f,
"ciphertext length {} not multiple of block size {}",
length, block_size
)
}
Self::Crypto(e) => write!(f, "{}", e),
}
}
}
impl std::error::Error for PrivacyError {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
match self {
Self::Crypto(e) => Some(e),
_ => None,
}
}
}
pub type PrivacyResult<T> = std::result::Result<T, PrivacyError>;
fn random_nonzero_u64() -> super::crypto::CryptoResult<u64> {
let mut buf = [0u8; 8];
loop {
getrandom::fill(&mut buf).map_err(|_| super::crypto::CryptoError::RandomSource)?;
let val = u64::from_ne_bytes(buf);
if val != 0 {
return Ok(val);
}
}
}
#[derive(Zeroize, ZeroizeOnDrop)]
pub struct PrivKey {
key: Vec<u8>,
#[zeroize(skip)]
protocol: PrivProtocol,
#[zeroize(skip)]
salt_counter: AtomicU64,
}
pub struct SaltCounter(AtomicU64);
impl SaltCounter {
pub fn new() -> Self {
Self(AtomicU64::new(
random_nonzero_u64().expect("OS random source unavailable"),
))
}
pub fn from_value(value: u64) -> Self {
Self(AtomicU64::new(value))
}
pub fn next(&self) -> u64 {
let old = self.0.fetch_add(1, Ordering::SeqCst);
let val = old.wrapping_add(1);
if val != 0 {
return val;
}
let _ = self
.0
.compare_exchange(0, 1, Ordering::SeqCst, Ordering::SeqCst);
1
}
}
impl Default for SaltCounter {
fn default() -> Self {
Self::new()
}
}
impl PrivKey {
pub fn from_password(
auth_protocol: AuthProtocol,
priv_protocol: PrivProtocol,
password: &[u8],
engine_id: &[u8],
) -> super::crypto::CryptoResult<Self> {
use super::MasterKey;
let master = MasterKey::from_password(auth_protocol, password)?;
Self::from_master_key(&master, priv_protocol, engine_id)
}
pub fn from_master_key(
master: &super::MasterKey,
priv_protocol: PrivProtocol,
engine_id: &[u8],
) -> super::crypto::CryptoResult<Self> {
use super::{
KeyExtension,
auth::{extend_key, extend_key_reeder},
};
let auth_protocol = master.protocol();
let key_extension = priv_protocol.key_extension_for(auth_protocol);
let localized = master.localize(engine_id)?;
let key_bytes = localized.as_bytes();
let key = match key_extension {
KeyExtension::None => key_bytes.to_vec(),
KeyExtension::Blumenthal => {
extend_key(auth_protocol, key_bytes, priv_protocol.key_len())?
}
KeyExtension::Reeder => {
extend_key_reeder(auth_protocol, key_bytes, engine_id, priv_protocol.key_len())?
}
};
Ok(Self {
key,
protocol: priv_protocol,
salt_counter: Self::init_salt()?,
})
}
pub fn from_bytes(
protocol: PrivProtocol,
key: impl Into<Vec<u8>>,
) -> super::crypto::CryptoResult<Self> {
Ok(Self {
key: key.into(),
protocol,
salt_counter: Self::init_salt()?,
})
}
fn init_salt() -> super::crypto::CryptoResult<AtomicU64> {
Ok(AtomicU64::new(random_nonzero_u64()?))
}
pub fn protocol(&self) -> PrivProtocol {
self.protocol
}
pub fn encryption_key(&self) -> &[u8] {
match self.protocol {
PrivProtocol::Des => &self.key[..8],
PrivProtocol::Des3 => &self.key[..24],
PrivProtocol::Aes128 => &self.key[..16],
PrivProtocol::Aes192 => &self.key[..24],
PrivProtocol::Aes256 => &self.key[..32],
}
}
pub fn encrypt(
&self,
plaintext: &[u8],
engine_boots: u32,
engine_time: u32,
salt_counter: Option<&SaltCounter>,
) -> PrivacyResult<(Bytes, Bytes)> {
let salt = salt_counter.map(|c| c.next()).unwrap_or_else(|| {
let val = self.salt_counter.fetch_add(1, Ordering::Relaxed);
if val != 0 {
return val;
}
self.salt_counter.fetch_add(1, Ordering::Relaxed)
});
match self.protocol {
PrivProtocol::Des => self.encrypt_des(plaintext, engine_boots, salt),
PrivProtocol::Des3 => self.encrypt_des3(plaintext, engine_boots, salt),
PrivProtocol::Aes128 => {
self.encrypt_aes(plaintext, engine_boots, engine_time, salt, 16)
}
PrivProtocol::Aes192 => {
self.encrypt_aes(plaintext, engine_boots, engine_time, salt, 24)
}
PrivProtocol::Aes256 => {
self.encrypt_aes(plaintext, engine_boots, engine_time, salt, 32)
}
}
}
pub fn decrypt(
&self,
ciphertext: &[u8],
engine_boots: u32,
engine_time: u32,
priv_params: &[u8],
) -> PrivacyResult<Bytes> {
if priv_params.len() != 8 {
tracing::debug!(target: "async_snmp::crypto", { expected = 8, actual = priv_params.len() }, "invalid privParameters length");
return Err(PrivacyError::InvalidPrivParamsLength {
expected: 8,
actual: priv_params.len(),
});
}
match self.protocol {
PrivProtocol::Des => self.decrypt_des(ciphertext, priv_params),
PrivProtocol::Des3 => self.decrypt_des3(ciphertext, priv_params),
PrivProtocol::Aes128 | PrivProtocol::Aes192 | PrivProtocol::Aes256 => {
self.decrypt_aes(ciphertext, engine_boots, engine_time, priv_params)
}
}
}
fn encrypt_des(
&self,
plaintext: &[u8],
engine_boots: u32,
salt_int: u64,
) -> PrivacyResult<(Bytes, Bytes)> {
let key = &self.key[..8];
let pre_iv = &self.key[8..16];
let mut salt = [0u8; 8];
salt[..4].copy_from_slice(&engine_boots.to_be_bytes());
salt[4..].copy_from_slice(&(salt_int as u32).to_be_bytes());
let mut iv = [0u8; 8];
for i in 0..8 {
iv[i] = pre_iv[i] ^ salt[i];
}
let padded_len = plaintext.len().next_multiple_of(8);
let mut buffer = vec![0u8; padded_len];
buffer[..plaintext.len()].copy_from_slice(plaintext);
super::crypto::provider().encrypt(PrivProtocol::Des, key, &iv, &mut buffer)?;
Ok((Bytes::from(buffer), Bytes::copy_from_slice(&salt)))
}
fn decrypt_des(&self, ciphertext: &[u8], priv_params: &[u8]) -> PrivacyResult<Bytes> {
if !ciphertext.len().is_multiple_of(8) {
tracing::debug!(target: "async_snmp::crypto", { length = ciphertext.len(), block_size = 8 }, "DES decryption failed: invalid ciphertext length");
return Err(PrivacyError::InvalidCiphertextLength {
length: ciphertext.len(),
block_size: 8,
});
}
let key = &self.key[..8];
let pre_iv = &self.key[8..16];
let salt = priv_params;
let mut iv = [0u8; 8];
for i in 0..8 {
iv[i] = pre_iv[i] ^ salt[i];
}
let mut buffer = ciphertext.to_vec();
super::crypto::provider().decrypt(PrivProtocol::Des, key, &iv, &mut buffer)?;
Ok(Bytes::from(buffer))
}
fn encrypt_des3(
&self,
plaintext: &[u8],
engine_boots: u32,
salt_int: u64,
) -> PrivacyResult<(Bytes, Bytes)> {
let key = &self.key[..24];
let pre_iv = &self.key[24..32];
let mut salt = [0u8; 8];
salt[..4].copy_from_slice(&engine_boots.to_be_bytes());
salt[4..].copy_from_slice(&(salt_int as u32).to_be_bytes());
let mut iv = [0u8; 8];
for i in 0..8 {
iv[i] = pre_iv[i] ^ salt[i];
}
let padded_len = plaintext.len().next_multiple_of(8);
let mut buffer = vec![0u8; padded_len];
buffer[..plaintext.len()].copy_from_slice(plaintext);
super::crypto::provider().encrypt(PrivProtocol::Des3, key, &iv, &mut buffer)?;
Ok((Bytes::from(buffer), Bytes::copy_from_slice(&salt)))
}
fn decrypt_des3(&self, ciphertext: &[u8], priv_params: &[u8]) -> PrivacyResult<Bytes> {
if !ciphertext.len().is_multiple_of(8) {
tracing::debug!(target: "async_snmp::crypto", { length = ciphertext.len(), block_size = 8 }, "3DES decryption failed: invalid ciphertext length");
return Err(PrivacyError::InvalidCiphertextLength {
length: ciphertext.len(),
block_size: 8,
});
}
let key = &self.key[..24];
let pre_iv = &self.key[24..32];
let salt = priv_params;
let mut iv = [0u8; 8];
for i in 0..8 {
iv[i] = pre_iv[i] ^ salt[i];
}
let mut buffer = ciphertext.to_vec();
super::crypto::provider().decrypt(PrivProtocol::Des3, key, &iv, &mut buffer)?;
Ok(Bytes::from(buffer))
}
fn encrypt_aes(
&self,
plaintext: &[u8],
engine_boots: u32,
engine_time: u32,
salt: u64,
key_len: usize,
) -> PrivacyResult<(Bytes, Bytes)> {
let key = &self.key[..key_len];
let salt_bytes = salt.to_be_bytes();
let mut iv = [0u8; 16];
iv[..4].copy_from_slice(&engine_boots.to_be_bytes());
iv[4..8].copy_from_slice(&engine_time.to_be_bytes());
iv[8..].copy_from_slice(&salt_bytes);
let mut buffer = plaintext.to_vec();
super::crypto::provider().encrypt(self.protocol, key, &iv, &mut buffer)?;
Ok((Bytes::from(buffer), Bytes::copy_from_slice(&salt_bytes)))
}
fn decrypt_aes(
&self,
ciphertext: &[u8],
engine_boots: u32,
engine_time: u32,
priv_params: &[u8],
) -> PrivacyResult<Bytes> {
let key_len = match self.protocol {
PrivProtocol::Aes128 => 16,
PrivProtocol::Aes192 => 24,
PrivProtocol::Aes256 => 32,
_ => unreachable!(),
};
let key = &self.key[..key_len];
let mut iv = [0u8; 16];
iv[..4].copy_from_slice(&engine_boots.to_be_bytes());
iv[4..8].copy_from_slice(&engine_time.to_be_bytes());
iv[8..].copy_from_slice(priv_params);
let mut buffer = ciphertext.to_vec();
super::crypto::provider().decrypt(self.protocol, key, &iv, &mut buffer)?;
Ok(Bytes::from(buffer))
}
}
impl std::fmt::Debug for PrivKey {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("PrivKey")
.field("protocol", &self.protocol)
.field("key", &"[REDACTED]")
.finish()
}
}
impl Clone for PrivKey {
fn clone(&self) -> Self {
Self {
key: self.key.clone(),
protocol: self.protocol,
salt_counter: Self::init_salt().expect("OS random source unavailable"),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::format::hex::decode as decode_hex;
#[cfg(feature = "crypto-rustcrypto")]
#[test]
fn test_des_encrypt_decrypt_roundtrip() {
let key = vec![
0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, ];
let priv_key = PrivKey::from_bytes(PrivProtocol::Des, key).unwrap();
let plaintext = b"Hello, SNMPv3 World!";
let engine_boots = 100u32;
let engine_time = 12345u32;
let (ciphertext, priv_params) = priv_key
.encrypt(plaintext, engine_boots, engine_time, None)
.expect("encryption failed");
assert_ne!(ciphertext.as_ref(), plaintext);
assert_eq!(priv_params.len(), 8);
let decrypted = priv_key
.decrypt(&ciphertext, engine_boots, engine_time, &priv_params)
.expect("decryption failed");
assert!(decrypted.len() >= plaintext.len());
assert_eq!(&decrypted[..plaintext.len()], plaintext);
}
#[cfg(feature = "crypto-rustcrypto")]
#[test]
fn test_des3_encrypt_decrypt_roundtrip() {
let key = vec![
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, ];
let priv_key = PrivKey::from_bytes(PrivProtocol::Des3, key).unwrap();
let plaintext = b"Hello, SNMPv3 World with 3DES!";
let engine_boots = 100u32;
let engine_time = 12345u32;
let (ciphertext, priv_params) = priv_key
.encrypt(plaintext, engine_boots, engine_time, None)
.expect("encryption failed");
assert_ne!(ciphertext.as_ref(), plaintext);
assert_eq!(priv_params.len(), 8);
let decrypted = priv_key
.decrypt(&ciphertext, engine_boots, engine_time, &priv_params)
.expect("decryption failed");
assert!(decrypted.len() >= plaintext.len());
assert_eq!(&decrypted[..plaintext.len()], plaintext);
}
#[test]
fn test_aes128_encrypt_decrypt_roundtrip() {
let key = vec![
0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e,
0x0f, 0x10,
];
let priv_key = PrivKey::from_bytes(PrivProtocol::Aes128, key).unwrap();
let plaintext = b"Hello, SNMPv3 AES World!";
let engine_boots = 200u32;
let engine_time = 54321u32;
let (ciphertext, priv_params) = priv_key
.encrypt(plaintext, engine_boots, engine_time, None)
.expect("encryption failed");
assert_ne!(ciphertext.as_ref(), plaintext);
assert_eq!(priv_params.len(), 8);
let decrypted = priv_key
.decrypt(&ciphertext, engine_boots, engine_time, &priv_params)
.expect("decryption failed");
assert_eq!(decrypted.len(), plaintext.len());
assert_eq!(decrypted.as_ref(), plaintext);
}
#[cfg(feature = "crypto-rustcrypto")]
#[test]
fn test_des_invalid_ciphertext_length() {
let key = vec![0u8; 16];
let priv_key = PrivKey::from_bytes(PrivProtocol::Des, key).unwrap();
let ciphertext = [0u8; 13];
let priv_params = [0u8; 8];
let result = priv_key.decrypt(&ciphertext, 0, 0, &priv_params);
assert!(result.is_err());
}
#[test]
fn test_invalid_priv_params_length() {
let key = vec![0u8; 16];
let priv_key = PrivKey::from_bytes(PrivProtocol::Aes128, key).unwrap();
let ciphertext = [0u8; 16];
let priv_params = [0u8; 4];
let result = priv_key.decrypt(&ciphertext, 0, 0, &priv_params);
assert!(result.is_err());
}
#[test]
fn test_salt_counter() {
let counter = SaltCounter::new();
let s1 = counter.next();
let s2 = counter.next();
let s3 = counter.next();
assert_eq!(s2, s1.wrapping_add(1));
assert_eq!(s3, s2.wrapping_add(1));
}
#[test]
fn test_salt_counter_skips_zero() {
let counter = SaltCounter::from_value(u64::MAX - 1);
let s1 = counter.next();
assert_eq!(s1, u64::MAX);
let s2 = counter.next();
assert_ne!(s2, 0, "SaltCounter should never return zero");
assert_eq!(s2, 1, "SaltCounter should skip 0 and return 1");
let s3 = counter.next();
assert_eq!(s3, 2);
}
#[test]
fn test_priv_key_internal_salt_skips_zero() {
let key = vec![0u8; 16];
let priv_key = PrivKey::from_bytes(PrivProtocol::Aes128, key).unwrap();
priv_key.salt_counter.store(u64::MAX, Ordering::Relaxed);
let plaintext = b"test";
let (_, salt1) = priv_key.encrypt(plaintext, 0, 0, None).unwrap();
assert_eq!(
u64::from_be_bytes(salt1.as_ref().try_into().unwrap()),
u64::MAX
);
let (_, salt2) = priv_key.encrypt(plaintext, 0, 0, None).unwrap();
let salt2_value = u64::from_be_bytes(salt2.as_ref().try_into().unwrap());
assert_ne!(salt2_value, 0, "Salt should never be zero");
assert_eq!(salt2_value, 1, "Salt should skip 0 and be 1");
let (_, salt3) = priv_key.encrypt(plaintext, 0, 0, None).unwrap();
let salt3_value = u64::from_be_bytes(salt3.as_ref().try_into().unwrap());
assert_eq!(salt3_value, 2);
}
#[test]
fn test_multiple_encryptions_different_salt() {
let key = vec![0u8; 16];
let priv_key = PrivKey::from_bytes(PrivProtocol::Aes128, key).unwrap();
let plaintext = b"test data";
let (_, salt1) = priv_key.encrypt(plaintext, 0, 0, None).unwrap();
let (_, salt2) = priv_key.encrypt(plaintext, 0, 0, None).unwrap();
assert_ne!(salt1, salt2);
}
#[test]
fn test_from_password() {
let password = b"maplesyrup";
let engine_id = decode_hex("000000000000000000000002").unwrap();
let priv_key = PrivKey::from_password(
AuthProtocol::Sha1,
PrivProtocol::Aes128,
password,
&engine_id,
)
.unwrap();
let plaintext = b"test message";
let (ciphertext, priv_params) = priv_key.encrypt(plaintext, 100, 200, None).unwrap();
let decrypted = priv_key
.decrypt(&ciphertext, 100, 200, &priv_params)
.unwrap();
assert_eq!(decrypted.as_ref(), plaintext);
}
#[test]
fn test_aes192_encrypt_decrypt_roundtrip() {
let key = vec![
0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e,
0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18,
];
let priv_key = PrivKey::from_bytes(PrivProtocol::Aes192, key).unwrap();
let plaintext = b"Hello, SNMPv3 AES-192 World!";
let engine_boots = 300u32;
let engine_time = 67890u32;
let (ciphertext, priv_params) = priv_key
.encrypt(plaintext, engine_boots, engine_time, None)
.expect("AES-192 encryption failed");
assert_ne!(ciphertext.as_ref(), plaintext);
assert_eq!(priv_params.len(), 8);
let decrypted = priv_key
.decrypt(&ciphertext, engine_boots, engine_time, &priv_params)
.expect("AES-192 decryption failed");
assert_eq!(decrypted.len(), plaintext.len());
assert_eq!(decrypted.as_ref(), plaintext);
}
#[test]
fn test_aes256_encrypt_decrypt_roundtrip() {
let key = vec![
0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e,
0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c,
0x1d, 0x1e, 0x1f, 0x20,
];
let priv_key = PrivKey::from_bytes(PrivProtocol::Aes256, key).unwrap();
let plaintext = b"Hello, SNMPv3 AES-256 World!";
let engine_boots = 400u32;
let engine_time = 11111u32;
let (ciphertext, priv_params) = priv_key
.encrypt(plaintext, engine_boots, engine_time, None)
.expect("AES-256 encryption failed");
assert_ne!(ciphertext.as_ref(), plaintext);
assert_eq!(priv_params.len(), 8);
let decrypted = priv_key
.decrypt(&ciphertext, engine_boots, engine_time, &priv_params)
.expect("AES-256 decryption failed");
assert_eq!(decrypted.len(), plaintext.len());
assert_eq!(decrypted.as_ref(), plaintext);
}
#[test]
fn test_aes192_from_password() {
let password = b"longpassword123";
let engine_id = decode_hex("80001f8880e9b104617361000000").unwrap();
let priv_key = PrivKey::from_password(
AuthProtocol::Sha256, PrivProtocol::Aes192,
password,
&engine_id,
)
.unwrap();
let plaintext = b"test message for AES-192";
let (ciphertext, priv_params) = priv_key.encrypt(plaintext, 100, 200, None).unwrap();
let decrypted = priv_key
.decrypt(&ciphertext, 100, 200, &priv_params)
.unwrap();
assert_eq!(decrypted.as_ref(), plaintext);
}
#[test]
fn test_aes256_from_password() {
let password = b"anotherlongpassword456";
let engine_id = decode_hex("80001f8880e9b104617361000000").unwrap();
let priv_key = PrivKey::from_password(
AuthProtocol::Sha256, PrivProtocol::Aes256,
password,
&engine_id,
)
.unwrap();
let plaintext = b"test message for AES-256";
let (ciphertext, priv_params) = priv_key.encrypt(plaintext, 100, 200, None).unwrap();
let decrypted = priv_key
.decrypt(&ciphertext, 100, 200, &priv_params)
.unwrap();
assert_eq!(decrypted.as_ref(), plaintext);
}
#[cfg(feature = "crypto-rustcrypto")]
#[test]
fn test_des_wrong_key_produces_garbage() {
let correct_key = vec![
0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16,
0x17, 0x18,
];
let wrong_key = vec![
0xFF, 0xFE, 0xFD, 0xFC, 0xFB, 0xFA, 0xF9, 0xF8, 0xE7, 0xE6, 0xE5, 0xE4, 0xE3, 0xE2,
0xE1, 0xE0,
];
let correct_priv_key = PrivKey::from_bytes(PrivProtocol::Des, correct_key).unwrap();
let wrong_priv_key = PrivKey::from_bytes(PrivProtocol::Des, wrong_key).unwrap();
let plaintext = b"Secret SNMPv3 message data!";
let engine_boots = 100u32;
let engine_time = 12345u32;
let (ciphertext, priv_params) = correct_priv_key
.encrypt(plaintext, engine_boots, engine_time, None)
.expect("encryption failed");
let wrong_decrypted = wrong_priv_key
.decrypt(&ciphertext, engine_boots, engine_time, &priv_params)
.expect("decryption should succeed cryptographically");
assert_ne!(
&wrong_decrypted[..plaintext.len()],
plaintext,
"wrong key should NOT produce the original plaintext"
);
let correct_decrypted = correct_priv_key
.decrypt(&ciphertext, engine_boots, engine_time, &priv_params)
.expect("correct key decryption failed");
assert_eq!(
&correct_decrypted[..plaintext.len()],
plaintext,
"correct key should produce the original plaintext"
);
}
#[test]
fn test_aes128_wrong_key_produces_garbage() {
let correct_key = vec![
0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e,
0x0f, 0x10,
];
let wrong_key = vec![
0xFF, 0xFE, 0xFD, 0xFC, 0xFB, 0xFA, 0xF9, 0xF8, 0xF7, 0xF6, 0xF5, 0xF4, 0xF3, 0xF2,
0xF1, 0xF0,
];
let correct_priv_key = PrivKey::from_bytes(PrivProtocol::Aes128, correct_key).unwrap();
let wrong_priv_key = PrivKey::from_bytes(PrivProtocol::Aes128, wrong_key).unwrap();
let plaintext = b"Secret AES-128 message data!";
let engine_boots = 200u32;
let engine_time = 54321u32;
let (ciphertext, priv_params) = correct_priv_key
.encrypt(plaintext, engine_boots, engine_time, None)
.expect("encryption failed");
let wrong_decrypted = wrong_priv_key
.decrypt(&ciphertext, engine_boots, engine_time, &priv_params)
.expect("decryption should succeed cryptographically");
assert_ne!(
wrong_decrypted.as_ref(),
plaintext,
"wrong key should NOT produce the original plaintext"
);
let correct_decrypted = correct_priv_key
.decrypt(&ciphertext, engine_boots, engine_time, &priv_params)
.expect("correct key decryption failed");
assert_eq!(correct_decrypted.as_ref(), plaintext);
}
#[test]
fn test_aes192_wrong_key_produces_garbage() {
let correct_key = vec![
0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e,
0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18,
];
let wrong_key = vec![
0xFF, 0xFE, 0xFD, 0xFC, 0xFB, 0xFA, 0xF9, 0xF8, 0xF7, 0xF6, 0xF5, 0xF4, 0xF3, 0xF2,
0xF1, 0xF0, 0xEF, 0xEE, 0xED, 0xEC, 0xEB, 0xEA, 0xE9, 0xE8,
];
let correct_priv_key = PrivKey::from_bytes(PrivProtocol::Aes192, correct_key).unwrap();
let wrong_priv_key = PrivKey::from_bytes(PrivProtocol::Aes192, wrong_key).unwrap();
let plaintext = b"Secret AES-192 message data!";
let engine_boots = 300u32;
let engine_time = 67890u32;
let (ciphertext, priv_params) = correct_priv_key
.encrypt(plaintext, engine_boots, engine_time, None)
.expect("encryption failed");
let wrong_decrypted = wrong_priv_key
.decrypt(&ciphertext, engine_boots, engine_time, &priv_params)
.expect("decryption should succeed cryptographically");
assert_ne!(
wrong_decrypted.as_ref(),
plaintext,
"wrong key should NOT produce the original plaintext"
);
}
#[test]
fn test_aes256_wrong_key_produces_garbage() {
let correct_key = vec![
0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e,
0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c,
0x1d, 0x1e, 0x1f, 0x20,
];
let wrong_key = vec![
0xFF, 0xFE, 0xFD, 0xFC, 0xFB, 0xFA, 0xF9, 0xF8, 0xF7, 0xF6, 0xF5, 0xF4, 0xF3, 0xF2,
0xF1, 0xF0, 0xEF, 0xEE, 0xED, 0xEC, 0xEB, 0xEA, 0xE9, 0xE8, 0xE7, 0xE6, 0xE5, 0xE4,
0xE3, 0xE2, 0xE1, 0xE0,
];
let correct_priv_key = PrivKey::from_bytes(PrivProtocol::Aes256, correct_key).unwrap();
let wrong_priv_key = PrivKey::from_bytes(PrivProtocol::Aes256, wrong_key).unwrap();
let plaintext = b"Secret AES-256 message data!";
let engine_boots = 400u32;
let engine_time = 11111u32;
let (ciphertext, priv_params) = correct_priv_key
.encrypt(plaintext, engine_boots, engine_time, None)
.expect("encryption failed");
let wrong_decrypted = wrong_priv_key
.decrypt(&ciphertext, engine_boots, engine_time, &priv_params)
.expect("decryption should succeed cryptographically");
assert_ne!(
wrong_decrypted.as_ref(),
plaintext,
"wrong key should NOT produce the original plaintext"
);
}
#[cfg(feature = "crypto-rustcrypto")]
#[test]
fn test_des_wrong_priv_params_produces_garbage() {
let key = vec![
0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16,
0x17, 0x18,
];
let priv_key = PrivKey::from_bytes(PrivProtocol::Des, key).unwrap();
let plaintext = b"DES test message";
let engine_boots = 100u32;
let engine_time = 12345u32;
let (ciphertext, correct_priv_params) = priv_key
.encrypt(plaintext, engine_boots, engine_time, None)
.expect("encryption failed");
let wrong_priv_params = [0xFF, 0xEE, 0xDD, 0xCC, 0xBB, 0xAA, 0x99, 0x88];
let wrong_decrypted = priv_key
.decrypt(&ciphertext, engine_boots, engine_time, &wrong_priv_params)
.expect("decryption should succeed cryptographically");
assert_ne!(
&wrong_decrypted[..plaintext.len()],
plaintext,
"wrong priv_params should NOT produce the original plaintext"
);
let correct_decrypted = priv_key
.decrypt(&ciphertext, engine_boots, engine_time, &correct_priv_params)
.expect("correct decryption failed");
assert_eq!(&correct_decrypted[..plaintext.len()], plaintext);
}
#[test]
fn test_salt_counter_no_duplicates_concurrent() {
use std::collections::HashSet;
use std::sync::{Arc, Mutex};
use std::thread;
let counter = Arc::new(SaltCounter::new());
let results = Arc::new(Mutex::new(HashSet::new()));
let iterations = 10_000usize;
let threads = 8usize;
let handles: Vec<_> = (0..threads)
.map(|_| {
let counter = Arc::clone(&counter);
let results = Arc::clone(&results);
thread::spawn(move || {
for _ in 0..iterations {
let salt = counter.next();
assert_ne!(salt, 0, "SaltCounter must never return zero");
let mut set = results.lock().unwrap();
assert!(set.insert(salt), "SaltCounter emitted duplicate: {salt}");
}
})
})
.collect();
for h in handles {
h.join().expect("thread panicked");
}
}
#[test]
fn test_priv_key_clone_independent_salts() {
let key = vec![0u8; 16];
let original = PrivKey::from_bytes(PrivProtocol::Aes128, key).unwrap();
let cloned = original.clone();
let plaintext = b"test";
let (_, salt_orig) = original.encrypt(plaintext, 0, 0, None).unwrap();
let (_, salt_clone) = cloned.encrypt(plaintext, 0, 0, None).unwrap();
assert_ne!(
salt_orig, salt_clone,
"cloned PrivKey must start with an independent salt counter"
);
}
#[test]
fn test_aes_wrong_engine_time_produces_garbage() {
let key = vec![
0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e,
0x0f, 0x10,
];
let priv_key = PrivKey::from_bytes(PrivProtocol::Aes128, key).unwrap();
let plaintext = b"AES test message";
let engine_boots = 200u32;
let engine_time = 54321u32;
let (ciphertext, priv_params) = priv_key
.encrypt(plaintext, engine_boots, engine_time, None)
.expect("encryption failed");
let wrong_decrypted = priv_key
.decrypt(&ciphertext, engine_boots, engine_time + 1, &priv_params)
.expect("decryption should succeed cryptographically");
assert_ne!(
wrong_decrypted.as_ref(),
plaintext,
"wrong engine_time should NOT produce the original plaintext"
);
let wrong_decrypted2 = priv_key
.decrypt(&ciphertext, engine_boots + 1, engine_time, &priv_params)
.expect("decryption should succeed cryptographically");
assert_ne!(
wrong_decrypted2.as_ref(),
plaintext,
"wrong engine_boots should NOT produce the original plaintext"
);
}
}