use crate::aes::aes_128_ecb_decrypt;
use crate::content::{decrypt_aligned_unit, ALIGNED_UNIT_SIZE};
use crate::error::AacsError;
use crate::keydb::KeyDb;
use crate::mkb::Mkb;
use crate::unit_key::UnitKeyFile;
use crate::vuk::Vuk;
use std::path::{Path, PathBuf};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct DeviceKey {
pub key: [u8; 16],
pub uv: u32,
pub u_mask_zero_bits: u8,
pub v_mask_zero_bits: u8,
pub device_node: Option<u32>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct TitleKey(pub [u8; 16]);
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct CpsUnit {
pub id: u16,
pub encrypted_title_key: [u8; 16],
pub title_key: Option<TitleKey>,
}
#[derive(Debug, Clone)]
pub struct AacsVolume {
pub mkb: Mkb,
pub unit_key_file: UnitKeyFile,
pub cps_units: Vec<CpsUnit>,
pub disc_root: PathBuf,
}
impl AacsVolume {
pub fn open(disc_root: impl AsRef<Path>) -> Result<Self, AacsError> {
let disc_root = disc_root.as_ref().to_path_buf();
let mkb_bytes = read_aacs_file(&disc_root, "MKB_RO.inf")?;
let unit_key_bytes = read_aacs_file(&disc_root, "Unit_Key_RO.inf")?;
let mkb = Mkb::parse(&mkb_bytes)?;
let unit_key_file = UnitKeyFile::parse(&unit_key_bytes)?;
let cps_units = unit_key_file
.cps_units
.iter()
.enumerate()
.map(|(i, rec)| CpsUnit {
id: (i + 1) as u16,
encrypted_title_key: rec.encrypted_cps_unit_key,
title_key: None,
})
.collect();
Ok(Self {
mkb,
unit_key_file,
cps_units,
disc_root,
})
}
pub fn resolve_vuk_from_keydb(&self, keydb: &KeyDb, disc_id: &[u8; 20]) -> Option<Vuk> {
keydb.vuk_for_disc(disc_id)
}
pub fn derive_vuk_from_device_key(
&self,
device_key: &DeviceKey,
volume_id: &[u8; 16],
) -> Result<Vuk, AacsError> {
let km = self.derive_media_key_from_device_key(device_key)?;
self.mkb.verify_media_key(&km)?;
Ok(crate::vuk::derive_vuk(&km, volume_id))
}
pub fn derive_vuk_from_device_key_with_kcd(
&self,
device_key: &DeviceKey,
volume_id: &[u8; 16],
kcd: Option<&[u8; 16]>,
) -> Result<Vuk, AacsError> {
let candidate = self.derive_media_key_from_device_key(device_key)?;
if self.mkb.is_verified_media_key(&candidate) {
return Ok(crate::vuk::derive_vuk(&candidate, volume_id));
}
if let Some(kcd_bytes) = kcd {
let km = crate::subdiff::apply_key_conversion_data(&candidate, kcd_bytes);
self.mkb.verify_media_key(&km)?;
return Ok(crate::vuk::derive_vuk(&km, volume_id));
}
Err(AacsError::MediaKeyVerificationFailed)
}
pub fn derive_media_key_from_device_key(
&self,
device_key: &DeviceKey,
) -> Result<[u8; 16], AacsError> {
use crate::subdiff::{
applies_to_device, derive_processing_key, media_key_from_processing_key,
SubsetDifference,
};
let d_node = device_key.device_node.unwrap_or((device_key.uv << 1) | 1);
let mut chosen: Option<(usize, SubsetDifference)> = None;
for (i, e) in self.mkb.explicit_subdiff.iter().enumerate() {
let sd = SubsetDifference {
u_mask_zero_bits: e.u_mask_zero_bits,
uv: e.uv,
};
if applies_to_device(&sd, d_node) {
chosen = Some((i, sd));
break;
}
}
let (idx, sd) = chosen.ok_or(AacsError::DeviceRevoked)?;
let target_v_mask_zero_bits = if sd.uv == 0 {
32
} else {
(sd.uv.trailing_zeros() + 1).min(32) as u8
};
let pk = derive_processing_key(
&device_key.key,
device_key.uv,
device_key.v_mask_zero_bits,
sd.uv,
target_v_mask_zero_bits,
)
.ok_or(AacsError::DeviceRevoked)?;
let enc_km = *self
.mkb
.media_key_data
.get(idx)
.ok_or(AacsError::MissingVerifyMediaKeyRecord)?;
Ok(media_key_from_processing_key(&pk, sd.uv, &enc_km))
}
pub fn unwrap_title_keys(&mut self, vuk: &Vuk) -> Result<(), AacsError> {
for unit in self.cps_units.iter_mut() {
let pt = aes_128_ecb_decrypt(vuk.as_bytes(), &unit.encrypted_title_key);
unit.title_key = Some(TitleKey(pt));
}
Ok(())
}
pub fn decrypt_unit(
&self,
cps_unit: &CpsUnit,
unit_bytes: &[u8],
) -> Result<[u8; ALIGNED_UNIT_SIZE], AacsError> {
let tk = cps_unit.title_key.ok_or(AacsError::InvalidValue {
what: "CPS Unit title key (not yet unwrapped)",
value: cps_unit.id as u64,
})?;
decrypt_aligned_unit(&tk.0, unit_bytes)
}
}
fn read_aacs_file(disc_root: &Path, name: &'static str) -> Result<Vec<u8>, AacsError> {
let primary = disc_root.join("AACS").join(name);
if let Ok(bytes) = std::fs::read(&primary) {
return Ok(bytes);
}
let dup = disc_root.join("AACS").join("DUPLICATE").join(name);
if let Ok(bytes) = std::fs::read(&dup) {
return Ok(bytes);
}
Err(AacsError::MissingDiscFile(name))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn unwrap_title_keys_uses_aes_128e_inverse() {
let vuk = Vuk::from_bytes([0x11u8; 16]);
let title_key = [0xABu8; 16];
let enc = crate::aes::aes_128_ecb_encrypt(vuk.as_bytes(), &title_key);
let mut vol = AacsVolume {
mkb: Mkb::default(),
unit_key_file: UnitKeyFile {
unit_key_block_start_address: 0,
header: crate::unit_key::UnitKeyFileHeader {
application_type: 1,
num_of_bd_directory: 1,
use_skb_unified_mkb: false,
bd_directories: Vec::new(),
},
cps_units: Vec::new(),
},
cps_units: vec![CpsUnit {
id: 1,
encrypted_title_key: enc,
title_key: None,
}],
disc_root: PathBuf::new(),
};
vol.unwrap_title_keys(&vuk).unwrap();
assert_eq!(vol.cps_units[0].title_key, Some(TitleKey(title_key)));
}
}