use crate::aes::{aes_128_ecb_decrypt, BLOCK_SIZE};
pub const AES_G3_SEED_S0: [u8; 16] = [
0x7B, 0x10, 0x3C, 0x5D, 0xCB, 0x08, 0xC4, 0xE5, 0x1A, 0x27, 0xB0, 0x17, 0x99, 0x05, 0x3B, 0xD9,
];
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct AesG3Output {
pub left_child: [u8; 16],
pub processing_key: [u8; 16],
pub right_child: [u8; 16],
}
pub fn aes_g3(device_key: &[u8; 16]) -> AesG3Output {
aes_g3_with_seed(device_key, &AES_G3_SEED_S0)
}
pub fn aes_g3_with_seed(device_key: &[u8; 16], seed: &[u8; 16]) -> AesG3Output {
let outs = [
aes_g3_step(device_key, seed, 0),
aes_g3_step(device_key, seed, 1),
aes_g3_step(device_key, seed, 2),
];
AesG3Output {
left_child: outs[0],
processing_key: outs[1],
right_child: outs[2],
}
}
fn aes_g3_step(key: &[u8; 16], seed: &[u8; 16], i: u8) -> [u8; 16] {
let mut s = *seed;
add_be_u128(&mut s, i as u128);
let d = aes_128_ecb_decrypt(key, &s);
let mut out = [0u8; 16];
for j in 0..BLOCK_SIZE {
out[j] = d[j] ^ s[j];
}
out
}
fn add_be_u128(buf: &mut [u8; 16], addend: u128) {
let cur = u128::from_be_bytes(*buf);
let new = cur.wrapping_add(addend);
*buf = new.to_be_bytes();
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct SubsetDifference {
pub u_mask_zero_bits: u8,
pub uv: u32,
}
impl SubsetDifference {
pub fn u_mask(&self) -> u32 {
if self.u_mask_zero_bits == 0 {
0xFFFF_FFFF
} else if self.u_mask_zero_bits >= 32 {
0
} else {
0xFFFF_FFFFu32 << self.u_mask_zero_bits
}
}
pub fn v_mask(&self) -> u32 {
if self.uv == 0 {
0
} else {
let zero_bits = self.uv.trailing_zeros() + 1;
if zero_bits >= 32 {
0
} else {
u32::MAX << zero_bits
}
}
}
}
pub fn applies_to_device(sd: &SubsetDifference, d_node: u32) -> bool {
let m_u = sd.u_mask();
let m_v = sd.v_mask();
((d_node & m_u) == (sd.uv & m_u)) && ((d_node & m_v) != (sd.uv & m_v))
}
pub fn derive_processing_key(
stored_device_key: &[u8; 16],
stored_uv: u32,
stored_v_mask_zero_bits: u8,
target_uv: u32,
target_v_mask_zero_bits: u8,
) -> Option<[u8; 16]> {
if stored_v_mask_zero_bits == target_v_mask_zero_bits {
return Some(aes_g3(stored_device_key).processing_key);
}
let mut d_k = *stored_device_key;
let mut m_zeros = stored_v_mask_zero_bits;
let _ = stored_uv; while m_zeros > target_v_mask_zero_bits {
let bit_pos = m_zeros - 1;
let bit = (target_uv >> bit_pos) & 1;
let triple = aes_g3(&d_k);
d_k = if bit == 0 {
triple.left_child
} else {
triple.right_child
};
m_zeros -= 1;
}
Some(aes_g3(&d_k).processing_key)
}
pub fn media_key_from_processing_key(
processing_key: &[u8; 16],
target_uv: u32,
encrypted_media_key: &[u8; 16],
) -> [u8; 16] {
let mut d = aes_128_ecb_decrypt(processing_key, encrypted_media_key);
let uv_be = target_uv.to_be_bytes();
d[12] ^= uv_be[0];
d[13] ^= uv_be[1];
d[14] ^= uv_be[2];
d[15] ^= uv_be[3];
d
}
pub fn apply_key_conversion_data(media_key_precursor: &[u8; 16], kcd: &[u8; 16]) -> [u8; 16] {
crate::aes::aes_g(media_key_precursor, kcd)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn aes_g3_outputs_are_distinct() {
let dk = [0x55u8; 16];
let triple = aes_g3(&dk);
assert_ne!(triple.left_child, triple.processing_key);
assert_ne!(triple.processing_key, triple.right_child);
assert_ne!(triple.left_child, triple.right_child);
}
#[test]
fn u_mask_expansion() {
let sd = SubsetDifference {
u_mask_zero_bits: 0x01,
uv: 0x1234_5678,
};
assert_eq!(sd.u_mask(), 0xFFFF_FFFE);
let sd = SubsetDifference {
u_mask_zero_bits: 0x0A,
uv: 0,
};
assert_eq!(sd.u_mask(), 0xFFFF_FC00);
}
#[test]
fn v_mask_from_uv() {
let sd = SubsetDifference {
u_mask_zero_bits: 0,
uv: 0x0000_0010,
};
assert_eq!(sd.v_mask(), 0xFFFF_FFE0);
let sd = SubsetDifference {
u_mask_zero_bits: 0,
uv: 0x0000_0001,
};
assert_eq!(sd.v_mask(), 0xFFFF_FFFE);
let sd = SubsetDifference {
u_mask_zero_bits: 0,
uv: 0,
};
assert_eq!(sd.v_mask(), 0);
}
#[test]
fn applies_to_device_basic() {
let sd = SubsetDifference {
u_mask_zero_bits: 24,
uv: 0x1101_0000,
};
assert_eq!(sd.u_mask(), 0xFF00_0000);
assert_eq!(sd.v_mask(), 0xFFFE_0000);
assert!(applies_to_device(&sd, 0x1102_0000));
assert!(!applies_to_device(&sd, 0x1101_FFFF));
assert!(!applies_to_device(&sd, 0x2200_0000));
}
#[test]
fn media_key_xor_lower_4_bytes() {
let kp = [0x42u8; 16];
let c = [0xAAu8; 16];
let km = media_key_from_processing_key(&kp, 0, &c);
assert_eq!(km, aes_128_ecb_decrypt(&kp, &c));
let km2 = media_key_from_processing_key(&kp, 0xDEAD_BEEF, &c);
let mut expected = aes_128_ecb_decrypt(&kp, &c);
expected[12] ^= 0xDE;
expected[13] ^= 0xAD;
expected[14] ^= 0xBE;
expected[15] ^= 0xEF;
assert_eq!(km2, expected);
}
#[test]
fn apply_kcd_equals_aes_g() {
let kmp = [
0x01, 0x23, 0x45, 0x67, 0x89, 0xAB, 0xCD, 0xEF, 0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54,
0x32, 0x10,
];
let kcd = [
0xDE, 0xAD, 0xBE, 0xEF, 0xCA, 0xFE, 0xBA, 0xBE, 0xF0, 0x0D, 0xFA, 0xCE, 0x12, 0x34,
0x56, 0x78,
];
let km_direct = crate::aes::aes_g(&kmp, &kcd);
let km_helper = apply_key_conversion_data(&kmp, &kcd);
assert_eq!(
km_helper, km_direct,
"apply_key_conversion_data must be aes_g(kmp, kcd)"
);
}
#[test]
fn apply_kcd_distinguishes_distinct_kcds() {
let kmp = [0x55u8; 16];
let a = apply_key_conversion_data(&kmp, &[0x00u8; 16]);
let b = apply_key_conversion_data(&kmp, &[0xFFu8; 16]);
assert_ne!(a, b, "different KCDs must yield different Media Keys");
}
}