- Rename DriveSession → Drive across entire codebase - find_drives() returns Vec<Drive>, find_drive() returns Option<Drive> - resolve_device() now pub(crate) — internal only - StreamUrl is now a typed enum (Disc, Mkv, M2ts, Iso, Network, Stdio, Null) with scheme() and path_str() accessors, replacing struct of Strings - Add lock_tray() / unlock_tray() for safe disc access during rips - Improve reset() with eject cycle that clears LibreDrive stuck state - Add Send bounds to ScsiTransport and PlatformDriver traits - DiscOptions uses PathBuf instead of String for device/keydb paths - Update doc example to use new Drive API
123 lines
4.4 KiB
Rust
123 lines
4.4 KiB
Rust
//! 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<HandshakeResult> {
|
|
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<AacsState> {
|
|
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,
|
|
})
|
|
}
|
|
}
|