oxideav_aacs/subdiff.rs
1//! AACS Subset-Difference broadcast-encryption tree walk
2//! (Common spec §3.2.1 — §3.2.4).
3//!
4//! Each compliant device holds a small set of 128-bit *Device Keys*.
5//! Each Device Key sits at a position in a binary key-tree, identified
6//! by a 32-bit `uv` number with separate `u_mask` and `v_mask`
7//! "don't-care" masks. To extract the Media Key from an MKB, the
8//! device must:
9//!
10//! 1. Find an Explicit-Subset-Difference Record entry `(uv', m_u', m_v')`
11//! that *applies* to it — i.e. its leaf node `D_node` satisfies
12//! `(D_node & m_u) == (uv & m_u) && (D_node & m_v) != (uv & m_v)`.
13//! 2. Find a stored Device Key whose path matches the `m_u` part of
14//! the entry but whose `m_v'` doesn't match the entry's `m_v` —
15//! i.e. the stored key sits between the entry's `u`-node and the
16//! target `v`-node.
17//! 3. Walk down from that stored Device Key toward the entry's `v`
18//! node by repeated AES-G3 left/right-child derivation, ending at
19//! the device-key for the subset-difference's (u, v) pair.
20//! 4. Apply AES-G3 once more on the final Device Key to extract its
21//! *Processing Key* `K_p`.
22//! 5. The Media Key is then
23//! `K_m = AES-128D(K_p, C) XOR (0^96 || uv)` where `C` is the
24//! 16-byte ciphertext from the Media Key Data Record for this
25//! subset-difference.
26//!
27//! This module exposes each step as a public function so a caller can
28//! validate the intermediate Processing Key against a test vector if
29//! one becomes available, and to keep the cryptographic primitives
30//! decoupled from the MKB I/O.
31
32use crate::aes::{aes_128_ecb_decrypt, BLOCK_SIZE};
33
34/// AACS Triple-AES Generator seed register IV per Common spec §3.2.2
35/// Figure 3-3 (`s0 = 7B103C5DCB08C4E51A27B01799053BD9`).
36pub const AES_G3_SEED_S0: [u8; 16] = [
37 0x7B, 0x10, 0x3C, 0x5D, 0xCB, 0x08, 0xC4, 0xE5, 0x1A, 0x27, 0xB0, 0x17, 0x99, 0x05, 0x3B, 0xD9,
38];
39
40/// Output of [`aes_g3`]: the three 128-bit values derived from a
41/// single input Device Key.
42///
43/// Per Common spec §3.2.2 these are interpreted as:
44///
45/// - `left_child` — the subsidiary Device Key for the left child of
46/// the current node (or "ignored if the device key is a leaf").
47/// - `processing_key` — the Processing Key associated with the
48/// current node's subset-difference (if any).
49/// - `right_child` — the subsidiary Device Key for the right child.
50///
51/// All three are computed as `AES-128D(k, s0 + i) XOR (s0 + i)` for
52/// `i = 0, 1, 2`.
53#[derive(Debug, Clone, Copy, PartialEq, Eq)]
54pub struct AesG3Output {
55 /// Subsidiary Device Key for the left child of the current node.
56 pub left_child: [u8; 16],
57 /// Processing Key for the current node's subset-difference.
58 pub processing_key: [u8; 16],
59 /// Subsidiary Device Key for the right child of the current node.
60 pub right_child: [u8; 16],
61}
62
63/// AACS Triple-AES Generator (`AES-G3`) per Common spec §3.2.2,
64/// Figure 3-3.
65///
66/// `seed_register` starts at [`AES_G3_SEED_S0`] for the canonical
67/// generator; tests can supply other seeds.
68pub fn aes_g3(device_key: &[u8; 16]) -> AesG3Output {
69 aes_g3_with_seed(device_key, &AES_G3_SEED_S0)
70}
71
72/// `aes_g3` with a caller-supplied seed (used internally by
73/// [`aes_g3`] and exposed for the test crate).
74pub fn aes_g3_with_seed(device_key: &[u8; 16], seed: &[u8; 16]) -> AesG3Output {
75 let outs = [
76 aes_g3_step(device_key, seed, 0),
77 aes_g3_step(device_key, seed, 1),
78 aes_g3_step(device_key, seed, 2),
79 ];
80 AesG3Output {
81 left_child: outs[0],
82 processing_key: outs[1],
83 right_child: outs[2],
84 }
85}
86
87/// One step of the Triple-AES Generator: take the seed register
88/// incremented by `i`, decrypt it under the Device Key, then XOR the
89/// (incremented) seed back in. Spec §3.2.2, Figure 3-3.
90///
91/// The "seed register is incremented by one each time" in the figure
92/// is interpreted (per the text following) as treating the seed
93/// register as a 128-bit big-endian integer and adding `i`.
94fn aes_g3_step(key: &[u8; 16], seed: &[u8; 16], i: u8) -> [u8; 16] {
95 let mut s = *seed;
96 add_be_u128(&mut s, i as u128);
97 let d = aes_128_ecb_decrypt(key, &s);
98 let mut out = [0u8; 16];
99 for j in 0..BLOCK_SIZE {
100 out[j] = d[j] ^ s[j];
101 }
102 out
103}
104
105/// Treat `buf` as a 128-bit big-endian unsigned integer and add
106/// `addend` (wrapping). The seed register is only ever incremented by
107/// 0, 1, or 2 in the AACS pipeline so we never overflow in practice.
108fn add_be_u128(buf: &mut [u8; 16], addend: u128) {
109 let cur = u128::from_be_bytes(*buf);
110 let new = cur.wrapping_add(addend);
111 *buf = new.to_be_bytes();
112}
113
114/// A parsed Explicit-Subset-Difference entry per Common spec §3.2.5.1.5,
115/// i.e. one row of the Explicit-Subset-Difference Record. The `u_mask`
116/// is the first byte (number of low-order zero bits in the full
117/// 32-bit `m_u`); `uv` is the 32-bit `uv` number itself.
118///
119/// The `v_mask` is *derived* from `uv` per Common spec §3.2.3 (the
120/// `while ((uv & ~v_mask) == 0) v_mask <<= 1` C snippet).
121#[derive(Debug, Clone, Copy, PartialEq, Eq)]
122pub struct SubsetDifference {
123 /// Number of trailing zero bits in the 32-bit `m_u` mask — i.e.
124 /// `m_u = 0xFFFFFFFF << u_mask_zero_bits`.
125 pub u_mask_zero_bits: u8,
126 /// The 32-bit `uv` number itself.
127 pub uv: u32,
128}
129
130impl SubsetDifference {
131 /// Expand `u_mask_zero_bits` into the full 32-bit `m_u`.
132 pub fn u_mask(&self) -> u32 {
133 if self.u_mask_zero_bits == 0 {
134 0xFFFF_FFFF
135 } else if self.u_mask_zero_bits >= 32 {
136 0
137 } else {
138 0xFFFF_FFFFu32 << self.u_mask_zero_bits
139 }
140 }
141
142 /// Derive `m_v` from `uv` per Common spec §3.2.3 ("the mask for v
143 /// is given by the first lower-order 1-bit in the uv number.
144 /// **That bit, and all lower-order 0-bits, are zero bits in the
145 /// 'v' mask.**"). The spec's reference C code is:
146 ///
147 /// ```c
148 /// long v_mask = 0xFFFFFFFF;
149 /// while ((uv & ~v_mask) == 0) v_mask <<= 1;
150 /// ```
151 ///
152 /// i.e. the zero region of `m_v` is `trailing_zeros(uv) + 1` bits
153 /// wide — the lowest-order 1-bit AND every 0-bit below it.
154 pub fn v_mask(&self) -> u32 {
155 if self.uv == 0 {
156 // All "don't care" — degenerate but well-defined.
157 0
158 } else {
159 let zero_bits = self.uv.trailing_zeros() + 1;
160 if zero_bits >= 32 {
161 0
162 } else {
163 u32::MAX << zero_bits
164 }
165 }
166 }
167}
168
169/// Test whether the subset-difference `sd` covers the device whose
170/// 32-bit `D_node` is given, per Common spec §3.2.4:
171///
172/// `((D_node & m_u) == (uv & m_u)) && ((D_node & m_v) != (uv & m_v))`.
173pub fn applies_to_device(sd: &SubsetDifference, d_node: u32) -> bool {
174 let m_u = sd.u_mask();
175 let m_v = sd.v_mask();
176 ((d_node & m_u) == (sd.uv & m_u)) && ((d_node & m_v) != (sd.uv & m_v))
177}
178
179/// Walk down the Subset-Difference tree from a stored Device Key to
180/// the Processing Key for a target subset-difference, per Common spec
181/// §3.2.4 "the device does that as follows: …".
182///
183/// Inputs:
184///
185/// - `stored_device_key`: the 128-bit Device Key in the device's set
186/// that matched the target subset-difference's `m_u` half.
187/// - `stored_uv`: the `uv` number of `stored_device_key` (i.e. the
188/// node it sits at in the tree).
189/// - `stored_v_mask_zero_bits`: trailing zero bits of the stored
190/// key's `v` mask.
191/// - `target_uv`: the `uv` of the target subset-difference (the
192/// Explicit-Subset-Difference Record entry).
193/// - `target_v_mask_zero_bits`: trailing zero bits of the target
194/// subset-difference's `v` mask.
195///
196/// Returns the Processing Key for the target subset-difference, or
197/// `None` if `stored_v_mask_zero_bits == target_v_mask_zero_bits`
198/// (which means `stored_device_key` is *itself* the final key — in
199/// that case the caller should just call [`aes_g3`] on it and take
200/// `processing_key`).
201pub fn derive_processing_key(
202 stored_device_key: &[u8; 16],
203 stored_uv: u32,
204 stored_v_mask_zero_bits: u8,
205 target_uv: u32,
206 target_v_mask_zero_bits: u8,
207) -> Option<[u8; 16]> {
208 if stored_v_mask_zero_bits == target_v_mask_zero_bits {
209 // The stored key IS the final Device Key. Per spec §3.2.4:
210 // "If m'_v equals m_v, the starting Device Key is the final
211 // Device Key, and is used directly to derive the Processing
212 // Key, as described above."
213 return Some(aes_g3(stored_device_key).processing_key);
214 }
215 // Walk down: for each level, the bit *just above* the current
216 // m_v zero count tells us which child to take.
217 let mut d_k = *stored_device_key;
218 let mut m_zeros = stored_v_mask_zero_bits;
219 let _ = stored_uv; // not needed for the walk — only target_uv matters
220 while m_zeros > target_v_mask_zero_bits {
221 // Inspect the bit in `target_uv` at position (m_zeros - 1).
222 let bit_pos = m_zeros - 1;
223 let bit = (target_uv >> bit_pos) & 1;
224 let triple = aes_g3(&d_k);
225 d_k = if bit == 0 {
226 triple.left_child
227 } else {
228 triple.right_child
229 };
230 m_zeros -= 1;
231 }
232 Some(aes_g3(&d_k).processing_key)
233}
234
235/// Recover the Media Key from a Processing Key + the subset-
236/// difference's `uv` + the matching 16-byte Media-Key-Data entry, per
237/// Common spec §3.2.4 end:
238///
239/// `K_m = AES-128D(K_p, C) XOR (0^96 || uv)`
240///
241/// where `uv` is interpreted as a 32-bit big-endian value left-padded
242/// with 12 zero bytes.
243///
244/// **Note on MKB type**: for a Type-3 MKB this returns the actual
245/// Media Key `K_m`. For a Type-4 MKB the same calculation yields the
246/// Media Key *Precursor* `K_mp`, which the device must then post-
247/// process with the disc's Key Conversion Data via
248/// [`apply_key_conversion_data`] to obtain `K_m`. Common spec
249/// §3.2.5.1.4 and BD-Prerecorded §3.8.
250pub fn media_key_from_processing_key(
251 processing_key: &[u8; 16],
252 target_uv: u32,
253 encrypted_media_key: &[u8; 16],
254) -> [u8; 16] {
255 let mut d = aes_128_ecb_decrypt(processing_key, encrypted_media_key);
256 let uv_be = target_uv.to_be_bytes();
257 // XOR the 4-byte uv into the *last 4 bytes* of d (since the
258 // padding is "0^96 || uv" — 96 leading zero bits then the 32-bit
259 // uv).
260 d[12] ^= uv_be[0];
261 d[13] ^= uv_be[1];
262 d[14] ^= uv_be[2];
263 d[15] ^= uv_be[3];
264 d
265}
266
267/// Apply Key Conversion Data to a Media Key Precursor to obtain the
268/// Media Key, per AACS Common spec §3.2.5.1.4 and BD-Prerecorded
269/// spec §3.8:
270///
271/// ```text
272/// K_m = AES-G(K_mp, KCD)
273/// ```
274///
275/// For Type-4 MKBs (`MKBType = 0x0004_1003`), the subset-difference
276/// tree walk yields a Media Key Precursor `K_mp` rather than the
277/// Media Key directly. Devices that are required to use KCD (per the
278/// AACS Compliance Rules — broadly, non-PC Licensed Players without
279/// proactive renewal) combine the precursor with the disc's KCD
280/// payload to obtain `K_m`.
281///
282/// The 16-byte `kcd` parameter corresponds to the payload of the
283/// BD-ROM "KCD-Mark" (BD-Prerecorded Table 3-11), which a device
284/// reads via an out-of-band mechanism not described in the public
285/// spec set. In `oxideav-aacs` the KCD is supplied externally — most
286/// commonly via the `| KCD |` row of a `KEYDB.cfg` file, surfaced as
287/// [`crate::keydb::DiscRecords::kcd`].
288///
289/// **Important "old MKB" rule** (Common spec §3.2.5.1.4 final
290/// paragraph): a device that normally uses KCD must NOT apply it if
291/// the precursor already verifies as the Media Key. Callers that
292/// don't know the MKB type in advance should call
293/// [`Mkb::verify_media_key`](crate::mkb::Mkb::verify_media_key) on
294/// the precursor first, and only invoke `apply_key_conversion_data`
295/// when verification fails.
296pub fn apply_key_conversion_data(media_key_precursor: &[u8; 16], kcd: &[u8; 16]) -> [u8; 16] {
297 crate::aes::aes_g(media_key_precursor, kcd)
298}
299
300#[cfg(test)]
301mod tests {
302 use super::*;
303
304 #[test]
305 fn aes_g3_outputs_are_distinct() {
306 let dk = [0x55u8; 16];
307 let triple = aes_g3(&dk);
308 assert_ne!(triple.left_child, triple.processing_key);
309 assert_ne!(triple.processing_key, triple.right_child);
310 assert_ne!(triple.left_child, triple.right_child);
311 }
312
313 #[test]
314 fn u_mask_expansion() {
315 let sd = SubsetDifference {
316 u_mask_zero_bits: 0x01,
317 uv: 0x1234_5678,
318 };
319 assert_eq!(sd.u_mask(), 0xFFFF_FFFE);
320 let sd = SubsetDifference {
321 u_mask_zero_bits: 0x0A,
322 uv: 0,
323 };
324 assert_eq!(sd.u_mask(), 0xFFFF_FC00);
325 }
326
327 #[test]
328 fn v_mask_from_uv() {
329 // Per Common spec §3.2.3, zero bits in m_v include the lowest
330 // 1-bit AND all 0-bits below it. The spec's reference C is
331 // `while ((uv & ~v_mask) == 0) v_mask <<= 1;`
332 // — for `uv = 0x10` that means trailing_zeros(4) + 1 = 5 zero
333 // bits and `m_v = 0xFFFF_FFE0`.
334 let sd = SubsetDifference {
335 u_mask_zero_bits: 0,
336 uv: 0x0000_0010,
337 };
338 assert_eq!(sd.v_mask(), 0xFFFF_FFE0);
339 // uv with low bit at position 0 -> 1 zero bit -> m_v = 0xFFFFFFFE
340 let sd = SubsetDifference {
341 u_mask_zero_bits: 0,
342 uv: 0x0000_0001,
343 };
344 assert_eq!(sd.v_mask(), 0xFFFF_FFFE);
345 // uv = 0 is degenerate (no 1-bit) -> m_v = 0
346 let sd = SubsetDifference {
347 u_mask_zero_bits: 0,
348 uv: 0,
349 };
350 assert_eq!(sd.v_mask(), 0);
351 }
352
353 #[test]
354 fn applies_to_device_basic() {
355 // Pick a subset-difference where m_u spans 1 byte but m_v
356 // spans 2 — that gives applies() something meaningful to test
357 // (when m_u == m_v every applicable device has identical v
358 // halves, so the v-check is trivial).
359 //
360 // uv = 0x1101_0000 -> low bit at position 16 -> 17 zero bits
361 // (bit 16 + bits 0..15) -> m_v = 0xFFFE_0000.
362 // u_mask_zero_bits = 24 -> m_u = 0xFF00_0000.
363 let sd = SubsetDifference {
364 u_mask_zero_bits: 24,
365 uv: 0x1101_0000,
366 };
367 assert_eq!(sd.u_mask(), 0xFF00_0000);
368 assert_eq!(sd.v_mask(), 0xFFFE_0000);
369
370 // Device 0x1102_0000: D&m_u = 0x1100_0000 = uv&m_u (OK);
371 // D&m_v = 0x1102_0000 & 0xFFFE_0000 = 0x1102_0000,
372 // uv&m_v = 0x1101_0000 & 0xFFFE_0000 = 0x1100_0000;
373 // -> applies = true.
374 assert!(applies_to_device(&sd, 0x1102_0000));
375 // Device 0x1101_FFFF: D&m_v = 0x1100_0000 == uv&m_v -> false.
376 assert!(!applies_to_device(&sd, 0x1101_FFFF));
377 // Device 0x2200_0000: D&m_u = 0x2200_0000 != uv&m_u -> false.
378 assert!(!applies_to_device(&sd, 0x2200_0000));
379 }
380
381 #[test]
382 fn media_key_xor_lower_4_bytes() {
383 // With uv = 0 the XOR contributes nothing, so K_m must equal
384 // AES-128D(K_p, C).
385 let kp = [0x42u8; 16];
386 let c = [0xAAu8; 16];
387 let km = media_key_from_processing_key(&kp, 0, &c);
388 assert_eq!(km, aes_128_ecb_decrypt(&kp, &c));
389 // With uv != 0 the lower 4 bytes flip:
390 let km2 = media_key_from_processing_key(&kp, 0xDEAD_BEEF, &c);
391 let mut expected = aes_128_ecb_decrypt(&kp, &c);
392 expected[12] ^= 0xDE;
393 expected[13] ^= 0xAD;
394 expected[14] ^= 0xBE;
395 expected[15] ^= 0xEF;
396 assert_eq!(km2, expected);
397 }
398
399 /// Common spec §3.2.5.1.4 / BD-Prerecorded §3.8 define KCD post-
400 /// processing as `K_m = AES-G(K_mp, KCD)`. The helper must be
401 /// exactly equal to the public [`crate::aes::aes_g`] primitive.
402 #[test]
403 fn apply_kcd_equals_aes_g() {
404 let kmp = [
405 0x01, 0x23, 0x45, 0x67, 0x89, 0xAB, 0xCD, 0xEF, 0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54,
406 0x32, 0x10,
407 ];
408 let kcd = [
409 0xDE, 0xAD, 0xBE, 0xEF, 0xCA, 0xFE, 0xBA, 0xBE, 0xF0, 0x0D, 0xFA, 0xCE, 0x12, 0x34,
410 0x56, 0x78,
411 ];
412 let km_direct = crate::aes::aes_g(&kmp, &kcd);
413 let km_helper = apply_key_conversion_data(&kmp, &kcd);
414 assert_eq!(
415 km_helper, km_direct,
416 "apply_key_conversion_data must be aes_g(kmp, kcd)"
417 );
418 }
419
420 /// Idempotence-style sanity check: zero KCD with the AES-G
421 /// definition is still meaningful (it's `AES-128D(kmp, 0) XOR 0`).
422 /// Just pin that two distinct KCDs produce two distinct media
423 /// keys, so a future refactor that accidentally ignored `kcd`
424 /// would fail.
425 #[test]
426 fn apply_kcd_distinguishes_distinct_kcds() {
427 let kmp = [0x55u8; 16];
428 let a = apply_key_conversion_data(&kmp, &[0x00u8; 16]);
429 let b = apply_key_conversion_data(&kmp, &[0xFFu8; 16]);
430 assert_ne!(a, b, "different KCDs must yield different Media Keys");
431 }
432}