use crate::{Error, Result};
#[derive(Debug, Clone)]
pub struct AesProperties {
pub num_cycles_power: u8,
pub salt: Vec<u8>,
pub iv: Vec<u8>,
}
impl AesProperties {
pub fn parse(properties: &[u8]) -> Result<Self> {
let first_byte = *properties
.first()
.ok_or_else(|| Error::InvalidFormat("AES properties are empty".into()))?;
let num_cycles_power = first_byte & 0x3F;
let salt_flag = (first_byte >> 7) & 1;
let iv_flag = (first_byte >> 6) & 1;
if salt_flag == 0 && iv_flag == 0 {
if properties.len() != 1 {
return Err(Error::InvalidFormat(format!(
"AES properties declare no salt and no IV but carry {} bytes",
properties.len()
)));
}
return Ok(Self {
num_cycles_power,
salt: Vec::new(),
iv: vec![0u8; 16],
});
}
let second_byte = *properties.get(1).ok_or_else(|| {
Error::InvalidFormat(
"AES properties declare a salt or IV but carry no size byte".into(),
)
})?;
let salt_size_extra = (second_byte >> 4) & 0x0F;
let iv_size_extra = second_byte & 0x0F;
let salt_size = if salt_flag == 1 {
(1 + salt_size_extra) as usize
} else {
0
};
let iv_size = if iv_flag == 1 {
(1 + iv_size_extra) as usize
} else {
0
};
let data_start = 2;
let salt_end = data_start + salt_size;
let iv_end = salt_end + iv_size;
if properties.len() != iv_end {
return Err(Error::InvalidFormat(format!(
"AES properties declare {} bytes of salt and IV but carry {}",
iv_end - data_start,
properties.len().saturating_sub(data_start)
)));
}
let salt = properties[data_start..salt_end].to_vec();
let mut iv = vec![0u8; 16];
let iv_data = &properties[salt_end..iv_end];
iv[..iv_data.len()].copy_from_slice(iv_data);
Ok(Self {
num_cycles_power,
salt,
iv,
})
}
pub fn encode(num_cycles_power: u8, salt: &[u8], iv: &[u8]) -> Result<Vec<u8>> {
if salt.len() > 16 {
return Err(Error::InvalidFormat(format!(
"salt is {} bytes; the 7z AES property encoding allows at most 16",
salt.len()
)));
}
let salt_size = salt.len();
let iv_size = iv.len().min(16);
let salt_flag = if salt_size > 0 { 1u8 } else { 0u8 };
let iv_flag = if iv_size > 0 { 1u8 } else { 0u8 };
let salt_size_extra = if salt_size > 0 {
(salt_size - 1) as u8
} else {
0
};
let iv_size_extra = if iv_size > 0 { (iv_size - 1) as u8 } else { 0 };
let first_byte = (salt_flag << 7) | (iv_flag << 6) | (num_cycles_power & 0x3F);
let second_byte = (salt_size_extra << 4) | iv_size_extra;
let mut result = vec![first_byte, second_byte];
result.extend_from_slice(salt);
result.extend_from_slice(&iv[..iv_size]);
Ok(result)
}
}
#[derive(Debug, Clone)]
pub enum NoncePolicy {
Random {
num_cycles_power: u8,
salt_size: usize,
},
Deterministic {
num_cycles_power: u8,
seed: [u8; 32],
},
Explicit {
num_cycles_power: u8,
salt: Vec<u8>,
iv: Vec<u8>,
},
}
impl Default for NoncePolicy {
fn default() -> Self {
Self::Random {
num_cycles_power: 19, salt_size: 8,
}
}
}
impl NoncePolicy {
pub fn random() -> Self {
Self::default()
}
pub fn random_with_params(num_cycles_power: u8, salt_size: usize) -> Self {
Self::Random {
num_cycles_power,
salt_size: salt_size.min(16),
}
}
pub fn explicit(num_cycles_power: u8, salt: Vec<u8>, iv: Vec<u8>) -> Self {
Self::Explicit {
num_cycles_power,
salt,
iv,
}
}
pub fn num_cycles_power(&self) -> u8 {
match self {
Self::Random {
num_cycles_power, ..
} => *num_cycles_power,
Self::Deterministic {
num_cycles_power, ..
} => *num_cycles_power,
Self::Explicit {
num_cycles_power, ..
} => *num_cycles_power,
}
}
pub fn next_iv(&self) -> Result<[u8; 16]> {
match self {
Self::Random { .. } => {
let mut iv = [0u8; 16];
getrandom::getrandom(&mut iv)
.map_err(|e| Error::InvalidFormat(format!("IV generation failed: {e}")))?;
Ok(iv)
}
Self::Deterministic { .. } | Self::Explicit { .. } => Ok(self.generate()?.1),
}
}
pub fn generate(&self) -> Result<(Vec<u8>, [u8; 16])> {
match self {
Self::Random { salt_size, .. } => {
let mut salt = vec![0u8; *salt_size];
let mut iv = [0u8; 16];
getrandom::getrandom(&mut salt)
.map_err(|e| Error::InvalidFormat(format!("salt generation failed: {e}")))?;
getrandom::getrandom(&mut iv)
.map_err(|e| Error::InvalidFormat(format!("IV generation failed: {e}")))?;
Ok((salt, iv))
}
Self::Deterministic { seed, .. } => {
use sha2::{Digest, Sha256};
let mut hasher = Sha256::new();
hasher.update(seed);
hasher.update(b"salt");
let salt_hash = hasher.finalize();
let mut hasher = Sha256::new();
hasher.update(seed);
hasher.update(b"iv");
let iv_hash = hasher.finalize();
let salt = salt_hash[..8].to_vec();
let mut iv = [0u8; 16];
iv.copy_from_slice(&iv_hash[..16]);
Ok((salt, iv))
}
Self::Explicit { salt, iv, .. } => {
let mut iv_arr = [0u8; 16];
let iv_len = iv.len().min(16);
iv_arr[..iv_len].copy_from_slice(&iv[..iv_len]);
Ok((salt.clone(), iv_arr))
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_parse_minimal_properties() {
let parsed = AesProperties::parse(&[0x13]).unwrap();
assert_eq!(parsed.num_cycles_power, 19);
assert!(parsed.salt.is_empty());
assert_eq!(parsed.iv, vec![0u8; 16]);
assert!(AesProperties::parse(&[0x13, 0x00]).is_err());
}
#[test]
fn test_parse_with_salt_and_iv() {
let mut props = vec![0xD3, 0x7F]; props.extend_from_slice(&[1, 2, 3, 4, 5, 6, 7, 8]); props.extend_from_slice(&[
9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24,
]);
let parsed = AesProperties::parse(&props).unwrap();
assert_eq!(parsed.num_cycles_power, 19);
assert_eq!(parsed.salt, vec![1, 2, 3, 4, 5, 6, 7, 8]);
assert_eq!(
parsed.iv,
vec![
9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24
]
);
}
#[test]
fn test_parse_too_short() {
let decoded = AesProperties::parse(&[0x13]).expect("a bare cycle count is legal");
assert_eq!(decoded.num_cycles_power, 0x13);
assert!(decoded.salt.is_empty());
assert!(AesProperties::parse(&[0x93]).is_err());
}
#[test]
fn test_encode_decode_roundtrip() {
let salt = vec![1, 2, 3, 4];
let iv = vec![5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16];
let num_cycles_power = 19;
let encoded = AesProperties::encode(num_cycles_power, &salt, &iv).unwrap();
let decoded = AesProperties::parse(&encoded).unwrap();
assert_eq!(decoded.num_cycles_power, num_cycles_power);
assert_eq!(decoded.salt, salt);
let mut expected_iv = iv.clone();
expected_iv.resize(16, 0);
assert_eq!(decoded.iv, expected_iv);
}
#[test]
fn test_encode_rejects_oversized_salt() {
let salt = vec![0u8; 17];
let iv = vec![0u8; 16];
let error = AesProperties::encode(19, &salt, &iv)
.expect_err("a 17-byte salt does not fit the size nibble");
assert!(error.to_string().contains("at most 16"), "{error}");
}
#[test]
fn test_parse_rejects_trailing_bytes() {
let mut encoded = AesProperties::encode(19, &[1, 2, 3, 4], &[7u8; 16]).unwrap();
encoded.push(0xFF);
assert!(AesProperties::parse(&encoded).is_err());
}
#[test]
fn test_nonce_policy_explicit() {
let policy = NoncePolicy::explicit(19, vec![1, 2, 3], vec![4, 5, 6, 7]);
let (salt, iv) = policy.generate().unwrap();
assert_eq!(salt, vec![1, 2, 3]);
assert_eq!(&iv[..4], &[4, 5, 6, 7]);
}
#[test]
fn test_nonce_policy_deterministic() {
let seed = [42u8; 32];
let policy = NoncePolicy::Deterministic {
num_cycles_power: 19,
seed,
};
let (salt1, iv1) = policy.generate().unwrap();
let (salt2, iv2) = policy.generate().unwrap();
assert_eq!(salt1, salt2);
assert_eq!(iv1, iv2);
}
}