aacs: merge media_key + volume_key into derive.rs
The volume_key module was only 34 lines and is just the tail of the same DK/PK -> MK -> VUK -> UK derivation ladder as media_key. Fold both into one derive module so every aacs module is a substantial, distinct responsibility (crypto/mkb/derive/inf/content/variant/resolve). Relocation only; logic hash identical (95fb9924); 2210 tests green.
This commit is contained in:
+5
-5
@@ -26,9 +26,9 @@
|
|||||||
//! the processing-key path starts from a precomputed PK. Neither needs a VID
|
//! the processing-key path starts from a precomputed PK. Neither needs a VID
|
||||||
//! (the VID enters at `vuk_from_mk`).
|
//! (the VID enters at `vuk_from_mk`).
|
||||||
|
|
||||||
use super::media_key::{derive_media_key_and_pk_from_dk, derive_media_key_from_pk};
|
use super::derive::{decrypt_unit_key, derive_vuk};
|
||||||
|
use super::derive::{derive_media_key_and_pk_from_dk, derive_media_key_from_pk};
|
||||||
use super::types::DeviceKey;
|
use super::types::DeviceKey;
|
||||||
use super::volume_key::{decrypt_unit_key, derive_vuk};
|
|
||||||
|
|
||||||
/// Volume ID (16 bytes) — read from the disc via the SCSI handshake / OEM path.
|
/// Volume ID (16 bytes) — read from the disc via the SCSI handshake / OEM path.
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
@@ -203,8 +203,8 @@ pub fn resolve_candidate(
|
|||||||
unit_key_ro: &[u8],
|
unit_key_ro: &[u8],
|
||||||
vid: Option<Vid>,
|
vid: Option<Vid>,
|
||||||
) -> Option<ResolvedChain> {
|
) -> Option<ResolvedChain> {
|
||||||
|
use super::derive::derive_media_key_and_pk_from_dk;
|
||||||
use super::inf::parse_unit_key_ro;
|
use super::inf::parse_unit_key_ro;
|
||||||
use super::media_key::derive_media_key_and_pk_from_dk;
|
|
||||||
use super::mkb::{AacsVersion, mkb_type};
|
use super::mkb::{AacsVersion, mkb_type};
|
||||||
|
|
||||||
// Boil a VUK → all unit keys, each paired with its declared CPS-unit number.
|
// Boil a VUK → all unit keys, each paired with its declared CPS-unit number.
|
||||||
@@ -219,7 +219,7 @@ pub fn resolve_candidate(
|
|||||||
if ukf.encrypted_keys.is_empty() {
|
if ukf.encrypted_keys.is_empty() {
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
Some(super::volume_key::derive_unit_keys(&ukf, &vuk.0))
|
Some(super::derive::derive_unit_keys(&ukf, &vuk.0))
|
||||||
};
|
};
|
||||||
|
|
||||||
match candidate {
|
match candidate {
|
||||||
@@ -277,7 +277,7 @@ pub fn resolve_candidate(
|
|||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::aacs::crypto::aes_ecb_encrypt;
|
use crate::aacs::crypto::aes_ecb_encrypt;
|
||||||
use crate::aacs::volume_key::{decrypt_unit_key, derive_vuk};
|
use crate::aacs::derive::{decrypt_unit_key, derive_vuk};
|
||||||
|
|
||||||
/// `vuk_from_mk` must equal the inline `derive_vuk` path bit-for-bit, for
|
/// `vuk_from_mk` must equal the inline `derive_vuk` path bit-for-bit, for
|
||||||
/// several known (MK, VID) vectors.
|
/// several known (MK, VID) vectors.
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
//! [C] §3.2.2–§3.2.5.
|
//! [C] §3.2.2–§3.2.5.
|
||||||
|
|
||||||
use super::crypto::*;
|
use super::crypto::*;
|
||||||
|
use super::inf::*;
|
||||||
use super::mkb::*;
|
use super::mkb::*;
|
||||||
use super::types::*;
|
use super::types::*;
|
||||||
|
|
||||||
@@ -409,3 +410,34 @@ pub mod probe {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ── Volume key: Media Key + Volume ID → VUK → unit keys ──────────────────────
|
||||||
|
|
||||||
|
/// Derive VUK from Media Key and Volume ID. [PR] §3.3 / [BD] §3.3
|
||||||
|
/// (`Kvu = AES-G(Km, IDv)`; AES-G uses AES-128D):
|
||||||
|
/// VUK = AES-128-ECB-DECRYPT(media_key, volume_id) XOR volume_id
|
||||||
|
pub fn derive_vuk(media_key: &[u8; 16], volume_id: &[u8; 16]) -> [u8; 16] {
|
||||||
|
let mut vuk = aes_ecb_decrypt(media_key, volume_id);
|
||||||
|
for i in 0..16 {
|
||||||
|
vuk[i] ^= volume_id[i];
|
||||||
|
}
|
||||||
|
vuk
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Decrypt an encrypted unit key using the VUK (AES-128-ECB). [PR] §3.5
|
||||||
|
/// (Title Key unwrap `Kt = AES-128D(Ku, Kte)`); the BD "CPS Unit Key" synonym is [BD] §3.9.3.
|
||||||
|
pub fn decrypt_unit_key(vuk: &[u8; 16], encrypted_uk: &[u8; 16]) -> [u8; 16] {
|
||||||
|
aes_ecb_decrypt(vuk, encrypted_uk)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Decrypt every encrypted unit key in a parsed `Unit_Key_RO.inf` with a VUK,
|
||||||
|
/// paired with its declared CPS-unit number. THE single VUK→unit-keys step:
|
||||||
|
/// both classical/v21 resolvers and `boil::resolve_candidate` call this, so the
|
||||||
|
/// map cannot drift between the player and harvest paths.
|
||||||
|
pub(crate) fn derive_unit_keys(uk_file: &UnitKeyFile, vuk: &[u8; 16]) -> Vec<(u32, [u8; 16])> {
|
||||||
|
uk_file
|
||||||
|
.encrypted_keys
|
||||||
|
.iter()
|
||||||
|
.map(|(num, enc_key)| (*num, decrypt_unit_key(vuk, enc_key)))
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
+8
-9
@@ -28,16 +28,15 @@
|
|||||||
pub mod boil;
|
pub mod boil;
|
||||||
pub mod content;
|
pub mod content;
|
||||||
pub mod crypto;
|
pub mod crypto;
|
||||||
|
pub mod derive;
|
||||||
pub mod host_certs;
|
pub mod host_certs;
|
||||||
pub mod inf;
|
pub mod inf;
|
||||||
pub mod media_key;
|
|
||||||
pub mod mkb;
|
pub mod mkb;
|
||||||
pub mod provider;
|
pub mod provider;
|
||||||
pub mod resolve;
|
pub mod resolve;
|
||||||
pub mod trace;
|
pub mod trace;
|
||||||
pub mod types;
|
pub mod types;
|
||||||
pub mod variant;
|
pub mod variant;
|
||||||
pub mod volume_key;
|
|
||||||
|
|
||||||
/// On-disc UDF paths to the AACS key-input files (with their fallbacks).
|
/// On-disc UDF paths to the AACS key-input files (with their fallbacks).
|
||||||
/// Centralised so every reader (`resolve_vid_only`, `read_aacs_inputs`,
|
/// Centralised so every reader (`resolve_vid_only`, `read_aacs_inputs`,
|
||||||
@@ -71,16 +70,17 @@ pub use content::{
|
|||||||
// `probe` is a reproduction-harness helper (see keys.rs), not part of the
|
// `probe` is a reproduction-harness helper (see keys.rs), not part of the
|
||||||
// documented 1.0 surface; keep it reachable but off the rendered docs so we
|
// documented 1.0 surface; keep it reachable but off the rendered docs so we
|
||||||
// don't commit semver stability to test primitives.
|
// don't commit semver stability to test primitives.
|
||||||
|
#[doc(hidden)]
|
||||||
|
pub use derive::probe;
|
||||||
|
pub use derive::{decrypt_unit_key, derive_vuk};
|
||||||
|
pub use derive::{
|
||||||
|
derive_media_key_and_pk_from_dk, derive_media_key_from_dk, derive_media_key_from_pk,
|
||||||
|
recover_dk_position,
|
||||||
|
};
|
||||||
pub use inf::{
|
pub use inf::{
|
||||||
ContentCert, UnitKeyFile, disc_hash, disc_hash_hex, parse_content_cert, parse_unit_key_ro,
|
ContentCert, UnitKeyFile, disc_hash, disc_hash_hex, parse_content_cert, parse_unit_key_ro,
|
||||||
read_mkb_from_drive,
|
read_mkb_from_drive,
|
||||||
};
|
};
|
||||||
#[doc(hidden)]
|
|
||||||
pub use media_key::probe;
|
|
||||||
pub use media_key::{
|
|
||||||
derive_media_key_and_pk_from_dk, derive_media_key_from_dk, derive_media_key_from_pk,
|
|
||||||
recover_dk_position,
|
|
||||||
};
|
|
||||||
pub use mkb::{
|
pub use mkb::{
|
||||||
AACS_MAJOR_BD, AACS_MAJOR_UHD, AacsVersion, MKB_20_CATEGORY_C, MKB_21_CATEGORY_C,
|
AACS_MAJOR_BD, AACS_MAJOR_UHD, AacsVersion, MKB_20_CATEGORY_C, MKB_21_CATEGORY_C,
|
||||||
MKB_TYPE_3_RECORDABLE, MKB_TYPE_4_PRERECORDED, MKB_TYPE_10_CLASS_II, MkbRecord, MkbType,
|
MKB_TYPE_3_RECORDABLE, MKB_TYPE_4_PRERECORDED, MKB_TYPE_10_CLASS_II, MkbRecord, MkbType,
|
||||||
@@ -96,7 +96,6 @@ pub use variant::{
|
|||||||
KEY_CORRECTION_DATA_PLACEHOLDER, MediaKeyVariantError, ProcessingKeyMatch,
|
KEY_CORRECTION_DATA_PLACEHOLDER, MediaKeyVariantError, ProcessingKeyMatch,
|
||||||
derive_media_key_variant, is_variant_mkb, variant_nonce, walk_processing_key,
|
derive_media_key_variant, is_variant_mkb, variant_nonce, walk_processing_key,
|
||||||
};
|
};
|
||||||
pub use volume_key::{decrypt_unit_key, derive_vuk};
|
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
|
|||||||
+2
-4
@@ -1,10 +1,9 @@
|
|||||||
//! AACS key resolution — VUK derivation, MKB processing, disc hash, unit key parsing.
|
//! AACS key resolution — VUK derivation, MKB processing, disc hash, unit key parsing.
|
||||||
|
|
||||||
use super::crypto::aes_ecb_decrypt;
|
use super::crypto::aes_ecb_decrypt;
|
||||||
|
use super::derive::*;
|
||||||
use super::inf::*;
|
use super::inf::*;
|
||||||
use super::media_key::*;
|
|
||||||
use super::mkb::*;
|
use super::mkb::*;
|
||||||
use super::volume_key::*;
|
|
||||||
|
|
||||||
//
|
//
|
||||||
// Canonical form is `<category>1003` (low 16 bits `0x1003` is a fixed marker).
|
// Canonical form is `<category>1003` (low 16 bits `0x1003` is a fixed marker).
|
||||||
@@ -457,13 +456,12 @@ mod tests {
|
|||||||
// This suite predates the module split; it white-box-tests items now living
|
// This suite predates the module split; it white-box-tests items now living
|
||||||
// in sibling modules. Pull them all in so the tests keep exercising them.
|
// in sibling modules. Pull them all in so the tests keep exercising them.
|
||||||
use super::super::crypto::*;
|
use super::super::crypto::*;
|
||||||
|
use super::super::derive::*;
|
||||||
use super::super::inf::*;
|
use super::super::inf::*;
|
||||||
use super::super::media_key::*;
|
|
||||||
use super::super::mkb::*;
|
use super::super::mkb::*;
|
||||||
use super::super::provider::SuppliedKey;
|
use super::super::provider::SuppliedKey;
|
||||||
use super::super::types::DiscEntry;
|
use super::super::types::DiscEntry;
|
||||||
use super::super::types::*;
|
use super::super::types::*;
|
||||||
use super::super::volume_key::*;
|
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
/// Audit #5: the `major` / `from_major` mapping is load-bearing for the
|
/// Audit #5: the `major` / `from_major` mapping is load-bearing for the
|
||||||
|
|||||||
+2
-2
@@ -151,7 +151,7 @@ pub(crate) fn variant_key_data(records: &[MkbRecord]) -> Option<&[u8]> {
|
|||||||
// on) are shared with the classical walk in [`super::keys`] — a single
|
// on) are shared with the classical walk in [`super::keys`] — a single
|
||||||
// definition keeps the variant SD tree byte-identical to the classical one.
|
// definition keeps the variant SD tree byte-identical to the classical one.
|
||||||
// (`aesg3` itself is imported separately in the test module.)
|
// (`aesg3` itself is imported separately in the test module.)
|
||||||
use super::media_key::{calc_pk_from_dk, calc_v_mask};
|
use super::derive::{calc_pk_from_dk, calc_v_mask};
|
||||||
|
|
||||||
/// Outcome of a subset-difference walk against an MKB. Carries the
|
/// Outcome of a subset-difference walk against an MKB. Carries the
|
||||||
/// processing key and the matching `uv` slot — both needed as inputs
|
/// processing key and the matching `uv` slot — both needed as inputs
|
||||||
@@ -483,7 +483,7 @@ mod tests {
|
|||||||
// `use super::*` does not re-export the parent module's private `use`
|
// `use super::*` does not re-export the parent module's private `use`
|
||||||
// imports, so pull them in directly for the tests below.
|
// imports, so pull them in directly for the tests below.
|
||||||
use super::super::crypto::aesg3;
|
use super::super::crypto::aesg3;
|
||||||
use super::super::media_key::calc_pk_from_dk;
|
use super::super::derive::calc_pk_from_dk;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn calc_pk_from_dk_terminates_on_nonconvergent_mask() {
|
fn calc_pk_from_dk_terminates_on_nonconvergent_mask() {
|
||||||
|
|||||||
@@ -1,34 +0,0 @@
|
|||||||
//! Volume-key layer: derive the Volume Unique Key from the Media Key + Volume
|
|
||||||
//! ID, unwrap unit keys with it. [PR] §3.3 / §3.5, [BD] §3.3 / §3.9.3.
|
|
||||||
|
|
||||||
use super::crypto::*;
|
|
||||||
use super::inf::*;
|
|
||||||
|
|
||||||
/// Derive VUK from Media Key and Volume ID. [PR] §3.3 / [BD] §3.3
|
|
||||||
/// (`Kvu = AES-G(Km, IDv)`; AES-G uses AES-128D):
|
|
||||||
/// VUK = AES-128-ECB-DECRYPT(media_key, volume_id) XOR volume_id
|
|
||||||
pub fn derive_vuk(media_key: &[u8; 16], volume_id: &[u8; 16]) -> [u8; 16] {
|
|
||||||
let mut vuk = aes_ecb_decrypt(media_key, volume_id);
|
|
||||||
for i in 0..16 {
|
|
||||||
vuk[i] ^= volume_id[i];
|
|
||||||
}
|
|
||||||
vuk
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Decrypt an encrypted unit key using the VUK (AES-128-ECB). [PR] §3.5
|
|
||||||
/// (Title Key unwrap `Kt = AES-128D(Ku, Kte)`); the BD "CPS Unit Key" synonym is [BD] §3.9.3.
|
|
||||||
pub fn decrypt_unit_key(vuk: &[u8; 16], encrypted_uk: &[u8; 16]) -> [u8; 16] {
|
|
||||||
aes_ecb_decrypt(vuk, encrypted_uk)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Decrypt every encrypted unit key in a parsed `Unit_Key_RO.inf` with a VUK,
|
|
||||||
/// paired with its declared CPS-unit number. THE single VUK→unit-keys step:
|
|
||||||
/// both classical/v21 resolvers and `boil::resolve_candidate` call this, so the
|
|
||||||
/// map cannot drift between the player and harvest paths.
|
|
||||||
pub(crate) fn derive_unit_keys(uk_file: &UnitKeyFile, vuk: &[u8; 16]) -> Vec<(u32, [u8; 16])> {
|
|
||||||
uk_file
|
|
||||||
.encrypted_keys
|
|
||||||
.iter()
|
|
||||||
.map(|(num, enc_key)| (*num, decrypt_unit_key(vuk, enc_key)))
|
|
||||||
.collect()
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user