Skip to main content

iso_cenc/
cenc.rs

1//! ISO/IEC 23001-7 sample crypto — `cenc` (AES-128-CTR) first.
2//!
3//! Counter rules (CTR): each encrypted 16-byte block consumes one counter
4//! increment. Bytes in clear subsample ranges do **not** advance the counter.
5//! Partial final blocks of a protected range still consume one keystream block
6//! (unused keystream bytes discarded).
7
8#![forbid(unsafe_code)]
9
10use crate::Error;
11use aes::Aes128;
12use aes::cipher::{BlockCipherEncrypt, KeyInit};
13
14type Block16 = aes::cipher::Block<Aes128>;
15
16/// Protection scheme (`schm.scheme_type`).
17#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
18#[non_exhaustive]
19pub enum Scheme {
20    /// AES-128 CTR, full protected ranges (`cenc`).
21    Cenc,
22}
23
24/// Crypt/skip block pattern (`tenc` / pattern encryption).
25///
26/// [`Pattern::NONE`] means full-region encryption within each protected range
27/// (`cenc` / `cbc1`). Non-zero patterns are for `cens` / `cbcs` (not Stage 1).
28#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
29pub struct Pattern {
30    /// Encrypt this many 16-byte blocks, then skip.
31    pub crypt_blocks: u8,
32    /// Leave this many 16-byte blocks clear (pattern schemes).
33    pub skip_blocks: u8,
34}
35
36impl Pattern {
37    /// No pattern — encrypt every block in each protected range.
38    pub const NONE: Self = Self {
39        crypt_blocks: 0,
40        skip_blocks: 0,
41    };
42
43    /// True when this is full-region (no crypt/skip pattern).
44    #[must_use]
45    pub const fn is_none(self) -> bool {
46        self.crypt_blocks == 0
47    }
48}
49
50/// One subsample: clear bytes then protected bytes (ISO CENC subsample).
51#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
52pub struct Subsample {
53    /// Leading clear (unencrypted) byte count.
54    pub clear_bytes: u16,
55    /// Following protected byte count.
56    pub protected_bytes: u32,
57}
58
59/// Decrypt a sample in place under [`Scheme::Cenc`].
60///
61/// `iv` is the 16-byte CTR initialization block. For an 8-byte per-sample IV,
62/// place the IV in the high 8 bytes and zero the low 8 (ISO CTR construction).
63///
64/// Empty `subsamples` means the entire `data` buffer is one protected range.
65pub fn decrypt_cenc(
66    key: &[u8; 16],
67    iv: &[u8; 16],
68    pattern: Pattern,
69    data: &mut [u8],
70    subsamples: &[Subsample],
71) -> Result<(), Error> {
72    apply_cenc(key, iv, pattern, data, subsamples)
73}
74
75/// Encrypt a sample in place under [`Scheme::Cenc`] (same CTR keystream as decrypt).
76pub fn encrypt_cenc(
77    key: &[u8; 16],
78    iv: &[u8; 16],
79    pattern: Pattern,
80    data: &mut [u8],
81    subsamples: &[Subsample],
82) -> Result<(), Error> {
83    apply_cenc(key, iv, pattern, data, subsamples)
84}
85
86fn apply_cenc(
87    key: &[u8; 16],
88    iv: &[u8; 16],
89    pattern: Pattern,
90    data: &mut [u8],
91    subsamples: &[Subsample],
92) -> Result<(), Error> {
93    if !pattern.is_none() {
94        // Stage 1: `cenc` only — pattern schemes come later.
95        return Err(Error::InvalidPattern);
96    }
97    let cipher = Aes128::new(&(*key).into());
98    let mut counter = *iv;
99    if subsamples.is_empty() {
100        xor_ctr(&cipher, &mut counter, data);
101        return Ok(());
102    }
103    let mut pos = 0usize;
104    for sub in subsamples {
105        let clear = usize::from(sub.clear_bytes);
106        let protected = sub.protected_bytes as usize;
107        let end_clear = pos.checked_add(clear).ok_or(Error::SubsampleOverflow)?;
108        let end_prot = end_clear
109            .checked_add(protected)
110            .ok_or(Error::SubsampleOverflow)?;
111        if end_prot > data.len() {
112            return Err(Error::SubsampleOverflow);
113        }
114        // Clear range: leave bytes alone; do not advance CTR.
115        pos = end_clear;
116        if protected > 0 {
117            xor_ctr(&cipher, &mut counter, &mut data[pos..end_prot]);
118        }
119        pos = end_prot;
120    }
121    Ok(())
122}
123
124fn xor_ctr(cipher: &Aes128, counter: &mut [u8; 16], data: &mut [u8]) {
125    let mut offset = 0;
126    while offset < data.len() {
127        let mut block: Block16 = (*counter).into();
128        cipher.encrypt_block(&mut block);
129        let n = (data.len() - offset).min(16);
130        for i in 0..n {
131            data[offset + i] ^= block[i];
132        }
133        offset += n;
134        inc_be128(counter);
135    }
136}
137
138/// Big-endian 128-bit counter increment (wraps).
139fn inc_be128(block: &mut [u8; 16]) {
140    for i in (0..16).rev() {
141        let (v, overflow) = block[i].overflowing_add(1);
142        block[i] = v;
143        if !overflow {
144            break;
145        }
146    }
147}
148
149/// Build a 16-byte CTR IV from an 8-byte per-sample IV (high 8 = IV, low 8 = 0).
150#[must_use]
151pub fn iv_from_8(iv8: &[u8; 8]) -> [u8; 16] {
152    let mut out = [0u8; 16];
153    out[..8].copy_from_slice(iv8);
154    out
155}
156
157/// Build a 16-byte CTR IV from a constant IV of size 8 or 16.
158pub fn iv_from_constant(constant_iv: &[u8]) -> Result<[u8; 16], Error> {
159    match constant_iv.len() {
160        8 => {
161            let mut a = [0u8; 8];
162            a.copy_from_slice(constant_iv);
163            Ok(iv_from_8(&a))
164        }
165        16 => {
166            let mut a = [0u8; 16];
167            a.copy_from_slice(constant_iv);
168            Ok(a)
169        }
170        _ => Err(Error::InvalidKeyMaterial),
171    }
172}
173
174#[cfg(test)]
175#[path = "cenc_tests.rs"]
176mod tests;