//! AACS encryption resolution — key derivation, SCSI handshake, VUK lookup. use super::*; use crate::error::{Error, Result}; use crate::sector::SectorReader; use crate::udf; /// Result of SCSI AACS handshake (ECDH authentication). /// Only available when scanning from a real drive, not ISO images. #[derive(Debug)] pub(super) struct HandshakeResult { pub volume_id: [u8; 16], pub read_data_key: Option<[u8; 16]>, } impl Disc { /// SCSI handshake result — volume ID and bus keys from ECDH authentication. /// Only available when scanning from a real drive (not ISO images). pub(super) fn do_handshake( session: &mut crate::drive::Drive, opts: &ScanOptions, ) -> Option { use crate::aacs::{self, KeyDb}; let keydb_path = opts.resolve_keydb()?; let keydb = KeyDb::load(&keydb_path).ok()?; let mut last_error = None; for hc in &keydb.host_certs { match aacs::handshake::aacs_authenticate(session, &hc.private_key, &hc.certificate) { Ok(mut auth) => { let volume_id = aacs::handshake::read_volume_id(session, &mut auth).unwrap_or([0u8; 16]); let read_data_key = aacs::handshake::read_data_keys(session, &mut auth) .ok() .map(|(rdk, _)| rdk); return Some(HandshakeResult { volume_id, read_data_key, }); } Err(e) => { // Try next host cert last_error = Some(e); continue; } } } last_error.map(|_e| HandshakeResult { volume_id: [0u8; 16], read_data_key: None, }) } /// Resolve disc encryption — AACS 1.0, AACS 2.0, CSS, or none. /// /// Reads AACS files from UDF (via SectorReader), resolves keys through /// whatever path works: KEYDB VUK lookup, media key derivation, processing /// keys, device keys. Uses handshake result (volume ID, bus key) if available. pub(super) fn resolve_encryption( udf_fs: &udf::UdfFs, reader: &mut dyn SectorReader, keydb_path: &std::path::Path, handshake: Option<&HandshakeResult>, ) -> Result { use crate::aacs::{self, KeyDb}; let keydb = KeyDb::load(keydb_path).map_err(|_| Error::KeydbLoad { path: keydb_path.display().to_string(), })?; // Read AACS files from disc/image via UDF let uk_ro_data = udf_fs .read_file(reader, "/AACS/Unit_Key_RO.inf") .or_else(|_| udf_fs.read_file(reader, "/AACS/DUPLICATE/Unit_Key_RO.inf")) .map_err(|_| Error::AacsNoKeys)?; let cc_data = udf_fs .read_file(reader, "/AACS/Content000.cer") .or_else(|_| udf_fs.read_file(reader, "/AACS/Content001.cer")) .ok(); let mkb_data = udf_fs .read_file(reader, "/AACS/MKB_RW.inf") .or_else(|_| udf_fs.read_file(reader, "/AACS/MKB_RO.inf")) .ok(); let mkb_ver = mkb_data.as_deref().and_then(aacs::mkb_version); // Use handshake volume ID if available, otherwise zeros // (KEYDB VUK lookup by disc hash works without volume ID) let volume_id = handshake.map(|h| h.volume_id).unwrap_or([0u8; 16]); let read_data_key = handshake.and_then(|h| h.read_data_key); // Resolve: tries all available paths — KEYDB VUK, media key, processing key, device key let resolved = aacs::resolve_keys( &uk_ro_data, cc_data.as_deref(), &volume_id, &keydb, mkb_data.as_deref(), ) .ok_or(Error::AacsNoKeys)?; 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: match resolved.key_source { 1 => KeySource::KeyDb, 2 => KeySource::KeyDbDerived, 3 => KeySource::ProcessingKey, 4 => KeySource::DeviceKey, _ => KeySource::KeyDb, }, vuk: resolved.vuk, unit_keys: resolved.unit_keys, read_data_key, volume_id, }) } }