diff --git a/src/aacs.rs b/src/aacs.rs index 242fe7a..885047e 100644 --- a/src/aacs.rs +++ b/src/aacs.rs @@ -858,6 +858,8 @@ pub struct ResolvedKeys { pub aacs2: bool, /// Whether bus encryption is enabled (from Content Certificate) pub bus_encryption: bool, + /// Which resolution path succeeded (1=KEYDB, 2=KEYDB derived, 3=PK, 4=DK) + pub key_source: u8, } /// Resolve all AACS keys for a disc given: @@ -893,22 +895,26 @@ pub fn resolve_keys( let hash_hex = disc_hash_hex(&uk_file.disc_hash); + // Helper to build result + let build = |vuk: [u8; 16], key_source: u8| -> ResolvedKeys { + let unit_keys: Vec<(u32, [u8; 16])> = uk_file.encrypted_keys.iter() + .map(|(num, enc_key)| (*num, decrypt_unit_key(&vuk, enc_key))) + .collect(); + ResolvedKeys { + disc_hash: uk_file.disc_hash, + vuk, + unit_keys, + title_cps_unit: uk_file.title_cps_unit.clone(), + aacs2, + bus_encryption, + key_source, + } + }; + // Path 1: Look up VUK by disc hash in KEYDB if let Some(entry) = keydb.find_disc(&hash_hex) { if let Some(vuk) = entry.vuk { - // Decrypt unit keys with VUK - let unit_keys: Vec<(u32, [u8; 16])> = uk_file.encrypted_keys.iter() - .map(|(num, enc_key)| (*num, decrypt_unit_key(&vuk, enc_key))) - .collect(); - - return Some(ResolvedKeys { - disc_hash: uk_file.disc_hash, - vuk, - unit_keys, - title_cps_unit: uk_file.title_cps_unit, - aacs2, - bus_encryption, - }); + return Some(build(vuk, 1)); } } @@ -916,19 +922,7 @@ pub fn resolve_keys( for entry in keydb.disc_entries.values() { if let (Some(mk), Some(did)) = (entry.media_key, entry.disc_id) { if did == *volume_id { - let vuk = derive_vuk(&mk, volume_id); - let unit_keys: Vec<(u32, [u8; 16])> = uk_file.encrypted_keys.iter() - .map(|(num, enc_key)| (*num, decrypt_unit_key(&vuk, enc_key))) - .collect(); - - return Some(ResolvedKeys { - disc_hash: uk_file.disc_hash, - vuk, - unit_keys, - title_cps_unit: uk_file.title_cps_unit, - aacs2, - bus_encryption, - }); + return Some(build(derive_vuk(&mk, volume_id), 2)); } } } @@ -936,36 +930,12 @@ pub fn resolve_keys( // Path 3: MKB + processing keys → media key → VUK if let Some(mkb) = mkb_data { if let Some(mk) = derive_media_key_from_pk(mkb, &keydb.processing_keys) { - let vuk = derive_vuk(&mk, volume_id); - let unit_keys: Vec<(u32, [u8; 16])> = uk_file.encrypted_keys.iter() - .map(|(num, enc_key)| (*num, decrypt_unit_key(&vuk, enc_key))) - .collect(); - - return Some(ResolvedKeys { - disc_hash: uk_file.disc_hash, - vuk, - unit_keys, - title_cps_unit: uk_file.title_cps_unit, - aacs2, - bus_encryption, - }); + return Some(build(derive_vuk(&mk, volume_id), 3)); } // Path 4: MKB + device keys → processing key → media key → VUK if let Some(mk) = derive_media_key_from_dk(mkb, &keydb.device_keys) { - let vuk = derive_vuk(&mk, volume_id); - let unit_keys: Vec<(u32, [u8; 16])> = uk_file.encrypted_keys.iter() - .map(|(num, enc_key)| (*num, decrypt_unit_key(&vuk, enc_key))) - .collect(); - - return Some(ResolvedKeys { - disc_hash: uk_file.disc_hash, - vuk, - unit_keys, - title_cps_unit: uk_file.title_cps_unit, - aacs2, - bus_encryption, - }); + return Some(build(derive_vuk(&mk, volume_id), 4)); } } diff --git a/src/disc.rs b/src/disc.rs index 86ef1f9..23f538d 100644 --- a/src/disc.rs +++ b/src/disc.rs @@ -256,14 +256,48 @@ impl Stream { /// AACS decryption state for a disc. #[derive(Debug)] pub struct AacsState { - /// Volume Unique Key - pub vuk: [u8; 16], - /// Decrypted unit keys indexed by CPS unit number - pub unit_keys: Vec<(u32, [u8; 16])>, - /// Read data key (AACS 2.0 bus decryption) — None for AACS 1.0 - pub read_data_key: Option<[u8; 16]>, - /// Whether bus encryption is enabled + /// AACS version (1 or 2) + pub version: u8, + /// Whether bus encryption is enabled (always true for AACS 2.0 / UHD) pub bus_encryption: bool, + /// MKB version from disc (e.g. 68, 77) + pub mkb_version: Option, + /// Disc hash (SHA1 of Unit_Key_RO.inf) — hex string with 0x prefix + pub disc_hash: String, + /// How keys were resolved + pub key_source: KeySource, + /// Volume Unique Key (16 bytes) + pub vuk: [u8; 16], + /// Decrypted unit keys (CPS unit number, key) + pub unit_keys: Vec<(u32, [u8; 16])>, + /// Read data key for AACS 2.0 bus decryption — None for AACS 1.0 + pub read_data_key: Option<[u8; 16]>, + /// Volume ID (16 bytes) — from SCSI handshake + pub volume_id: [u8; 16], +} + +/// How AACS keys were resolved. +#[derive(Debug, Clone, Copy, PartialEq)] +pub enum KeySource { + /// VUK found directly in KEYDB by disc hash + KeyDb, + /// Media key + Volume ID from KEYDB → derived VUK + KeyDbDerived, + /// MKB + processing keys → media key → VUK + ProcessingKey, + /// MKB + device keys → subset-difference tree → VUK + DeviceKey, +} + +impl KeySource { + pub fn name(&self) -> &'static str { + match self { + KeySource::KeyDb => "KEYDB", + KeySource::KeyDbDerived => "KEYDB (derived)", + KeySource::ProcessingKey => "MKB + processing key", + KeySource::DeviceKey => "MKB + device key", + } + } } // ─── Disc scanning ────────────────────────────────────────────────────────── @@ -428,21 +462,38 @@ impl Disc { // Path 1: disc hash → KEYDB → VUK (fast, 99% of discs) // Path 2: KEYDB media key + VID → VUK // Path 3: MKB + processing keys → media key → VUK (fallback) + // Read MKB from drive + let mkb_data = aacs::read_mkb_from_drive(session).ok(); + let mkb_ver = mkb_data.as_deref().and_then(aacs::mkb_version); + let resolved = aacs::resolve_keys( &uk_ro_data, cc_data.as_deref(), &vid, &keydb, - aacs::read_mkb_from_drive(session).ok().as_deref(), + mkb_data.as_deref(), ).ok_or_else(|| Error::AacsError { detail: "failed to resolve AACS keys".into(), })?; + let key_source = match resolved.key_source { + 1 => KeySource::KeyDb, + 2 => KeySource::KeyDbDerived, + 3 => KeySource::ProcessingKey, + 4 => KeySource::DeviceKey, + _ => KeySource::KeyDb, + }; + Ok(AacsState { + version: if resolved.aacs2 { 2 } else { 1 }, + bus_encryption: resolved.bus_encryption, + mkb_version: mkb_ver, + disc_hash: aacs::disc_hash_hex(&resolved.disc_hash), + key_source, vuk: resolved.vuk, unit_keys: resolved.unit_keys, read_data_key, - bus_encryption: resolved.bus_encryption, + volume_id: vid, }) }