1.1.1: unify AACS key-input path on Disc::inputs() + named constants
- read_aacs_inputs* now returns the AACS major version; DiscInputs carries it, and DiscInputsCtx parses Unit_Key_RO.inf at the disc's own stride (fixes the hardcoded-V20 read-time fetch for V10 discs). One source of truth, no version argument to drift. - Disc::inputs() is the single complete AACS-input source (inf/MKB/VID/hash/ version); the out-of-band duplicate readers go away. - Named constants for AACS file paths (aacs::PATH_*) and the AACS majors (aacs::AACS_MAJOR_*, AacsVersion::major/from_major) replace magic strings/ints. - push_ranges saturating (corrupt-disc panic guard).
This commit is contained in:
@@ -24,6 +24,30 @@
|
|||||||
MKB through the same bounded prefix-grow + trim reader as the out-of-band
|
MKB through the same bounded prefix-grow + trim reader as the out-of-band
|
||||||
path, so `Disc::inputs()` is the single complete source of AACS inputs — one
|
path, so `Disc::inputs()` is the single complete source of AACS inputs — one
|
||||||
reader for every caller.
|
reader for every caller.
|
||||||
|
- **Read-time key-fetch parses `Unit_Key_RO.inf` at the disc's own AACS stride.**
|
||||||
|
The on-demand fetch (for a CPS unit not sampled up front) hardcoded the V20
|
||||||
|
64-byte stride, so an AACS-1.0 (V10) disc whose key arrived as a VUK derived
|
||||||
|
the wrong unit keys. `DiscInputs` now carries the disc's `version`, and the
|
||||||
|
context parses at the matching stride — the disc is the single source of truth
|
||||||
|
for its own stride (no separate version argument to drift).
|
||||||
|
- **A dry key-fetch for one unit no longer blocks fetching a different unit.**
|
||||||
|
A global "fetch spent" latch meant that once the key service returned nothing
|
||||||
|
for one CPS unit's ciphertext, no further unit was ever asked — so a multi-CPS
|
||||||
|
disc could strand a unit whose key the service *would* have served. Replaced
|
||||||
|
with a per-unit "already-asked-dry" set (still bounded by the fetch budget).
|
||||||
|
- **`verify::push_ranges` uses saturating arithmetic** so a corrupt-disc LBA near
|
||||||
|
`u32::MAX` can't panic (matches `udf::merge_ranges`).
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
- **One reader, one `DiscInputs`.** `Disc::inputs()` is now the single, complete
|
||||||
|
source of a disc's AACS inputs (inf, MKB, VID, disc_hash, version), and
|
||||||
|
`read_aacs_inputs*` returns the version alongside inf+MKB. Both the CLI and
|
||||||
|
autorip resolve through `Disc::inputs()`; the duplicate out-of-band readers
|
||||||
|
(autorip's `key_files()`/`volume_id()`) and the stale mapfile-VID read are
|
||||||
|
removed. AACS file paths and the AACS major versions are now named constants
|
||||||
|
(`aacs::PATH_*`, `aacs::AACS_MAJOR_*`, `AacsVersion::major`/`from_major`) so a
|
||||||
|
fallback or stride change lives in exactly one place.
|
||||||
|
|
||||||
## [1.1.0]
|
## [1.1.0]
|
||||||
|
|
||||||
|
|||||||
@@ -24,6 +24,13 @@ pub enum AacsVersion {
|
|||||||
V21,
|
V21,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// AACS major version as the small integer threaded through the scan / key
|
||||||
|
/// paths (`AacsState.version`, `DiscInputs.version`, `DiscInputsCtx::new`):
|
||||||
|
/// 1 = AACS 1.0 (BD), 2 = AACS 2.x (UHD). Centralised so the bare `1`/`2` — and
|
||||||
|
/// the V10-vs-else stride choice it drives — lives in exactly one place.
|
||||||
|
pub const AACS_MAJOR_BD: u8 = 1;
|
||||||
|
pub const AACS_MAJOR_UHD: u8 = 2;
|
||||||
|
|
||||||
impl AacsVersion {
|
impl AacsVersion {
|
||||||
/// Stride (in bytes) between successive encrypted unit keys in
|
/// Stride (in bytes) between successive encrypted unit keys in
|
||||||
/// `Unit_Key_RO.inf`.
|
/// `Unit_Key_RO.inf`.
|
||||||
@@ -33,6 +40,24 @@ impl AacsVersion {
|
|||||||
AacsVersion::V20 | AacsVersion::V21 => 64,
|
AacsVersion::V20 | AacsVersion::V21 => 64,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// This version as the major integer ([`AACS_MAJOR_BD`] / [`AACS_MAJOR_UHD`]).
|
||||||
|
pub fn major(self) -> u8 {
|
||||||
|
match self {
|
||||||
|
AacsVersion::V10 => AACS_MAJOR_BD,
|
||||||
|
AacsVersion::V20 | AacsVersion::V21 => AACS_MAJOR_UHD,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The version a bare major integer selects for stride purposes: only the
|
||||||
|
/// BD major is V10; every other value takes the V20/V21 64-byte stride.
|
||||||
|
pub fn from_major(major: u8) -> Self {
|
||||||
|
if major == AACS_MAJOR_BD {
|
||||||
|
AacsVersion::V10
|
||||||
|
} else {
|
||||||
|
AacsVersion::V20
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── VUK derivation ──────────────────────────────────────────────────────────
|
// ── VUK derivation ──────────────────────────────────────────────────────────
|
||||||
|
|||||||
+15
-3
@@ -23,6 +23,18 @@ pub mod trace;
|
|||||||
pub mod types;
|
pub mod types;
|
||||||
pub mod variants;
|
pub mod variants;
|
||||||
|
|
||||||
|
/// On-disc UDF paths to the AACS key-input files (with their fallbacks).
|
||||||
|
/// Centralised so every reader (`resolve_vid_only`, `read_aacs_inputs`,
|
||||||
|
/// `read_mkb_content`, `read_aacs_version`) walks the exact same files — adding
|
||||||
|
/// or changing a fallback in one place can then never silently diverge the
|
||||||
|
/// disc_hash / MKB / VID that another reader feeds a key service.
|
||||||
|
pub const PATH_UNIT_KEY_RO: &str = "/AACS/Unit_Key_RO.inf";
|
||||||
|
pub const PATH_UNIT_KEY_RO_DUPLICATE: &str = "/AACS/DUPLICATE/Unit_Key_RO.inf";
|
||||||
|
pub const PATH_MKB_RO: &str = "/AACS/MKB_RO.inf";
|
||||||
|
pub const PATH_MKB_RW: &str = "/AACS/MKB_RW.inf";
|
||||||
|
pub const PATH_CONTENT_CERT: &str = "/AACS/Content000.cer";
|
||||||
|
pub const PATH_CONTENT_CERT_ALT: &str = "/AACS/Content001.cer";
|
||||||
|
|
||||||
// Boil-down derivation primitives (thin newtypes + wrappers over the crypto).
|
// Boil-down derivation primitives (thin newtypes + wrappers over the crypto).
|
||||||
pub use boil::{MediaKey, UnitKey, Vid, Vuk, mk_from_dk, mk_from_pk, uk_from_vuk, vuk_from_mk};
|
pub use boil::{MediaKey, UnitKey, Vid, Vuk, mk_from_dk, mk_from_pk, uk_from_vuk, vuk_from_mk};
|
||||||
// Structured, English-free resolution trace.
|
// Structured, English-free resolution trace.
|
||||||
@@ -42,9 +54,9 @@ pub use decrypt::{
|
|||||||
#[doc(hidden)]
|
#[doc(hidden)]
|
||||||
pub use keys::probe;
|
pub use keys::probe;
|
||||||
pub use keys::{
|
pub use keys::{
|
||||||
AacsVersion, ContentCert, MKB_20_CATEGORY_C, MKB_21_CATEGORY_C, MKB_TYPE_3_RECORDABLE,
|
AACS_MAJOR_BD, AACS_MAJOR_UHD, AacsVersion, ContentCert, MKB_20_CATEGORY_C, MKB_21_CATEGORY_C,
|
||||||
MKB_TYPE_4_PRERECORDED, MKB_TYPE_10_CLASS_II, MkbType, ResolveContext, ResolveFailure,
|
MKB_TYPE_3_RECORDABLE, MKB_TYPE_4_PRERECORDED, MKB_TYPE_10_CLASS_II, MkbType, ResolveContext,
|
||||||
ResolvedKeys, UnitKeyFile, decrypt_unit_key, derive_media_key_and_pk_from_dk,
|
ResolveFailure, ResolvedKeys, UnitKeyFile, decrypt_unit_key, derive_media_key_and_pk_from_dk,
|
||||||
derive_media_key_from_dk, derive_media_key_from_pk, derive_vuk, disc_hash, disc_hash_hex,
|
derive_media_key_from_dk, derive_media_key_from_pk, derive_vuk, disc_hash, disc_hash_hex,
|
||||||
mkb_content_len, mkb_is_uhd, mkb_type, mkb_type_raw, mkb_version, parse_content_cert,
|
mkb_content_len, mkb_is_uhd, mkb_type, mkb_type_raw, mkb_version, parse_content_cert,
|
||||||
parse_unit_key_ro, read_mkb_from_drive, recover_dk_position, resolve_keys_v1, resolve_keys_v2,
|
parse_unit_key_ro, read_mkb_from_drive, recover_dk_position, resolve_keys_v1, resolve_keys_v2,
|
||||||
|
|||||||
+8
-10
@@ -315,24 +315,22 @@ impl Disc {
|
|||||||
use crate::aacs;
|
use crate::aacs;
|
||||||
|
|
||||||
let uk_ro_data = udf_fs
|
let uk_ro_data = udf_fs
|
||||||
.read_file(reader, "/AACS/Unit_Key_RO.inf")
|
.read_file(reader, crate::aacs::PATH_UNIT_KEY_RO)
|
||||||
.or_else(|_| udf_fs.read_file(reader, "/AACS/DUPLICATE/Unit_Key_RO.inf"))
|
.or_else(|_| udf_fs.read_file(reader, crate::aacs::PATH_UNIT_KEY_RO_DUPLICATE))
|
||||||
.map_err(|_| Error::AacsNoKeys)?;
|
.map_err(|_| Error::AacsNoKeys)?;
|
||||||
let dh = aacs::disc_hash(&uk_ro_data);
|
let dh = aacs::disc_hash(&uk_ro_data);
|
||||||
|
|
||||||
let cc = udf_fs
|
let cc = udf_fs
|
||||||
.read_file(reader, "/AACS/Content000.cer")
|
.read_file(reader, crate::aacs::PATH_CONTENT_CERT)
|
||||||
.or_else(|_| udf_fs.read_file(reader, "/AACS/Content001.cer"))
|
.or_else(|_| udf_fs.read_file(reader, crate::aacs::PATH_CONTENT_CERT_ALT))
|
||||||
.ok()
|
.ok()
|
||||||
.as_deref()
|
.as_deref()
|
||||||
.and_then(aacs::parse_content_cert);
|
.and_then(aacs::parse_content_cert);
|
||||||
let bus_encryption = cc.as_ref().map(|c| c.bus_encryption).unwrap_or(false);
|
let bus_encryption = cc.as_ref().map(|c| c.bus_encryption).unwrap_or(false);
|
||||||
let version = match cc.as_ref().map(|c| c.version) {
|
let version = cc
|
||||||
Some(aacs::AacsVersion::V10) => 1,
|
.as_ref()
|
||||||
Some(_) => 2,
|
.map(|c| c.version.major())
|
||||||
None if bus_encryption => 2,
|
.unwrap_or(aacs::AACS_MAJOR_BD);
|
||||||
None => 1,
|
|
||||||
};
|
|
||||||
|
|
||||||
// OEM bus-key gate (wrong-keys guard). A bus-encrypted disc (Content
|
// OEM bus-key gate (wrong-keys guard). A bus-encrypted disc (Content
|
||||||
// Certificate bus-encryption bit set) still carries bus encryption on
|
// Certificate bus-encryption bit set) still carries bus encryption on
|
||||||
|
|||||||
+34
-11
@@ -1600,13 +1600,31 @@ impl Disc {
|
|||||||
pub(crate) fn read_aacs_inputs_from_reader(
|
pub(crate) fn read_aacs_inputs_from_reader(
|
||||||
reader: &mut dyn SectorSource,
|
reader: &mut dyn SectorSource,
|
||||||
udf_fs: &udf::UdfFs,
|
udf_fs: &udf::UdfFs,
|
||||||
) -> Result<(Vec<u8>, Vec<u8>)> {
|
) -> Result<(Vec<u8>, Vec<u8>, u8)> {
|
||||||
let inf = udf_fs
|
let inf = udf_fs
|
||||||
.read_file(reader, "/AACS/Unit_Key_RO.inf")
|
.read_file(reader, crate::aacs::PATH_UNIT_KEY_RO)
|
||||||
.or_else(|_| udf_fs.read_file(reader, "/AACS/DUPLICATE/Unit_Key_RO.inf"))
|
.or_else(|_| udf_fs.read_file(reader, crate::aacs::PATH_UNIT_KEY_RO_DUPLICATE))
|
||||||
.map_err(|_| Error::AacsNoKeys)?;
|
.map_err(|_| Error::AacsNoKeys)?;
|
||||||
let mkb = Self::read_mkb_content(reader, udf_fs)?;
|
let mkb = Self::read_mkb_content(reader, udf_fs)?;
|
||||||
Ok((inf, mkb))
|
let version = Self::read_aacs_version(reader, udf_fs);
|
||||||
|
Ok((inf, mkb, version))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// AACS major version ([`crate::aacs::AACS_MAJOR_BD`] /
|
||||||
|
/// [`crate::aacs::AACS_MAJOR_UHD`]) from the content certificate. Drives the
|
||||||
|
/// `Unit_Key_RO.inf` parse stride (48-byte V10 vs 64-byte V20/V21), so the
|
||||||
|
/// out-of-band key-fetch path parses `enc_title_keys` at the right stride (a
|
||||||
|
/// server VUK then derives the correct unit keys). Defaults to BD (V10) when
|
||||||
|
/// no content certificate is present.
|
||||||
|
fn read_aacs_version(reader: &mut dyn SectorSource, udf_fs: &udf::UdfFs) -> u8 {
|
||||||
|
udf_fs
|
||||||
|
.read_file(reader, crate::aacs::PATH_CONTENT_CERT)
|
||||||
|
.or_else(|_| udf_fs.read_file(reader, crate::aacs::PATH_CONTENT_CERT_ALT))
|
||||||
|
.ok()
|
||||||
|
.as_deref()
|
||||||
|
.and_then(crate::aacs::parse_content_cert)
|
||||||
|
.map(|c| c.version.major())
|
||||||
|
.unwrap_or(crate::aacs::AACS_MAJOR_BD)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Read the AACS MKB's real record stream — NOT its zero padding.
|
/// Read the AACS MKB's real record stream — NOT its zero padding.
|
||||||
@@ -1626,8 +1644,8 @@ impl Disc {
|
|||||||
let mut want = START_BYTES;
|
let mut want = START_BYTES;
|
||||||
loop {
|
loop {
|
||||||
let buf = udf_fs
|
let buf = udf_fs
|
||||||
.read_file_prefix(reader, "/AACS/MKB_RO.inf", want)
|
.read_file_prefix(reader, crate::aacs::PATH_MKB_RO, want)
|
||||||
.or_else(|_| udf_fs.read_file_prefix(reader, "/AACS/MKB_RW.inf", want))
|
.or_else(|_| udf_fs.read_file_prefix(reader, crate::aacs::PATH_MKB_RW, want))
|
||||||
.map_err(|_| Error::AacsNoKeys)?;
|
.map_err(|_| Error::AacsNoKeys)?;
|
||||||
let n = crate::aacs::mkb_content_len(&buf);
|
let n = crate::aacs::mkb_content_len(&buf);
|
||||||
// `n` strictly inside `buf` => the record walk reached the padding
|
// `n` strictly inside `buf` => the record walk reached the padding
|
||||||
@@ -1642,10 +1660,10 @@ impl Disc {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Read a disc's AACS key-input files from an ISO image: returns
|
/// Read a disc's AACS key-input files from an ISO image: returns
|
||||||
/// `(Unit_Key_RO.inf, MKB)` raw bytes. For callers that resolve a Unit Key
|
/// `(Unit_Key_RO.inf, MKB, aacs_major_version)`. For callers that resolve a
|
||||||
/// out-of-band: obtain the key however you like, then apply it via
|
/// Unit Key out-of-band: obtain the key however you like, then apply it via
|
||||||
/// [`Disc::decrypt_with`]. libfreemkv never makes a network call.
|
/// [`Disc::decrypt_with`]. libfreemkv never makes a network call.
|
||||||
pub fn read_aacs_inputs(iso_path: &std::path::Path) -> Result<(Vec<u8>, Vec<u8>)> {
|
pub fn read_aacs_inputs(iso_path: &std::path::Path) -> Result<(Vec<u8>, Vec<u8>, u8)> {
|
||||||
// Preserve the underlying open error (`Error::IoError`, E5000, carrying
|
// Preserve the underlying open error (`Error::IoError`, E5000, carrying
|
||||||
// the OS errno) instead of collapsing ENOENT/EPERM/etc. into
|
// the OS errno) instead of collapsing ENOENT/EPERM/etc. into
|
||||||
// `Error::AacsNoKeys` (E7000). A missing or unreadable ISO is an I/O
|
// `Error::AacsNoKeys` (E7000). A missing or unreadable ISO is an I/O
|
||||||
@@ -1661,7 +1679,7 @@ impl Disc {
|
|||||||
/// resolves a key from them however it likes, then applies it via
|
/// resolves a key from them however it likes, then applies it via
|
||||||
/// [`Disc::decrypt_with`]. These files are plaintext UDF metadata — no
|
/// [`Disc::decrypt_with`]. These files are plaintext UDF metadata — no
|
||||||
/// AACS handshake or keys are required to read them.
|
/// AACS handshake or keys are required to read them.
|
||||||
pub fn read_aacs_inputs_from_drive(drive: &mut Drive) -> Result<(Vec<u8>, Vec<u8>)> {
|
pub fn read_aacs_inputs_from_drive(drive: &mut Drive) -> Result<(Vec<u8>, Vec<u8>, u8)> {
|
||||||
let (_, mut reader, udf_fs) = Self::read_udf(drive)?;
|
let (_, mut reader, udf_fs) = Self::read_udf(drive)?;
|
||||||
Self::read_aacs_inputs_from_reader(&mut reader, &udf_fs)
|
Self::read_aacs_inputs_from_reader(&mut reader, &udf_fs)
|
||||||
}
|
}
|
||||||
@@ -2326,7 +2344,11 @@ impl Disc {
|
|||||||
aacs.key_source = KeyOrigin::ExternalUk;
|
aacs.key_source = KeyOrigin::ExternalUk;
|
||||||
} else if self.encrypted && self.css.is_none() {
|
} else if self.encrypted && self.css.is_none() {
|
||||||
self.aacs = Some(AacsState {
|
self.aacs = Some(AacsState {
|
||||||
version: if self.format == DiscFormat::Uhd { 2 } else { 1 },
|
version: if self.format == DiscFormat::Uhd {
|
||||||
|
crate::aacs::AACS_MAJOR_UHD
|
||||||
|
} else {
|
||||||
|
crate::aacs::AACS_MAJOR_BD
|
||||||
|
},
|
||||||
bus_encryption: self.format == DiscFormat::Uhd,
|
bus_encryption: self.format == DiscFormat::Uhd,
|
||||||
mkb_version: None,
|
mkb_version: None,
|
||||||
disc_hash: String::new(),
|
disc_hash: String::new(),
|
||||||
@@ -2353,6 +2375,7 @@ impl Disc {
|
|||||||
self.aacs.as_ref().map(|a| crate::keysource::DiscInputs {
|
self.aacs.as_ref().map(|a| crate::keysource::DiscInputs {
|
||||||
disc_hash: a.disc_hash.clone(),
|
disc_hash: a.disc_hash.clone(),
|
||||||
volume_id: a.volume_id,
|
volume_id: a.volume_id,
|
||||||
|
version: a.version,
|
||||||
mkb: a.mkb.clone(),
|
mkb: a.mkb.clone(),
|
||||||
unit_key_ro: a.uk_ro.clone(),
|
unit_key_ro: a.uk_ro.clone(),
|
||||||
// Content samples need the disc reader, which scan does not retain;
|
// Content samples need the disc reader, which scan does not retain;
|
||||||
|
|||||||
+23
-19
@@ -32,6 +32,10 @@ pub struct DiscInputs {
|
|||||||
/// Volume ID (16 bytes). `[0u8; 16]` when no authenticated handshake ran
|
/// Volume ID (16 bytes). `[0u8; 16]` when no authenticated handshake ran
|
||||||
/// (e.g. an ISO/mapfile flow), which disables VID-keyed lookups.
|
/// (e.g. an ISO/mapfile flow), which disables VID-keyed lookups.
|
||||||
pub volume_id: [u8; 16],
|
pub volume_id: [u8; 16],
|
||||||
|
/// AACS major version (1 = V10 / BD AACS 1.0, 2 = V20+ / UHD). Drives the
|
||||||
|
/// `Unit_Key_RO.inf` parse stride (48-byte V10 vs 64-byte V20/V21) when a
|
||||||
|
/// source returns a VUK to derive unit keys from. Defaults to 2.
|
||||||
|
pub version: u8,
|
||||||
/// Raw MKB bytes. Empty when not captured.
|
/// Raw MKB bytes. Empty when not captured.
|
||||||
pub mkb: Vec<u8>,
|
pub mkb: Vec<u8>,
|
||||||
/// Raw `Unit_Key_RO.inf` bytes. Empty when not captured.
|
/// Raw `Unit_Key_RO.inf` bytes. Empty when not captured.
|
||||||
@@ -99,24 +103,21 @@ pub struct DiscInputsCtx<'a> {
|
|||||||
|
|
||||||
impl<'a> DiscInputsCtx<'a> {
|
impl<'a> DiscInputsCtx<'a> {
|
||||||
/// Build a context over `inputs`, parsing the encrypted title keys at the
|
/// Build a context over `inputs`, parsing the encrypted title keys at the
|
||||||
/// stride for AACS major `version_u8` (1 = V10, else V20/V21).
|
/// stride for the disc's own AACS major (`inputs.version`: 1 → 48-byte V10
|
||||||
|
/// stride, else 64-byte V20/V21) — the single source of truth, no separate
|
||||||
|
/// version argument to drift from it.
|
||||||
///
|
///
|
||||||
/// A present-but-malformed `unit_key_ro` (truncated / wrong magic / wrong
|
/// A present-but-malformed `unit_key_ro` (truncated / wrong magic / wrong
|
||||||
/// stride) parses to an empty key set, so a later [`Self::enc_title_keys`]
|
/// stride) parses to an empty key set, so a later [`Self::enc_title_keys`]
|
||||||
/// returns `Ok(&[])` indistinguishably from a disc that legitimately has no
|
/// returns `Ok(&[])` indistinguishably from a disc that legitimately has no
|
||||||
/// title keys — the parse failure is swallowed here, not surfaced as an
|
/// title keys — the parse failure is swallowed here, not surfaced as an
|
||||||
/// error.
|
/// error.
|
||||||
pub fn new(inputs: &'a DiscInputs, version_u8: u8) -> Self {
|
pub fn new(inputs: &'a DiscInputs) -> Self {
|
||||||
use crate::aacs::{AacsVersion, parse_unit_key_ro};
|
use crate::aacs::{AacsVersion, parse_unit_key_ro};
|
||||||
let enc_keys = if inputs.unit_key_ro.is_empty() {
|
let enc_keys = if inputs.unit_key_ro.is_empty() {
|
||||||
Vec::new()
|
Vec::new()
|
||||||
} else {
|
} else {
|
||||||
let version = if version_u8 == 1 {
|
parse_unit_key_ro(&inputs.unit_key_ro, AacsVersion::from_major(inputs.version))
|
||||||
AacsVersion::V10
|
|
||||||
} else {
|
|
||||||
AacsVersion::V20
|
|
||||||
};
|
|
||||||
parse_unit_key_ro(&inputs.unit_key_ro, version)
|
|
||||||
.map(|f| f.encrypted_keys.into_iter().map(|(_, k)| k).collect())
|
.map(|f| f.encrypted_keys.into_iter().map(|(_, k)| k).collect())
|
||||||
.unwrap_or_default()
|
.unwrap_or_default()
|
||||||
};
|
};
|
||||||
@@ -232,11 +233,9 @@ pub fn resolve_and_apply_traced(
|
|||||||
|
|
||||||
let mut trace = crate::aacs::ResolutionTrace::new();
|
let mut trace = crate::aacs::ResolutionTrace::new();
|
||||||
|
|
||||||
// AACS major drives the Unit_Key_RO.inf stride the ctx parses at. Default to
|
// The ctx parses Unit_Key_RO.inf at the stride for `inputs.version` (the
|
||||||
// the V20/V21 stride when there is no AACS state (it is the common live case;
|
// disc's own AACS major), so the stride is the disc's single source of truth.
|
||||||
// a non-AACS disc has nothing to resolve and the loop simply finds nothing).
|
let ctx = DiscInputsCtx::new(inputs);
|
||||||
let version_u8 = disc.aacs.as_ref().map(|a| a.version).unwrap_or(2);
|
|
||||||
let ctx = DiscInputsCtx::new(inputs, version_u8);
|
|
||||||
|
|
||||||
for source in sources {
|
for source in sources {
|
||||||
// `who` is the source's own stable identifier — no enum to map back to.
|
// `who` is the source's own stable identifier — no enum to map back to.
|
||||||
@@ -320,9 +319,11 @@ pub fn key_fetch(
|
|||||||
let sources = make_sources();
|
let sources = make_sources();
|
||||||
let mut di = inputs.clone();
|
let mut di = inputs.clone();
|
||||||
di.samples = samples.to_vec();
|
di.samples = samples.to_vec();
|
||||||
// V20/V21 stride (BD/UHD AACS 2.x); the online /decode UK path forwards
|
// Parse Unit_Key_RO.inf at the disc's OWN stride (carried on `inputs`):
|
||||||
// raw inf + samples and doesn't depend on the parsed title-key stride.
|
// an online /decode reply that returns a VUK (not a terminal UK) then
|
||||||
let ctx = DiscInputsCtx::new(&di, 2);
|
// derives unit keys from `enc_title_keys`, which a V10 disc parses at the
|
||||||
|
// 48-byte stride — hardcoding the V20 stride here corrupted them.
|
||||||
|
let ctx = DiscInputsCtx::new(&di);
|
||||||
fetch_unit_keys(&sources, &ctx)
|
fetch_unit_keys(&sources, &ctx)
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|u| u.key)
|
.map(|u| u.key)
|
||||||
@@ -443,6 +444,7 @@ mod tests {
|
|||||||
let inputs = DiscInputs {
|
let inputs = DiscInputs {
|
||||||
disc_hash: "0xABC".into(),
|
disc_hash: "0xABC".into(),
|
||||||
volume_id: [0u8; 16],
|
volume_id: [0u8; 16],
|
||||||
|
version: crate::aacs::AACS_MAJOR_BD,
|
||||||
mkb: vec![1, 2, 3],
|
mkb: vec![1, 2, 3],
|
||||||
unit_key_ro: uk_ro,
|
unit_key_ro: uk_ro,
|
||||||
samples: vec![vec![9u8; 4], vec![8u8; 4], vec![7u8; 4]],
|
samples: vec![vec![9u8; 4], vec![8u8; 4], vec![7u8; 4]],
|
||||||
@@ -450,7 +452,7 @@ mod tests {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// Zero VID → None.
|
// Zero VID → None.
|
||||||
let ctx = DiscInputsCtx::new(&inputs, 1);
|
let ctx = DiscInputsCtx::new(&inputs);
|
||||||
assert_eq!(ctx.disc_hash(), "0xABC");
|
assert_eq!(ctx.disc_hash(), "0xABC");
|
||||||
assert_eq!(ctx.title(), Some("TITLE_X"));
|
assert_eq!(ctx.title(), Some("TITLE_X"));
|
||||||
assert!(ctx.vid().is_none(), "all-zero VID is the no-VID sentinel");
|
assert!(ctx.vid().is_none(), "all-zero VID is the no-VID sentinel");
|
||||||
@@ -461,7 +463,7 @@ mod tests {
|
|||||||
// Non-zero VID → Some(vid).
|
// Non-zero VID → Some(vid).
|
||||||
let mut inputs2 = inputs.clone();
|
let mut inputs2 = inputs.clone();
|
||||||
inputs2.volume_id = [0x42u8; 16];
|
inputs2.volume_id = [0x42u8; 16];
|
||||||
let ctx2 = DiscInputsCtx::new(&inputs2, 1);
|
let ctx2 = DiscInputsCtx::new(&inputs2);
|
||||||
assert_eq!(ctx2.vid(), Some(Vid([0x42u8; 16])));
|
assert_eq!(ctx2.vid(), Some(Vid([0x42u8; 16])));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -498,6 +500,7 @@ mod tests {
|
|||||||
let inputs = DiscInputs {
|
let inputs = DiscInputs {
|
||||||
disc_hash: "0x00".into(),
|
disc_hash: "0x00".into(),
|
||||||
volume_id: [0u8; 16],
|
volume_id: [0u8; 16],
|
||||||
|
version: crate::aacs::AACS_MAJOR_UHD,
|
||||||
mkb: Vec::new(),
|
mkb: Vec::new(),
|
||||||
unit_key_ro: Vec::new(),
|
unit_key_ro: Vec::new(),
|
||||||
samples: Vec::new(),
|
samples: Vec::new(),
|
||||||
@@ -518,6 +521,7 @@ mod tests {
|
|||||||
DiscInputs {
|
DiscInputs {
|
||||||
disc_hash: String::new(),
|
disc_hash: String::new(),
|
||||||
volume_id: [0u8; 16],
|
volume_id: [0u8; 16],
|
||||||
|
version: crate::aacs::AACS_MAJOR_UHD,
|
||||||
mkb: Vec::new(),
|
mkb: Vec::new(),
|
||||||
unit_key_ro: Vec::new(),
|
unit_key_ro: Vec::new(),
|
||||||
samples: Vec::new(),
|
samples: Vec::new(),
|
||||||
@@ -552,7 +556,7 @@ mod tests {
|
|||||||
#[test]
|
#[test]
|
||||||
fn fetch_unit_keys_first_nonempty_skips_empty_and_errors() {
|
fn fetch_unit_keys_first_nonempty_skips_empty_and_errors() {
|
||||||
let inputs = empty_inputs();
|
let inputs = empty_inputs();
|
||||||
let ctx = DiscInputsCtx::new(&inputs, 2);
|
let ctx = DiscInputsCtx::new(&inputs);
|
||||||
let key = [0xABu8; 16];
|
let key = [0xABu8; 16];
|
||||||
|
|
||||||
let sources: Vec<Box<dyn KeySource>> = vec![
|
let sources: Vec<Box<dyn KeySource>> = vec![
|
||||||
|
|||||||
+3
-2
@@ -1771,8 +1771,9 @@ mod tests {
|
|||||||
reader.put(50, [0xCC; 2048]);
|
reader.put(50, [0xCC; 2048]);
|
||||||
|
|
||||||
let fs = fs_with(0, 0, root);
|
let fs = fs_with(0, 0, root);
|
||||||
let (inf, _mkb) = crate::disc::Disc::read_aacs_inputs_from_reader(&mut reader, &fs)
|
let (inf, _mkb, _version) =
|
||||||
.expect("read_aacs_inputs must succeed for a Long-AD disc");
|
crate::disc::Disc::read_aacs_inputs_from_reader(&mut reader, &fs)
|
||||||
|
.expect("read_aacs_inputs must succeed for a Long-AD disc");
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
inf.len(),
|
inf.len(),
|
||||||
4096,
|
4096,
|
||||||
|
|||||||
Reference in New Issue
Block a user