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 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::volume_key::{decrypt_unit_key, derive_vuk};
|
||||
|
||||
/// Volume ID (16 bytes) — read from the disc via the SCSI handshake / OEM path.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
@@ -203,8 +203,8 @@ pub fn resolve_candidate(
|
||||
unit_key_ro: &[u8],
|
||||
vid: Option<Vid>,
|
||||
) -> Option<ResolvedChain> {
|
||||
use super::derive::derive_media_key_and_pk_from_dk;
|
||||
use super::inf::parse_unit_key_ro;
|
||||
use super::media_key::derive_media_key_and_pk_from_dk;
|
||||
use super::mkb::{AacsVersion, mkb_type};
|
||||
|
||||
// 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() {
|
||||
return None;
|
||||
}
|
||||
Some(super::volume_key::derive_unit_keys(&ukf, &vuk.0))
|
||||
Some(super::derive::derive_unit_keys(&ukf, &vuk.0))
|
||||
};
|
||||
|
||||
match candidate {
|
||||
@@ -277,7 +277,7 @@ pub fn resolve_candidate(
|
||||
mod tests {
|
||||
use super::*;
|
||||
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
|
||||
/// several known (MK, VID) vectors.
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
//! [C] §3.2.2–§3.2.5.
|
||||
|
||||
use super::crypto::*;
|
||||
use super::inf::*;
|
||||
use super::mkb::*;
|
||||
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 content;
|
||||
pub mod crypto;
|
||||
pub mod derive;
|
||||
pub mod host_certs;
|
||||
pub mod inf;
|
||||
pub mod media_key;
|
||||
pub mod mkb;
|
||||
pub mod provider;
|
||||
pub mod resolve;
|
||||
pub mod trace;
|
||||
pub mod types;
|
||||
pub mod variant;
|
||||
pub mod volume_key;
|
||||
|
||||
/// On-disc UDF paths to the AACS key-input files (with their fallbacks).
|
||||
/// 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
|
||||
// documented 1.0 surface; keep it reachable but off the rendered docs so we
|
||||
// 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::{
|
||||
ContentCert, UnitKeyFile, disc_hash, disc_hash_hex, parse_content_cert, parse_unit_key_ro,
|
||||
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::{
|
||||
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,
|
||||
@@ -96,7 +96,6 @@ pub use variant::{
|
||||
KEY_CORRECTION_DATA_PLACEHOLDER, MediaKeyVariantError, ProcessingKeyMatch,
|
||||
derive_media_key_variant, is_variant_mkb, variant_nonce, walk_processing_key,
|
||||
};
|
||||
pub use volume_key::{decrypt_unit_key, derive_vuk};
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
|
||||
+2
-4
@@ -1,10 +1,9 @@
|
||||
//! AACS key resolution — VUK derivation, MKB processing, disc hash, unit key parsing.
|
||||
|
||||
use super::crypto::aes_ecb_decrypt;
|
||||
use super::derive::*;
|
||||
use super::inf::*;
|
||||
use super::media_key::*;
|
||||
use super::mkb::*;
|
||||
use super::volume_key::*;
|
||||
|
||||
//
|
||||
// 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
|
||||
// in sibling modules. Pull them all in so the tests keep exercising them.
|
||||
use super::super::crypto::*;
|
||||
use super::super::derive::*;
|
||||
use super::super::inf::*;
|
||||
use super::super::media_key::*;
|
||||
use super::super::mkb::*;
|
||||
use super::super::provider::SuppliedKey;
|
||||
use super::super::types::DiscEntry;
|
||||
use super::super::types::*;
|
||||
use super::super::volume_key::*;
|
||||
use super::*;
|
||||
|
||||
/// 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
|
||||
// definition keeps the variant SD tree byte-identical to the classical one.
|
||||
// (`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
|
||||
/// 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`
|
||||
// imports, so pull them in directly for the tests below.
|
||||
use super::super::crypto::aesg3;
|
||||
use super::super::media_key::calc_pk_from_dk;
|
||||
use super::super::derive::calc_pk_from_dk;
|
||||
|
||||
#[test]
|
||||
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