diff --git a/src/disc/mod.rs b/src/disc/mod.rs index 7d9c7bf..881018c 100644 --- a/src/disc/mod.rs +++ b/src/disc/mod.rs @@ -1492,11 +1492,11 @@ impl Disc { /// AACS-chain steps it can from disc-read inputs (MKB / VID / `Unit_Key_RO.inf`). /// /// `#[non_exhaustive]`: AACS is a derivation chain -/// (`DK →(MKB)→ MK →(VID)→ VK →(Unit_Key_RO)→ UK`). Today only the final -/// [`Key::Unit`] entry point is wired; the higher entry points -/// (device / media / volume key) land as the derivation is lifted out of the -/// keydb-coupled scan path. Adding them is non-breaking thanks to -/// `#[non_exhaustive]`. +/// (`DK →(MKB)→ MK →(VID)→ VK →(Unit_Key_RO)→ UK`). Each variant is an entry +/// point at one level of that chain; [`Disc::decrypt_with`] derives down from +/// it to the per-CPS unit keys. New levels can be added without breaking +/// callers. +#[derive(Debug, Clone)] #[non_exhaustive] pub enum Key { /// Device key(s) (AACS DK, positioned). libfreemkv walks the MKB @@ -1602,6 +1602,9 @@ impl Disc { volume_id: a.volume_id, mkb: a.mkb.clone(), unit_key_ro: a.uk_ro.clone(), + // Content samples need the disc reader, which scan does not retain; + // the caller fills these for sources that validate against ciphertext. + samples: Vec::new(), }) } diff --git a/src/keysource.rs b/src/keysource.rs index 28f0a6c..0b20c2e 100644 --- a/src/keysource.rs +++ b/src/keysource.rs @@ -29,21 +29,33 @@ pub struct DiscInputs { pub mkb: Vec, /// Raw `Unit_Key_RO.inf` bytes. Empty when not captured. pub unit_key_ro: Vec, + /// Encrypted on-disc content sample units (each a 6144-byte aligned unit), + /// for sources that validate a key server-side against real ciphertext + /// (e.g. an online key service). Empty for sources that don't need them + /// (a local keydb). Populated by the application — reading content requires + /// the disc reader, which the library's scan does not retain — so + /// [`crate::Disc::inputs`] leaves it empty for the caller to fill. + pub samples: Vec>, } -/// A key source: given a disc's [`DiscInputs`], look up a [`Key`]. +/// A key source: given a disc's [`DiscInputs`], offer candidate [`Key`]s. /// -/// Dumb by contract — a source queries its backing store and returns the raw -/// key at whatever level it has (device / processing / media / volume / unit). -/// It performs NO AACS derivation; `Disc::decrypt_with` derives down. That -/// keeps every derivation step in one place (the library) across AACS -/// 1.0 / 2.0 / 2.1 / 2.x. +/// Dumb by contract — a source queries its backing store and enumerates the raw +/// material it holds as candidate keys at whatever level it has (device / +/// processing / media / volume / unit). It performs NO AACS derivation and NO +/// validation; `Disc::decrypt_with` derives down, and the caller validates by +/// decrypting a sample. That keeps every derivation step in one place (the +/// library) across AACS 1.0 / 2.0 / 2.1 / 2.x. +/// +/// A source returns *multiple ordered candidates* because a single store can +/// hold material for several derivation paths (a keydb has a per-disc VUK *and* +/// a device-key pool *and* a media-key pool — the source can't know which +/// applies without the MKB walk, which is derivation). The caller tries the +/// candidates in order and keeps the first that decrypts (validate-before- +/// return). A source that resolves server-side (an online key service) or holds +/// a cached final key (the mapfile) simply returns one candidate. pub trait KeySource { - /// Look up a key for this disc. - /// - /// - `Ok(Some(key))` — a key was found; the caller hands it to - /// `Disc::decrypt_with`. - /// - `Ok(None)` — this source has nothing for the disc; try the next one. - /// - `Err(_)` — the source itself failed (I/O, network, parse). - fn resolve(&self, inputs: &DiscInputs) -> Result>; + /// Candidate keys for this disc, most-specific first. Empty = this source + /// has nothing; `Err(_)` = the source itself failed (I/O, network, parse). + fn resolve(&self, inputs: &DiscInputs) -> Result>; }