use aes::cipher::generic_array::GenericArray;
use aes::cipher::{BlockDecryptMut, BlockEncryptMut, KeyIvInit, StreamCipher};
use crate::cenc::{SampleEncryptionEntry, SubSampleEntry, TrackEncryptionBox};
use crate::error::{Error, Result};
type Aes128Ctr = ctr::Ctr128BE<aes::Aes128>;
type Aes128CbcDec = cbc::Decryptor<aes::Aes128>;
type Aes128CbcEnc = cbc::Encryptor<aes::Aes128>;
const KEY_LEN: usize = 16;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum CbcsOp {
Encrypt,
Decrypt,
}
pub(crate) fn apply_ctr(
iv: &[u8],
key: &[u8; KEY_LEN],
subsamples: &[SubSampleEntry],
data: &mut [u8],
) -> Result<()> {
if iv.len() > KEY_LEN {
return Err(Error::InvalidInput(
"CENC per-sample IV longer than 16 bytes",
));
}
let mut counter = [0u8; KEY_LEN];
counter[..iv.len()].copy_from_slice(iv);
let mut cipher = Aes128Ctr::new(key.into(), (&counter).into());
if subsamples.is_empty() {
cipher.apply_keystream(data);
return Ok(());
}
let mut offset = 0usize;
for sub in subsamples {
let clear = sub.bytes_of_clear_data as usize;
let protected = sub.bytes_of_protected_data as usize;
offset = offset
.checked_add(clear)
.ok_or(Error::InvalidInput("CENC subsample clear length overflow"))?;
let end = offset.checked_add(protected).ok_or(Error::InvalidInput(
"CENC subsample protected length overflow",
))?;
if end > data.len() {
return Err(Error::BufferTooShort {
need: end,
have: data.len(),
what: "CENC subsample range exceeds sample",
});
}
cipher.apply_keystream(&mut data[offset..end]);
offset = end;
}
Ok(())
}
fn resolve_cbcs_iv(
entry: &SampleEncryptionEntry,
tenc: &TrackEncryptionBox,
) -> Result<[u8; KEY_LEN]> {
let src: &[u8] = if !entry.initialization_vector.is_empty() {
&entry.initialization_vector
} else if let Some(civ) = tenc.default_constant_iv.as_deref() {
civ
} else {
return Err(Error::InvalidInput(
"cbcs sample has no per-sample IV and tenc carries no default_constant_IV",
));
};
if src.len() > KEY_LEN {
return Err(Error::InvalidInput("CBCS IV longer than 16 bytes"));
}
let mut iv = [0u8; KEY_LEN];
iv[..src.len()].copy_from_slice(src);
Ok(iv)
}
pub(crate) fn cbcs_pattern(
key: &[u8; KEY_LEN],
chain_iv: &mut [u8; KEY_LEN],
crypt_byte_block: u8,
skip_byte_block: u8,
range: &mut [u8],
op: CbcsOp,
) {
let (crypt_blocks, skip_blocks) = if crypt_byte_block == 0 && skip_byte_block == 0 {
(1usize, 0usize)
} else {
(crypt_byte_block as usize, skip_byte_block as usize)
};
let mut offset = 0usize;
while offset < range.len() {
let remaining = range.len() - offset;
let want = crypt_blocks * KEY_LEN;
let run_len = (want.min(remaining) / KEY_LEN) * KEY_LEN;
if run_len == 0 {
break;
}
match op {
CbcsOp::Decrypt => {
let mut next_chain = [0u8; KEY_LEN];
next_chain.copy_from_slice(&range[offset + run_len - KEY_LEN..offset + run_len]);
let mut dec = Aes128CbcDec::new(key.into(), (&*chain_iv).into());
for chunk in range[offset..offset + run_len].chunks_exact_mut(KEY_LEN) {
let block = GenericArray::from_mut_slice(chunk);
dec.decrypt_block_mut(block);
}
*chain_iv = next_chain;
}
CbcsOp::Encrypt => {
let mut enc = Aes128CbcEnc::new(key.into(), (&*chain_iv).into());
for chunk in range[offset..offset + run_len].chunks_exact_mut(KEY_LEN) {
let block = GenericArray::from_mut_slice(chunk);
enc.encrypt_block_mut(block);
}
chain_iv.copy_from_slice(&range[offset + run_len - KEY_LEN..offset + run_len]);
}
}
offset += run_len;
if run_len < want {
break;
}
offset += (skip_blocks * KEY_LEN).min(range.len() - offset);
}
}
pub(crate) fn cbcs_sample(
tenc: &TrackEncryptionBox,
entry: &SampleEncryptionEntry,
key: &[u8; KEY_LEN],
data: &mut [u8],
op: CbcsOp,
) -> Result<()> {
let crypt_blocks = tenc.default_crypt_byte_block;
let skip_blocks = tenc.default_skip_byte_block;
if crypt_blocks == 0 && skip_blocks != 0 {
return Err(Error::InvalidInput(
"cbcs pattern crypt_byte_block=0 with nonzero skip leaves data unprotected",
));
}
let seed_iv = resolve_cbcs_iv(entry, tenc)?;
if entry.subsamples.is_empty() {
let mut chain_iv = seed_iv;
cbcs_pattern(key, &mut chain_iv, crypt_blocks, skip_blocks, data, op);
return Ok(());
}
let mut offset = 0usize;
for sub in &entry.subsamples {
let clear = sub.bytes_of_clear_data as usize;
let protected = sub.bytes_of_protected_data as usize;
offset = offset
.checked_add(clear)
.ok_or(Error::InvalidInput("CBCS subsample clear length overflow"))?;
let end = offset.checked_add(protected).ok_or(Error::InvalidInput(
"CBCS subsample protected length overflow",
))?;
if end > data.len() {
return Err(Error::BufferTooShort {
need: end,
have: data.len(),
what: "CBCS subsample range exceeds sample",
});
}
let mut chain_iv = seed_iv;
cbcs_pattern(
key,
&mut chain_iv,
crypt_blocks,
skip_blocks,
&mut data[offset..end],
op,
);
offset = end;
}
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
const KEY: [u8; KEY_LEN] = [
0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F,
0x10,
];
const IV8: [u8; 8] = [0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88];
#[test]
fn ctr_encrypt_then_decrypt_round_trips() {
let plaintext: Vec<u8> = (0u8..97).collect(); let subsamples = alloc::vec![
SubSampleEntry {
bytes_of_clear_data: 5,
bytes_of_protected_data: 32,
},
SubSampleEntry {
bytes_of_clear_data: 3,
bytes_of_protected_data: 57,
},
];
let mut buf = plaintext.clone();
apply_ctr(&IV8, &KEY, &subsamples, &mut buf).unwrap();
assert_ne!(
buf, plaintext,
"encryption should change the protected bytes"
);
apply_ctr(&IV8, &KEY, &subsamples, &mut buf).unwrap();
assert_eq!(buf, plaintext);
}
#[test]
fn cbcs_encrypt_then_decrypt_round_trips_with_pattern_and_trailing_partial() {
const CRYPT_BLOCKS: u8 = 1;
const SKIP_BLOCKS: u8 = 9;
let plaintext: Vec<u8> = (0u8..=255).cycle().take(320 + 10).collect();
let tenc = TrackEncryptionBox {
version: 1,
default_crypt_byte_block: CRYPT_BLOCKS,
default_skip_byte_block: SKIP_BLOCKS,
default_is_protected: 1,
default_per_sample_iv_size: 16,
default_kid: [0u8; KEY_LEN],
default_constant_iv: None,
};
let entry = SampleEncryptionEntry {
initialization_vector: IV8.to_vec(),
subsamples: Vec::new(),
};
let mut buf = plaintext.clone();
cbcs_sample(&tenc, &entry, &KEY, &mut buf, CbcsOp::Encrypt).unwrap();
assert_ne!(
buf, plaintext,
"encryption should change the protected blocks"
);
cbcs_sample(&tenc, &entry, &KEY, &mut buf, CbcsOp::Decrypt).unwrap();
assert_eq!(buf, plaintext);
}
#[test]
fn cbcs_sample_decrypt_rejects_zero_crypt_nonzero_skip() {
let tenc = TrackEncryptionBox {
version: 1,
default_crypt_byte_block: 0,
default_skip_byte_block: 9,
default_is_protected: 1,
default_per_sample_iv_size: 8,
default_kid: [0u8; KEY_LEN],
default_constant_iv: None,
};
let entry = SampleEncryptionEntry {
initialization_vector: IV8.to_vec(),
subsamples: Vec::new(),
};
let mut data: Vec<u8> = (0u8..64).collect();
let err = cbcs_sample(&tenc, &entry, &KEY, &mut data, CbcsOp::Decrypt).unwrap_err();
assert!(matches!(err, Error::InvalidInput(_)));
}
}