1use aes::cipher::{BlockCipherEncrypt, KeyInit};
25use aes::{Aes128, Aes256};
26use sheathe_core::{Error, Result};
27
28mod pssh;
29pub use pssh::ProtectionSystem;
30
31#[derive(Debug, Clone, Copy, PartialEq, Eq)]
33pub enum Scheme {
34 Cenc,
36 Cens,
38 Cbc1,
40 Cbcs,
42}
43
44impl Scheme {
45 pub fn scheme_type(self) -> [u8; 4] {
47 match self {
48 Scheme::Cenc => *b"cenc",
49 Scheme::Cens => *b"cens",
50 Scheme::Cbc1 => *b"cbc1",
51 Scheme::Cbcs => *b"cbcs",
52 }
53 }
54
55 pub fn is_cbc(self) -> bool {
58 matches!(self, Scheme::Cbc1 | Scheme::Cbcs)
59 }
60
61 pub fn is_pattern(self) -> bool {
64 matches!(self, Scheme::Cens | Scheme::Cbcs)
65 }
66
67 pub fn uses_constant_iv(self) -> bool {
70 matches!(self, Scheme::Cbcs)
71 }
72}
73
74#[derive(Debug, Clone, Copy, PartialEq, Eq)]
79pub struct Pattern {
80 pub crypt_blocks: u8,
82 pub skip_blocks: u8,
84}
85
86impl Pattern {
87 pub const NONE: Pattern = Pattern { crypt_blocks: 0, skip_blocks: 0 };
89 pub const VIDEO: Pattern = Pattern { crypt_blocks: 1, skip_blocks: 9 };
91
92 pub const fn from_blocks(crypt: u8, skip: u8) -> Pattern {
95 Pattern { crypt_blocks: crypt, skip_blocks: skip }
96 }
97
98 fn is_patterned(self) -> bool {
100 self.crypt_blocks != 0
101 }
102}
103
104#[derive(Debug, Clone)]
106pub struct ContentKey {
107 pub kid: [u8; 16],
109 pub key: [u8; 16],
111}
112
113impl ContentKey {
114 pub fn rotated(&self, period: u32) -> ContentKey {
118 let n = (period % 16) as usize;
119 let mut kid = [0u8; 16];
120 let mut key = [0u8; 16];
121 for i in 0..16 {
122 kid[i] = self.kid[(i + n) % 16];
123 key[i] = self.key[(i + n) % 16];
124 }
125 ContentKey { kid, key }
126 }
127}
128
129#[derive(Debug, Clone, Copy, PartialEq, Eq)]
132pub struct Subsample {
133 pub clear: u32,
135 pub protected: u32,
137}
138
139pub struct Encryptor {
141 cipher: Aes128,
142}
143
144impl Encryptor {
145 pub fn new(key: &[u8; 16]) -> Self {
147 Self { cipher: Aes128::new_from_slice(key).expect("AES-128 key is 16 bytes") }
148 }
149
150 pub fn encrypt(
155 &self,
156 scheme: Scheme,
157 pattern: Pattern,
158 iv: &[u8; 16],
159 data: &mut [u8],
160 subsamples: &[Subsample],
161 ) -> Result<()> {
162 let total: u64 =
164 subsamples.iter().map(|s| u64::from(s.clear) + u64::from(s.protected)).sum();
165 if total != data.len() as u64 {
166 return Err(Error::malformed("subsample layout does not cover sample"));
167 }
168 if pattern.is_patterned() && !scheme.is_pattern() {
169 return Err(Error::malformed("pattern set on a non-pattern scheme"));
170 }
171 if scheme.is_cbc() {
172 self.cbc(pattern, iv, data, subsamples);
173 } else {
174 self.ctr(pattern, iv, data, subsamples);
175 }
176 Ok(())
177 }
178
179 fn ctr(&self, pattern: Pattern, iv: &[u8; 16], data: &mut [u8], subsamples: &[Subsample]) {
185 let mut counter = *iv;
186 let mut keystream = [0u8; 16];
187 let mut ks_pos = 16usize; for_each_crypt_range(pattern, subsamples, |start, len| {
190 for byte in &mut data[start..start + len] {
191 if ks_pos == 16 {
192 keystream = counter;
193 self.encrypt_block(&mut keystream);
194 incr_be(&mut counter);
195 ks_pos = 0;
196 }
197 *byte ^= keystream[ks_pos];
198 ks_pos += 1;
199 }
200 });
201 }
202
203 fn cbc(&self, pattern: Pattern, iv: &[u8; 16], data: &mut [u8], subsamples: &[Subsample]) {
209 let mut off = 0usize;
210 let mut chain = *iv;
214 for s in subsamples {
215 off += s.clear as usize;
216 if pattern.is_patterned() {
217 chain = *iv;
218 }
219 let mut remaining = s.protected as usize;
220 let mut block_index = 0usize;
221 let cycle = pattern.crypt_blocks as usize + pattern.skip_blocks as usize;
222 while remaining >= 16 {
223 let encrypt =
224 !pattern.is_patterned() || block_index % cycle < pattern.crypt_blocks as usize;
225 if encrypt {
226 let mut block = [0u8; 16];
227 block.copy_from_slice(&data[off..off + 16]);
228 for (b, c) in block.iter_mut().zip(chain.iter()) {
229 *b ^= *c;
230 }
231 self.encrypt_block(&mut block);
232 data[off..off + 16].copy_from_slice(&block);
233 chain = block;
234 }
235 off += 16;
236 remaining -= 16;
237 block_index += 1;
238 }
239 off += remaining; }
241 }
242
243 fn encrypt_block(&self, block: &mut [u8; 16]) {
245 let mut ga = (*block).into();
246 self.cipher.encrypt_block(&mut ga);
247 block.copy_from_slice(&ga);
248 }
249}
250
251fn for_each_crypt_range(
258 pattern: Pattern,
259 subsamples: &[Subsample],
260 mut f: impl FnMut(usize, usize),
261) {
262 let mut off = 0usize;
263 for s in subsamples {
264 off += s.clear as usize;
265 let protected = s.protected as usize;
266 if !pattern.is_patterned() {
267 if protected > 0 {
268 f(off, protected);
269 }
270 off += protected;
271 continue;
272 }
273 let crypt = pattern.crypt_blocks as usize * 16;
274 let skip = pattern.skip_blocks as usize * 16;
275 let mut pos = 0usize;
276 while pos < protected {
277 let phase = crypt.min(protected - pos);
278 let whole = phase - phase % 16;
281 if whole > 0 {
282 f(off + pos, whole);
283 }
284 pos += phase;
285 pos += skip.min(protected - pos);
286 }
287 off += protected;
288 }
289}
290
291fn incr_be(counter: &mut [u8; 16]) {
293 for byte in counter.iter_mut().rev() {
294 let (v, carry) = byte.overflowing_add(1);
295 *byte = v;
296 if !carry {
297 break;
298 }
299 }
300}
301
302pub fn aes_cbc_pkcs7_encrypt(key: &[u8], iv: &[u8], data: &[u8]) -> Result<Vec<u8>> {
306 if iv.len() != 16 {
307 return Err(Error::malformed("CBC IV must be 16 bytes"));
308 }
309 let padded = pkcs7_pad(data, 16);
310 aes_cbc_raw(key, iv, &padded)
311}
312
313pub fn aes_cbc_pkcs7_decrypt(key: &[u8], iv: &[u8], data: &[u8]) -> Result<Vec<u8>> {
315 if iv.len() != 16 {
316 return Err(Error::malformed("CBC IV must be 16 bytes"));
317 }
318 if data.len() % 16 != 0 || data.is_empty() {
319 return Err(Error::malformed("CBC ciphertext must be a non-empty multiple of 16"));
320 }
321 let plain = aes_cbc_raw_decrypt(key, iv, data)?;
322 pkcs7_unpad(&plain)
323}
324
325fn pkcs7_pad(data: &[u8], block: usize) -> Vec<u8> {
326 let n = block - (data.len() % block);
327 let mut v = data.to_vec();
328 v.extend(std::iter::repeat_n(n as u8, n));
329 v
330}
331
332fn pkcs7_unpad(data: &[u8]) -> Result<Vec<u8>> {
333 let n = *data.last().ok_or_else(|| Error::malformed("empty PKCS7 buffer"))? as usize;
334 if n == 0
335 || n > 16
336 || n > data.len()
337 || !data[data.len() - n..].iter().all(|&b| b as usize == n)
338 {
339 return Err(Error::malformed("invalid PKCS7 padding"));
340 }
341 Ok(data[..data.len() - n].to_vec())
342}
343
344fn aes_cbc_raw(key: &[u8], iv: &[u8], data: &[u8]) -> Result<Vec<u8>> {
345 let mut prev = [0u8; 16];
346 prev.copy_from_slice(iv);
347 let mut out = data.to_vec();
348 for chunk in out.chunks_mut(16) {
349 for (b, p) in chunk.iter_mut().zip(prev.iter()) {
350 *b ^= *p;
351 }
352 encrypt_block(key, chunk)?;
353 prev.copy_from_slice(chunk);
354 }
355 Ok(out)
356}
357
358fn aes_cbc_raw_decrypt(key: &[u8], iv: &[u8], data: &[u8]) -> Result<Vec<u8>> {
359 let mut prev = [0u8; 16];
360 prev.copy_from_slice(iv);
361 let mut out = data.to_vec();
362 for chunk in out.chunks_mut(16) {
363 let saved = <[u8; 16]>::try_from(&chunk[..]).unwrap();
364 decrypt_block(key, chunk)?;
365 for (b, p) in chunk.iter_mut().zip(prev.iter()) {
366 *b ^= *p;
367 }
368 prev = saved;
369 }
370 Ok(out)
371}
372
373fn encrypt_block(key: &[u8], block: &mut [u8]) -> Result<()> {
374 match key.len() {
375 16 => {
376 let cipher = Aes128::new_from_slice(key).expect("16");
377 let mut b = [0u8; 16];
378 b.copy_from_slice(block);
379 cipher.encrypt_block((&mut b).into());
380 block.copy_from_slice(&b);
381 Ok(())
382 }
383 32 => {
384 let cipher = Aes256::new_from_slice(key).expect("32");
385 let mut b = [0u8; 16];
386 b.copy_from_slice(block);
387 cipher.encrypt_block((&mut b).into());
388 block.copy_from_slice(&b);
389 Ok(())
390 }
391 _ => Err(Error::malformed("AES key must be 16 or 32 bytes")),
392 }
393}
394
395fn decrypt_block(key: &[u8], block: &mut [u8]) -> Result<()> {
396 use aes::cipher::BlockCipherDecrypt;
400 match key.len() {
401 16 => {
402 let cipher = Aes128::new_from_slice(key).expect("16");
403 let mut b = [0u8; 16];
404 b.copy_from_slice(block);
405 cipher.decrypt_block((&mut b).into());
406 block.copy_from_slice(&b);
407 Ok(())
408 }
409 32 => {
410 let cipher = Aes256::new_from_slice(key).expect("32");
411 let mut b = [0u8; 16];
412 b.copy_from_slice(block);
413 cipher.decrypt_block((&mut b).into());
414 block.copy_from_slice(&b);
415 Ok(())
416 }
417 _ => Err(Error::malformed("AES key must be 16 or 32 bytes")),
418 }
419}
420
421#[cfg(test)]
422mod tests {
423 use super::*;
424
425 const KEY: [u8; 16] = [
426 0x2b, 0x7e, 0x15, 0x16, 0x28, 0xae, 0xd2, 0xa6, 0xab, 0xf7, 0x15, 0x88, 0x09, 0xcf, 0x4f,
427 0x3c,
428 ];
429
430 fn hex(s: &str) -> Vec<u8> {
431 (0..s.len()).step_by(2).map(|i| u8::from_str_radix(&s[i..i + 2], 16).unwrap()).collect()
432 }
433
434 #[test]
435 fn cenc_matches_nist_ctr_vector() {
436 let iv = hex("f0f1f2f3f4f5f6f7f8f9fafbfcfdfeff");
438 let mut data = hex("6bc1bee22e409f96e93d7e117393172a");
439 let enc = Encryptor::new(&KEY);
440 let subs = [Subsample { clear: 0, protected: 16 }];
441 enc.encrypt(Scheme::Cenc, Pattern::NONE, iv[..].try_into().unwrap(), &mut data, &subs)
442 .unwrap();
443 assert_eq!(data, hex("874d6191b620e3261bef6864990db6ce"));
444 }
445
446 #[test]
447 fn cbc_schemes_match_nist_cbc_vector() {
448 let iv = hex("000102030405060708090a0b0c0d0e0f");
452 let subs = [Subsample { clear: 0, protected: 16 }];
453 for (scheme, pattern) in [(Scheme::Cbc1, Pattern::NONE), (Scheme::Cbcs, Pattern::VIDEO)] {
454 let mut data = hex("6bc1bee22e409f96e93d7e117393172a");
455 let enc = Encryptor::new(&KEY);
456 enc.encrypt(scheme, pattern, iv[..].try_into().unwrap(), &mut data, &subs).unwrap();
457 assert_eq!(data, hex("7649abac8119b246cee98e9b12e9197d"), "{scheme:?}");
458 }
459 }
460
461 #[test]
462 fn cenc_leaves_clear_bytes_untouched() {
463 let iv = [0u8; 16];
464 let mut data = vec![0xAAu8; 32];
465 let enc = Encryptor::new(&KEY);
466 enc.encrypt(
468 Scheme::Cenc,
469 Pattern::NONE,
470 &iv,
471 &mut data,
472 &[Subsample { clear: 8, protected: 24 }],
473 )
474 .unwrap();
475 assert!(data[..8].iter().all(|&b| b == 0xAA), "clear prefix must be untouched");
476 assert!(data[8..].iter().any(|&b| b != 0xAA), "protected region must change");
477 }
478
479 #[test]
480 fn pattern_schemes_skip_blocks() {
481 let iv = [0u8; 16];
484 for scheme in [Scheme::Cbcs, Scheme::Cens] {
485 let mut data = vec![0x11u8; 160];
486 let original = data.clone();
487 let enc = Encryptor::new(&KEY);
488 enc.encrypt(
489 scheme,
490 Pattern::VIDEO,
491 &iv,
492 &mut data,
493 &[Subsample { clear: 0, protected: 160 }],
494 )
495 .unwrap();
496 assert_ne!(data[..16], original[..16], "{scheme:?}: first block encrypted");
497 assert_eq!(data[16..], original[16..], "{scheme:?}: blocks 1..9 skipped");
498 }
499 }
500
501 #[test]
502 fn rejects_mismatched_layout() {
503 let enc = Encryptor::new(&KEY);
504 let mut data = vec![0u8; 10];
505 let err = enc.encrypt(
506 Scheme::Cenc,
507 Pattern::NONE,
508 &[0u8; 16],
509 &mut data,
510 &[Subsample { clear: 0, protected: 9 }],
511 );
512 assert!(err.is_err());
513 }
514
515 #[test]
516 fn content_key_rotation_left_rotates_by_period() {
517 let base = ContentKey { kid: KEY, key: KEY };
518 assert_eq!(base.rotated(0).kid, KEY, "period 0 is unchanged");
519 let mut expect = KEY;
521 expect.rotate_left(1);
522 assert_eq!(base.rotated(1).kid, expect);
523 assert_eq!(base.rotated(1).key, expect);
524 assert_eq!(base.rotated(16).kid, KEY);
526 }
527
528 #[test]
529 fn rejects_pattern_on_non_pattern_scheme() {
530 let enc = Encryptor::new(&KEY);
531 let mut data = vec![0u8; 16];
532 let err = enc.encrypt(
533 Scheme::Cenc,
534 Pattern::VIDEO,
535 &[0u8; 16],
536 &mut data,
537 &[Subsample { clear: 0, protected: 16 }],
538 );
539 assert!(err.is_err());
540 }
541
542 #[test]
546 fn ctr_schemes_round_trip_across_subsamples() {
547 let enc = Encryptor::new(&KEY);
548 let iv = [3u8; 16];
549 let subs = [Subsample { clear: 5, protected: 40 }, Subsample { clear: 10, protected: 65 }];
550 for (scheme, pattern) in [(Scheme::Cenc, Pattern::NONE), (Scheme::Cens, Pattern::VIDEO)] {
551 let original: Vec<u8> = (0..120u8).collect();
552 let mut data = original.clone();
553 enc.encrypt(scheme, pattern, &iv, &mut data, &subs).unwrap();
554 assert_ne!(data, original, "{scheme:?}: ciphertext must differ");
555 assert_eq!(&data[..5], &original[..5], "{scheme:?}: leading clear bytes preserved");
556 enc.encrypt(scheme, pattern, &iv, &mut data, &subs).unwrap();
557 assert_eq!(data, original, "{scheme:?}: CTR round-trip restores plaintext");
558 }
559 }
560
561 #[test]
564 fn cbc1_leaves_trailing_partial_clear() {
565 let enc = Encryptor::new(&KEY);
566 let iv = [7u8; 16];
567 let original: Vec<u8> = (0..40u8).collect();
569 let mut data = original.clone();
570 enc.encrypt(
571 Scheme::Cbc1,
572 Pattern::NONE,
573 &iv,
574 &mut data,
575 &[Subsample { clear: 3, protected: 37 }],
576 )
577 .unwrap();
578 assert_eq!(&data[..3], &original[..3], "leading clear preserved");
579 assert_ne!(&data[3..35], &original[3..35], "full blocks encrypted");
580 assert_eq!(&data[35..], &original[35..], "trailing partial block left clear");
581 }
582}