use super::keys::{decrypt_unit_key, derive_media_key_from_pk, derive_vuk};
use super::types::DeviceKey;
use super::variants::{KEY_CORRECTION_DATA_PLACEHOLDER, derive_media_key_variant, walk_mkb};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Vid(pub [u8; 16]);
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct MediaKey(pub [u8; 16]);
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Vuk(pub [u8; 16]);
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct UnitKey {
pub idx: u32,
pub key: [u8; 16],
}
pub fn vuk_from_mk(mk: MediaKey, vid: Vid) -> Vuk {
Vuk(derive_vuk(&mk.0, &vid.0))
}
pub fn uk_from_vuk(vuk: Vuk, enc_title_keys: &[[u8; 16]]) -> Vec<UnitKey> {
enc_title_keys
.iter()
.enumerate()
.map(|(i, enc)| UnitKey {
idx: i as u32,
key: decrypt_unit_key(&vuk.0, enc),
})
.collect()
}
pub fn mk_from_dk(
device_keys: &[DeviceKey],
mkb: &[u8],
vid: Vid,
) -> Result<MediaKey, crate::error::Error> {
let records = walk_mkb(mkb);
match derive_media_key_variant(
&records,
device_keys,
&KEY_CORRECTION_DATA_PLACEHOLDER,
&vid.0,
) {
Ok((km, _kvu)) => Ok(MediaKey(km)),
Err(_) => Err(crate::error::Error::AacsMkUnavailable),
}
}
pub fn mk_from_pk(
processing_keys: &[[u8; 16]],
mkb: &[u8],
) -> Result<MediaKey, crate::error::Error> {
match derive_media_key_from_pk(mkb, processing_keys) {
Some(km) => Ok(MediaKey(km)),
None => Err(crate::error::Error::AacsMkUnavailable),
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::aacs::decrypt::aes_ecb_encrypt;
use crate::aacs::keys::{decrypt_unit_key, derive_vuk};
#[test]
fn vuk_from_mk_matches_inline_derive_vuk() {
let cases: [([u8; 16], [u8; 16]); 3] = [
([0x5A; 16], [0xA5; 16]),
([0x11; 16], [0x22; 16]),
(
[
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C,
0x0D, 0x0E, 0x0F,
],
[
0xF0, 0xE1, 0xD2, 0xC3, 0xB4, 0xA5, 0x96, 0x87, 0x78, 0x69, 0x5A, 0x4B, 0x3C,
0x2D, 0x1E, 0x0F,
],
),
];
for (mk, vid) in cases {
let inline = derive_vuk(&mk, &vid);
let boiled = vuk_from_mk(MediaKey(mk), Vid(vid));
assert_eq!(boiled.0, inline, "vuk_from_mk must equal derive_vuk");
}
}
#[test]
fn uk_from_vuk_matches_inline_decrypt_unit_key() {
let vuk = [0x5Au8; 16];
let plain_keys = [[0x11u8; 16], [0x22u8; 16], [0xCDu8; 16]];
let enc: Vec<[u8; 16]> = plain_keys
.iter()
.map(|k| aes_ecb_encrypt(&vuk, k))
.collect();
let boiled = uk_from_vuk(Vuk(vuk), &enc);
assert_eq!(boiled.len(), enc.len());
for (i, uk) in boiled.iter().enumerate() {
assert_eq!(uk.idx, i as u32, "idx must be the positional index");
assert_eq!(uk.key, decrypt_unit_key(&vuk, &enc[i]));
assert_eq!(
uk.key, plain_keys[i],
"VUK roundtrip recovers the title key"
);
}
}
#[test]
fn uk_from_vuk_empty_is_empty() {
assert!(uk_from_vuk(Vuk([0u8; 16]), &[]).is_empty());
}
#[test]
fn mk_from_dk_errors_without_integrator_kcd() {
let dk = DeviceKey {
key: [0x11; 16],
node: 1,
uv: 1,
u_mask_shift: 0,
};
let e = mk_from_dk(std::slice::from_ref(&dk), &[], Vid([0x09; 16]));
assert!(matches!(e, Err(crate::error::Error::AacsMkUnavailable)));
let mut mkb: Vec<u8> = Vec::new();
mkb.extend_from_slice(&[0x82, 0x00, 0x00, 0x14]); mkb.extend_from_slice(&[0xAB; 16]);
let e2 = mk_from_dk(&[dk], &mkb, Vid([0x09; 16]));
assert!(matches!(e2, Err(crate::error::Error::AacsMkUnavailable)));
}
fn mkb_record(rec_type: u8, body: &[u8]) -> Vec<u8> {
let total = 4 + body.len();
let mut rec = Vec::with_capacity(total);
rec.push(rec_type);
rec.push(((total >> 16) & 0xFF) as u8);
rec.push(((total >> 8) & 0xFF) as u8);
rec.push((total & 0xFF) as u8);
rec.extend_from_slice(body);
rec
}
#[test]
fn mk_from_pk_drives_full_chain_to_uks() {
let pk: [u8; 16] = [
0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xAA, 0xBB, 0xCC, 0xDD, 0xEE,
0xFF, 0x00,
];
let mk: [u8; 16] = [
0xA0, 0xA1, 0xA2, 0xA3, 0xA4, 0xA5, 0xA6, 0xA7, 0xA8, 0xA9, 0xAA, 0xAB, 0xAC, 0xAD,
0xAE, 0xAF,
];
let uv: [u8; 4] = [0x00, 0x00, 0x04, 0x00];
let mut mk_raw = mk;
for a in 0..4 {
mk_raw[12 + a] ^= uv[a];
}
let cv = aes_ecb_encrypt(&pk, &mk_raw);
let mut vd = [0x11u8; 16];
vd[..8].copy_from_slice(&[0x01, 0x23, 0x45, 0x67, 0x89, 0xAB, 0xCD, 0xEF]);
let mk_dv = aes_ecb_encrypt(&mk, &vd);
let mut sd = vec![0u8];
sd.extend_from_slice(&uv);
let mut mkb = Vec::new();
mkb.extend_from_slice(&mkb_record(0x10, &[0, 0, 0, 0x20, 0, 0, 0, 0x52]));
mkb.extend_from_slice(&mkb_record(0x86, &mk_dv));
mkb.extend_from_slice(&mkb_record(0x04, &sd));
mkb.extend_from_slice(&mkb_record(0x05, &cv));
let got_mk = mk_from_pk(std::slice::from_ref(&pk), &mkb).expect("planted PK resolves MK");
assert_eq!(got_mk, MediaKey(mk), "mk_from_pk recovers the planted MK");
let vid = Vid([0x42u8; 16]);
let plain_uk = [0x7Eu8; 16];
let vuk = vuk_from_mk(got_mk, vid);
let enc = aes_ecb_encrypt(&vuk.0, &plain_uk);
let uks = uk_from_vuk(vuk, std::slice::from_ref(&enc));
assert_eq!(uks.len(), 1);
assert_eq!(uks[0].key, plain_uk, "PK chain recovers the title key");
let mut bad = pk;
bad[0] ^= 0xFF;
assert!(matches!(
mk_from_pk(std::slice::from_ref(&bad), &mkb),
Err(crate::error::Error::AacsMkUnavailable)
));
}
}