Skip to main content

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}