Expose full AACS state: version, key source, disc hash, all keys public
- AacsState now exposes: version (1/2), bus_encryption, mkb_version, disc_hash, key_source (KeyDb/KeyDbDerived/ProcessingKey/DeviceKey), vuk, unit_keys, read_data_key, volume_id - All keys are public — apps that contribute to KEYDB ecosystem can read VUK, unit keys, Volume ID, disc hash for database submission - resolve_keys reports which path succeeded (key_source 1-4) - Cleaned up resolve_keys with shared builder to reduce duplication
This commit is contained in:
+17
-47
@@ -858,6 +858,8 @@ pub struct ResolvedKeys {
|
|||||||
pub aacs2: bool,
|
pub aacs2: bool,
|
||||||
/// Whether bus encryption is enabled (from Content Certificate)
|
/// Whether bus encryption is enabled (from Content Certificate)
|
||||||
pub bus_encryption: bool,
|
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:
|
/// 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);
|
let hash_hex = disc_hash_hex(&uk_file.disc_hash);
|
||||||
|
|
||||||
// Path 1: Look up VUK by disc hash in KEYDB
|
// Helper to build result
|
||||||
if let Some(entry) = keydb.find_disc(&hash_hex) {
|
let build = |vuk: [u8; 16], key_source: u8| -> ResolvedKeys {
|
||||||
if let Some(vuk) = entry.vuk {
|
|
||||||
// Decrypt unit keys with VUK
|
|
||||||
let unit_keys: Vec<(u32, [u8; 16])> = uk_file.encrypted_keys.iter()
|
let unit_keys: Vec<(u32, [u8; 16])> = uk_file.encrypted_keys.iter()
|
||||||
.map(|(num, enc_key)| (*num, decrypt_unit_key(&vuk, enc_key)))
|
.map(|(num, enc_key)| (*num, decrypt_unit_key(&vuk, enc_key)))
|
||||||
.collect();
|
.collect();
|
||||||
|
ResolvedKeys {
|
||||||
return Some(ResolvedKeys {
|
|
||||||
disc_hash: uk_file.disc_hash,
|
disc_hash: uk_file.disc_hash,
|
||||||
vuk,
|
vuk,
|
||||||
unit_keys,
|
unit_keys,
|
||||||
title_cps_unit: uk_file.title_cps_unit,
|
title_cps_unit: uk_file.title_cps_unit.clone(),
|
||||||
aacs2,
|
aacs2,
|
||||||
bus_encryption,
|
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 {
|
||||||
|
return Some(build(vuk, 1));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -916,19 +922,7 @@ pub fn resolve_keys(
|
|||||||
for entry in keydb.disc_entries.values() {
|
for entry in keydb.disc_entries.values() {
|
||||||
if let (Some(mk), Some(did)) = (entry.media_key, entry.disc_id) {
|
if let (Some(mk), Some(did)) = (entry.media_key, entry.disc_id) {
|
||||||
if did == *volume_id {
|
if did == *volume_id {
|
||||||
let vuk = derive_vuk(&mk, volume_id);
|
return Some(build(derive_vuk(&mk, volume_id), 2));
|
||||||
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,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -936,36 +930,12 @@ pub fn resolve_keys(
|
|||||||
// Path 3: MKB + processing keys → media key → VUK
|
// Path 3: MKB + processing keys → media key → VUK
|
||||||
if let Some(mkb) = mkb_data {
|
if let Some(mkb) = mkb_data {
|
||||||
if let Some(mk) = derive_media_key_from_pk(mkb, &keydb.processing_keys) {
|
if let Some(mk) = derive_media_key_from_pk(mkb, &keydb.processing_keys) {
|
||||||
let vuk = derive_vuk(&mk, volume_id);
|
return Some(build(derive_vuk(&mk, volume_id), 3));
|
||||||
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,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Path 4: MKB + device keys → processing key → media key → VUK
|
// Path 4: MKB + device keys → processing key → media key → VUK
|
||||||
if let Some(mk) = derive_media_key_from_dk(mkb, &keydb.device_keys) {
|
if let Some(mk) = derive_media_key_from_dk(mkb, &keydb.device_keys) {
|
||||||
let vuk = derive_vuk(&mk, volume_id);
|
return Some(build(derive_vuk(&mk, volume_id), 4));
|
||||||
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,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+60
-9
@@ -256,14 +256,48 @@ impl Stream {
|
|||||||
/// AACS decryption state for a disc.
|
/// AACS decryption state for a disc.
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct AacsState {
|
pub struct AacsState {
|
||||||
/// Volume Unique Key
|
/// AACS version (1 or 2)
|
||||||
pub vuk: [u8; 16],
|
pub version: u8,
|
||||||
/// Decrypted unit keys indexed by CPS unit number
|
/// Whether bus encryption is enabled (always true for AACS 2.0 / UHD)
|
||||||
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
|
|
||||||
pub bus_encryption: bool,
|
pub bus_encryption: bool,
|
||||||
|
/// MKB version from disc (e.g. 68, 77)
|
||||||
|
pub mkb_version: Option<u32>,
|
||||||
|
/// 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 ──────────────────────────────────────────────────────────
|
// ─── Disc scanning ──────────────────────────────────────────────────────────
|
||||||
@@ -428,21 +462,38 @@ impl Disc {
|
|||||||
// Path 1: disc hash → KEYDB → VUK (fast, 99% of discs)
|
// Path 1: disc hash → KEYDB → VUK (fast, 99% of discs)
|
||||||
// Path 2: KEYDB media key + VID → VUK
|
// Path 2: KEYDB media key + VID → VUK
|
||||||
// Path 3: MKB + processing keys → media key → VUK (fallback)
|
// 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(
|
let resolved = aacs::resolve_keys(
|
||||||
&uk_ro_data,
|
&uk_ro_data,
|
||||||
cc_data.as_deref(),
|
cc_data.as_deref(),
|
||||||
&vid,
|
&vid,
|
||||||
&keydb,
|
&keydb,
|
||||||
aacs::read_mkb_from_drive(session).ok().as_deref(),
|
mkb_data.as_deref(),
|
||||||
).ok_or_else(|| Error::AacsError {
|
).ok_or_else(|| Error::AacsError {
|
||||||
detail: "failed to resolve AACS keys".into(),
|
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 {
|
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,
|
vuk: resolved.vuk,
|
||||||
unit_keys: resolved.unit_keys,
|
unit_keys: resolved.unit_keys,
|
||||||
read_data_key,
|
read_data_key,
|
||||||
bus_encryption: resolved.bus_encryption,
|
volume_id: vid,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user