aacs: extract crypto.rs (shared AES primitives + constants)
Relocate the shared low-level primitives into a single crypto module: aes_ecb_encrypt/decrypt, aes_cbc_decrypt, aes_g (from content/variant) and aesg3 + AESG3_SEED (from keys), plus AACS_IV. Fixes the scatter where AES-G lived in the 2.1 file and AES-G3 in keys. Relocation only — no rename, no logic change (logic-hash identical to baseline; 277 items; 2210 tests green).
This commit is contained in:
+1
-1
@@ -275,7 +275,7 @@ pub fn resolve_candidate(
|
|||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::aacs::content::aes_ecb_encrypt;
|
use crate::aacs::crypto::aes_ecb_encrypt;
|
||||||
use crate::aacs::keys::{decrypt_unit_key, derive_vuk};
|
use crate::aacs::keys::{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
|
||||||
|
|||||||
+11
-59
@@ -1,15 +1,17 @@
|
|||||||
//! AACS content decryption — AES primitives, unit decryption, bus encryption.
|
//! AACS content decryption — aligned-unit / bus decryption and TS verification.
|
||||||
|
//! The low-level AES primitives it uses live in [`super::crypto`].
|
||||||
|
|
||||||
use aes::Aes128;
|
use aes::Aes128;
|
||||||
use aes::cipher::{BlockDecrypt, BlockEncrypt, KeyInit, generic_array::GenericArray};
|
use aes::cipher::{BlockDecrypt, KeyInit, generic_array::GenericArray};
|
||||||
|
|
||||||
|
use super::crypto::{aes_cbc_decrypt, aes_ecb_encrypt};
|
||||||
|
// Available at module scope for this module's test fixtures (they reference
|
||||||
|
// `super::AACS_IV` when building CBC ciphertext directly); test-only.
|
||||||
|
#[cfg(test)]
|
||||||
|
use super::crypto::AACS_IV;
|
||||||
|
|
||||||
// ── AACS constants ──────────────────────────────────────────────────────────
|
// ── AACS constants ──────────────────────────────────────────────────────────
|
||||||
|
|
||||||
/// Fixed IV used by AACS for all AES-CBC operations. [C] §2.1.2 (default CBC IV, `iv0`).
|
|
||||||
pub(crate) const AACS_IV: [u8; 16] = [
|
|
||||||
0x0B, 0xA0, 0xF8, 0xDD, 0xFE, 0xA6, 0x1F, 0xB3, 0xD8, 0xDF, 0x9F, 0x56, 0x6A, 0x05, 0x0F, 0x78,
|
|
||||||
];
|
|
||||||
|
|
||||||
/// Size of an AACS aligned unit (3 × 2048-byte sectors). [BD] §3.10.1.
|
/// Size of an AACS aligned unit (3 × 2048-byte sectors). [BD] §3.10.1.
|
||||||
pub const ALIGNED_UNIT_LEN: usize = 6144;
|
pub const ALIGNED_UNIT_LEN: usize = 6144;
|
||||||
|
|
||||||
@@ -47,58 +49,6 @@ use crate::consts::BD_SOURCE_PACKET_BYTES;
|
|||||||
/// TS sync byte.
|
/// TS sync byte.
|
||||||
const TS_SYNC: u8 = 0x47;
|
const TS_SYNC: u8 = 0x47;
|
||||||
|
|
||||||
// ── AES primitives ──────────────────────────────────────────────────────────
|
|
||||||
|
|
||||||
/// AES-128-ECB encrypt a single 16-byte block. [C] §2.1.1 (`AES-128E`).
|
|
||||||
pub(crate) fn aes_ecb_encrypt(key: &[u8; 16], data: &[u8; 16]) -> [u8; 16] {
|
|
||||||
let cipher = Aes128::new(GenericArray::from_slice(key));
|
|
||||||
let mut block = GenericArray::clone_from_slice(data);
|
|
||||||
cipher.encrypt_block(&mut block);
|
|
||||||
let mut out = [0u8; 16];
|
|
||||||
out.copy_from_slice(&block);
|
|
||||||
out
|
|
||||||
}
|
|
||||||
|
|
||||||
/// AES-128-ECB decrypt a single 16-byte block. [C] §2.1.1 (`AES-128D`).
|
|
||||||
pub(crate) fn aes_ecb_decrypt(key: &[u8; 16], data: &[u8; 16]) -> [u8; 16] {
|
|
||||||
let cipher = Aes128::new(GenericArray::from_slice(key));
|
|
||||||
let mut block = GenericArray::clone_from_slice(data);
|
|
||||||
cipher.decrypt_block(&mut block);
|
|
||||||
let mut out = [0u8; 16];
|
|
||||||
out.copy_from_slice(&block);
|
|
||||||
out
|
|
||||||
}
|
|
||||||
|
|
||||||
/// AES-128-CBC decrypt in-place with the fixed AACS IV. [C] §2.1.2 (`AES-128CBCD`).
|
|
||||||
///
|
|
||||||
/// Precondition: `data.len()` is a multiple of 16. Any trailing partial
|
|
||||||
/// block is silently ignored; all callers pass aligned regions (6128 and
|
|
||||||
/// 2032 bytes), and the assert documents/enforces that contract.
|
|
||||||
pub(crate) fn aes_cbc_decrypt(key: &[u8; 16], data: &mut [u8]) {
|
|
||||||
debug_assert!(
|
|
||||||
data.len() % 16 == 0,
|
|
||||||
"aes_cbc_decrypt requires a block-aligned slice"
|
|
||||||
);
|
|
||||||
let cipher = Aes128::new(GenericArray::from_slice(key));
|
|
||||||
let num_blocks = data.len() / 16;
|
|
||||||
// Process blocks in reverse to avoid clobbering ciphertext needed for XOR
|
|
||||||
for i in (0..num_blocks).rev() {
|
|
||||||
let offset = i * 16;
|
|
||||||
let prev = if i == 0 {
|
|
||||||
AACS_IV
|
|
||||||
} else {
|
|
||||||
let mut p = [0u8; 16];
|
|
||||||
p.copy_from_slice(&data[(i - 1) * 16..i * 16]);
|
|
||||||
p
|
|
||||||
};
|
|
||||||
let mut block = GenericArray::clone_from_slice(&data[offset..offset + 16]);
|
|
||||||
cipher.decrypt_block(&mut block);
|
|
||||||
for j in 0..16 {
|
|
||||||
data[offset + j] = block[j] ^ prev[j];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ── Content decryption ──────────────────────────────────────────────────────
|
// ── Content decryption ──────────────────────────────────────────────────────
|
||||||
|
|
||||||
/// True if a 6144-byte aligned unit's MPEG-TS sync structure is DESTROYED — it
|
/// True if a 6144-byte aligned unit's MPEG-TS sync structure is DESTROYED — it
|
||||||
@@ -587,7 +537,9 @@ pub fn decrypt_unit_full(
|
|||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
|
use super::super::crypto::aes_ecb_decrypt;
|
||||||
use super::*;
|
use super::*;
|
||||||
|
use aes::cipher::BlockEncrypt; // test fixtures build ciphertext directly
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_aes_ecb_roundtrip() {
|
fn test_aes_ecb_roundtrip() {
|
||||||
|
|||||||
@@ -0,0 +1,102 @@
|
|||||||
|
//! AACS common cryptographic primitives — [C] Chapter 2 / §3.2.2.
|
||||||
|
//!
|
||||||
|
//! Source: `[C]` = AACS Introduction and Common Cryptographic Elements Book,
|
||||||
|
//! Rev 0.953. The shared low-level building blocks — AES-128 ECB E/D, AES-G,
|
||||||
|
//! the AES-G3 Triple Generator, AES-CBC decrypt — and their fixed constants
|
||||||
|
//! (`iv0`, `s0`). Used by every AACS generation; relocated here so the
|
||||||
|
//! primitives live in one place instead of being scattered across the
|
||||||
|
//! content / keys / variant modules.
|
||||||
|
|
||||||
|
use aes::Aes128;
|
||||||
|
use aes::cipher::{BlockDecrypt, BlockEncrypt, KeyInit, generic_array::GenericArray};
|
||||||
|
|
||||||
|
/// Fixed IV used by AACS for all AES-CBC operations. [C] §2.1.2 (default CBC IV, `iv0`).
|
||||||
|
pub(crate) const AACS_IV: [u8; 16] = [
|
||||||
|
0x0B, 0xA0, 0xF8, 0xDD, 0xFE, 0xA6, 0x1F, 0xB3, 0xD8, 0xDF, 0x9F, 0x56, 0x6A, 0x05, 0x0F, 0x78,
|
||||||
|
];
|
||||||
|
|
||||||
|
/// AES-128-ECB encrypt a single 16-byte block. [C] §2.1.1 (`AES-128E`).
|
||||||
|
pub(crate) fn aes_ecb_encrypt(key: &[u8; 16], data: &[u8; 16]) -> [u8; 16] {
|
||||||
|
let cipher = Aes128::new(GenericArray::from_slice(key));
|
||||||
|
let mut block = GenericArray::clone_from_slice(data);
|
||||||
|
cipher.encrypt_block(&mut block);
|
||||||
|
let mut out = [0u8; 16];
|
||||||
|
out.copy_from_slice(&block);
|
||||||
|
out
|
||||||
|
}
|
||||||
|
|
||||||
|
/// AES-128-ECB decrypt a single 16-byte block. [C] §2.1.1 (`AES-128D`).
|
||||||
|
pub(crate) fn aes_ecb_decrypt(key: &[u8; 16], data: &[u8; 16]) -> [u8; 16] {
|
||||||
|
let cipher = Aes128::new(GenericArray::from_slice(key));
|
||||||
|
let mut block = GenericArray::clone_from_slice(data);
|
||||||
|
cipher.decrypt_block(&mut block);
|
||||||
|
let mut out = [0u8; 16];
|
||||||
|
out.copy_from_slice(&block);
|
||||||
|
out
|
||||||
|
}
|
||||||
|
|
||||||
|
/// AES-128-CBC decrypt in-place with the fixed AACS IV. [C] §2.1.2 (`AES-128CBCD`).
|
||||||
|
///
|
||||||
|
/// Precondition: `data.len()` is a multiple of 16. Any trailing partial
|
||||||
|
/// block is silently ignored; all callers pass aligned regions (6128 and
|
||||||
|
/// 2032 bytes), and the assert documents/enforces that contract.
|
||||||
|
pub(crate) fn aes_cbc_decrypt(key: &[u8; 16], data: &mut [u8]) {
|
||||||
|
debug_assert!(
|
||||||
|
data.len() % 16 == 0,
|
||||||
|
"aes_cbc_decrypt requires a block-aligned slice"
|
||||||
|
);
|
||||||
|
let cipher = Aes128::new(GenericArray::from_slice(key));
|
||||||
|
let num_blocks = data.len() / 16;
|
||||||
|
// Process blocks in reverse to avoid clobbering ciphertext needed for XOR
|
||||||
|
for i in (0..num_blocks).rev() {
|
||||||
|
let offset = i * 16;
|
||||||
|
let prev = if i == 0 {
|
||||||
|
AACS_IV
|
||||||
|
} else {
|
||||||
|
let mut p = [0u8; 16];
|
||||||
|
p.copy_from_slice(&data[(i - 1) * 16..i * 16]);
|
||||||
|
p
|
||||||
|
};
|
||||||
|
let mut block = GenericArray::clone_from_slice(&data[offset..offset + 16]);
|
||||||
|
cipher.decrypt_block(&mut block);
|
||||||
|
for j in 0..16 {
|
||||||
|
data[offset + j] = block[j] ^ prev[j];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// AES-G(x1, x2) = AES-128D(x1, x2) XOR x2. [C] §2.1.3 (note: uses AES-128**D**).
|
||||||
|
///
|
||||||
|
/// The Media Key Variant chain uses AES-G to derive both the variant
|
||||||
|
/// number (`Kvn = AES-G(Kp, Nonce)`) and the Volume Unique Key
|
||||||
|
/// (`Kvu = AES-G(Km, VID)`). See [`super::keys::derive_vuk`] for the
|
||||||
|
/// classical VUK form — the math is identical, this exposes it as a
|
||||||
|
/// neutral primitive for the variant chain.
|
||||||
|
pub(crate) fn aes_g(x1: &[u8; 16], x2: &[u8; 16]) -> [u8; 16] {
|
||||||
|
let mut out = aes_ecb_decrypt(x1, x2);
|
||||||
|
for i in 0..16 {
|
||||||
|
out[i] ^= x2[i];
|
||||||
|
}
|
||||||
|
out
|
||||||
|
}
|
||||||
|
|
||||||
|
/// AACS-G3 seed constant (`s0`). [C] §3.2.2.
|
||||||
|
pub(crate) const AESG3_SEED: [u8; 16] = [
|
||||||
|
0x7B, 0x10, 0x3C, 0x5D, 0xCB, 0x08, 0xC4, 0xE5, 0x1A, 0x27, 0xB0, 0x17, 0x99, 0x05, 0x3B, 0xD9,
|
||||||
|
];
|
||||||
|
|
||||||
|
/// AACS-G3: derive a subkey from a parent key. [C] §3.2.2 (Triple AES Generator:
|
||||||
|
/// left=`D(k,s0)⊕s0` inc 0, pk=`D(k,s0+1)⊕(s0+1)` inc 1, right=`D(k,s0+2)⊕(s0+2)` inc 2).
|
||||||
|
/// seed[15] += inc, then AES-DEC(key, seed) XOR seed.
|
||||||
|
///
|
||||||
|
/// Shared with [`super::variants`] (its variant chain runs the same SD
|
||||||
|
/// tree); a single definition keeps the two walks byte-identical.
|
||||||
|
pub(crate) fn aesg3(key: &[u8; 16], inc: u8) -> [u8; 16] {
|
||||||
|
let mut seed = AESG3_SEED;
|
||||||
|
seed[15] = seed[15].wrapping_add(inc);
|
||||||
|
let mut out = aes_ecb_decrypt(key, &seed);
|
||||||
|
for i in 0..16 {
|
||||||
|
out[i] ^= seed[i];
|
||||||
|
}
|
||||||
|
out
|
||||||
|
}
|
||||||
+9
-30
@@ -1,6 +1,6 @@
|
|||||||
//! 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::content::aes_ecb_decrypt;
|
use super::crypto::{aes_ecb_decrypt, aesg3};
|
||||||
use super::types::DeviceKey;
|
use super::types::DeviceKey;
|
||||||
|
|
||||||
// ── AACS version ────────────────────────────────────────────────────────────
|
// ── AACS version ────────────────────────────────────────────────────────────
|
||||||
@@ -600,27 +600,6 @@ pub fn mkb_is_uhd(mkb: &[u8]) -> Option<bool> {
|
|||||||
|
|
||||||
// ── AACS-G3 key derivation (subset-difference tree) ─────────────────────────
|
// ── AACS-G3 key derivation (subset-difference tree) ─────────────────────────
|
||||||
|
|
||||||
/// AACS-G3 seed constant (`s0`). [C] §3.2.2.
|
|
||||||
const AESG3_SEED: [u8; 16] = [
|
|
||||||
0x7B, 0x10, 0x3C, 0x5D, 0xCB, 0x08, 0xC4, 0xE5, 0x1A, 0x27, 0xB0, 0x17, 0x99, 0x05, 0x3B, 0xD9,
|
|
||||||
];
|
|
||||||
|
|
||||||
/// AACS-G3: derive a subkey from a parent key. [C] §3.2.2 (Triple AES Generator:
|
|
||||||
/// left=`D(k,s0)⊕s0` inc 0, pk=`D(k,s0+1)⊕(s0+1)` inc 1, right=`D(k,s0+2)⊕(s0+2)` inc 2).
|
|
||||||
/// seed[15] += inc, then AES-DEC(key, seed) XOR seed.
|
|
||||||
///
|
|
||||||
/// Shared with [`super::variants`] (its variant chain runs the same SD
|
|
||||||
/// tree); a single definition keeps the two walks byte-identical.
|
|
||||||
pub(super) fn aesg3(key: &[u8; 16], inc: u8) -> [u8; 16] {
|
|
||||||
let mut seed = AESG3_SEED;
|
|
||||||
seed[15] = seed[15].wrapping_add(inc);
|
|
||||||
let mut out = aes_ecb_decrypt(key, &seed);
|
|
||||||
for i in 0..16 {
|
|
||||||
out[i] ^= seed[i];
|
|
||||||
}
|
|
||||||
out
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Compute v_mask from a UV value. [C] §3.2.3. Shared with [`super::variants`].
|
/// Compute v_mask from a UV value. [C] §3.2.3. Shared with [`super::variants`].
|
||||||
pub(super) fn calc_v_mask(uv: u32) -> u32 {
|
pub(super) fn calc_v_mask(uv: u32) -> u32 {
|
||||||
let mut v_mask: u32 = 0xFFFF_FFFF;
|
let mut v_mask: u32 = 0xFFFF_FFFF;
|
||||||
@@ -1536,7 +1515,7 @@ mod tests {
|
|||||||
// keeps the crypto covered in libfreemkv. `aes_ecb_encrypt` is
|
// keeps the crypto covered in libfreemkv. `aes_ecb_encrypt` is
|
||||||
// pub(crate), reachable here but not from keysources — the reason this
|
// pub(crate), reachable here but not from keysources — the reason this
|
||||||
// half stays.
|
// half stays.
|
||||||
use super::super::content::aes_ecb_encrypt;
|
use super::super::crypto::aes_ecb_encrypt;
|
||||||
let vuk = [0x5Au8; 16];
|
let vuk = [0x5Au8; 16];
|
||||||
// A few representative "decrypted" unit keys.
|
// A few representative "decrypted" unit keys.
|
||||||
for expected_uk in [[0x11u8; 16], [0x22u8; 16], [0xCDu8; 16]] {
|
for expected_uk in [[0x11u8; 16], [0x22u8; 16], [0xCDu8; 16]] {
|
||||||
@@ -1726,7 +1705,7 @@ mod tests {
|
|||||||
// whose derived Media Key satisfies a synthetic verify record; confirm
|
// whose derived Media Key satisfies a synthetic verify record; confirm
|
||||||
// the scan ACCEPTS it against caller-supplied SD/cvalue tables and
|
// the scan ACCEPTS it against caller-supplied SD/cvalue tables and
|
||||||
// REJECTS a 1-byte corruption.
|
// REJECTS a 1-byte corruption.
|
||||||
use super::super::content::aes_ecb_encrypt as enc;
|
use super::super::crypto::aes_ecb_encrypt as enc;
|
||||||
|
|
||||||
let pk: [u8; 16] = [
|
let pk: [u8; 16] = [
|
||||||
0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xAA, 0xBB, 0xCC, 0xDD, 0xEE,
|
0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xAA, 0xBB, 0xCC, 0xDD, 0xEE,
|
||||||
@@ -1772,7 +1751,7 @@ mod tests {
|
|||||||
// recovers mk. Catches the bugs that landed pre-fix:
|
// recovers mk. Catches the bugs that landed pre-fix:
|
||||||
// * uv XOR step was missing → mk wrong whenever uv != 0
|
// * uv XOR step was missing → mk wrong whenever uv != 0
|
||||||
// * AES-128E + 12-zero check instead of AES-128D + magic
|
// * AES-128E + 12-zero check instead of AES-128D + magic
|
||||||
use super::super::content::{aes_ecb_decrypt as dec, aes_ecb_encrypt as enc};
|
use super::super::crypto::{aes_ecb_decrypt as dec, aes_ecb_encrypt as enc};
|
||||||
|
|
||||||
let pk: [u8; 16] = [
|
let pk: [u8; 16] = [
|
||||||
0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xAA, 0xBB, 0xCC, 0xDD, 0xEE,
|
0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xAA, 0xBB, 0xCC, 0xDD, 0xEE,
|
||||||
@@ -2191,7 +2170,7 @@ mod tests {
|
|||||||
// The keyless-disc case: this disc's own hash/VID are NOT in keydb, but its
|
// The keyless-disc case: this disc's own hash/VID are NOT in keydb, but its
|
||||||
// Media Key IS — filed under a sibling disc that shares its MKB. Path
|
// Media Key IS — filed under a sibling disc that shares its MKB. Path
|
||||||
// 2.5 must km_verifies that MK against the MKB and resolve.
|
// 2.5 must km_verifies that MK against the MKB and resolve.
|
||||||
use super::super::content::aes_ecb_encrypt as enc;
|
use super::super::crypto::aes_ecb_encrypt as enc;
|
||||||
let km = [0x11u8; 16];
|
let km = [0x11u8; 16];
|
||||||
let vid = [0x22u8; 16];
|
let vid = [0x22u8; 16];
|
||||||
// MKB: 0x10 type/version + 0x86 verify record whose mk_dv decrypts under
|
// MKB: 0x10 type/version + 0x86 verify record whose mk_dv decrypts under
|
||||||
@@ -2272,7 +2251,7 @@ mod tests {
|
|||||||
// Independently compute AES-ECB-D(mk, vid) XOR vid and confirm
|
// Independently compute AES-ECB-D(mk, vid) XOR vid and confirm
|
||||||
// derive_vuk produces the same 16 bytes. A mutation that dropped the
|
// derive_vuk produces the same 16 bytes. A mutation that dropped the
|
||||||
// XOR-VID step, or used encrypt instead of decrypt, fails this.
|
// XOR-VID step, or used encrypt instead of decrypt, fails this.
|
||||||
use super::super::content::aes_ecb_decrypt as dec;
|
use super::super::crypto::aes_ecb_decrypt as dec;
|
||||||
let mk = [
|
let mk = [
|
||||||
0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1A, 0x1B, 0x1C, 0x1D,
|
0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1A, 0x1B, 0x1C, 0x1D,
|
||||||
0x1E, 0x1F,
|
0x1E, 0x1F,
|
||||||
@@ -2292,7 +2271,7 @@ mod tests {
|
|||||||
// The encrypted unit key in Unit_Key_RO.inf is AES-ECB-E(VUK, uk);
|
// The encrypted unit key in Unit_Key_RO.inf is AES-ECB-E(VUK, uk);
|
||||||
// decrypt_unit_key must be the matching ECB-decrypt. Round-trip via
|
// decrypt_unit_key must be the matching ECB-decrypt. Round-trip via
|
||||||
// encrypt to pin the relation.
|
// encrypt to pin the relation.
|
||||||
use super::super::content::aes_ecb_encrypt as enc;
|
use super::super::crypto::aes_ecb_encrypt as enc;
|
||||||
let vuk = [0x9Eu8; 16];
|
let vuk = [0x9Eu8; 16];
|
||||||
let uk = [0x3Cu8; 16];
|
let uk = [0x3Cu8; 16];
|
||||||
let enc_uk = enc(&vuk, &uk);
|
let enc_uk = enc(&vuk, &uk);
|
||||||
@@ -2692,7 +2671,7 @@ mod tests {
|
|||||||
fn resolve_keys_v21_path4_resolves_by_hash() {
|
fn resolve_keys_v21_path4_resolves_by_hash() {
|
||||||
// resolve_keys_v21 must hit path 4 (hash→VUK) and stamp version V21,
|
// resolve_keys_v21 must hit path 4 (hash→VUK) and stamp version V21,
|
||||||
// deriving unit keys from the VUK.
|
// deriving unit keys from the VUK.
|
||||||
use super::super::content::aes_ecb_encrypt as enc;
|
use super::super::crypto::aes_ecb_encrypt as enc;
|
||||||
let data = build_unit_key_ro(1, 64);
|
let data = build_unit_key_ro(1, 64);
|
||||||
// The single encrypted key in build_unit_key_ro is [0x10;16].
|
// The single encrypted key in build_unit_key_ro is [0x10;16].
|
||||||
let hash = disc_hash(&data);
|
let hash = disc_hash(&data);
|
||||||
@@ -2842,7 +2821,7 @@ mod tests {
|
|||||||
// - 0x86 Verify Media Key: mk_dv = AES-E(mk, magic || pad)
|
// - 0x86 Verify Media Key: mk_dv = AES-E(mk, magic || pad)
|
||||||
// and a DK with node=4, uv=2, u_mask_shift=3 so dev_key_v_mask ==
|
// and a DK with node=4, uv=2, u_mask_shift=3 so dev_key_v_mask ==
|
||||||
// v_mask: the calc_pk_from_dk loop is a no-op and Kp == aesg3(dk, 1).
|
// v_mask: the calc_pk_from_dk loop is a no-op and Kp == aesg3(dk, 1).
|
||||||
use super::super::content::aes_ecb_encrypt as enc;
|
use super::super::crypto::aes_ecb_encrypt as enc;
|
||||||
|
|
||||||
let dk_bytes: [u8; 16] = [
|
let dk_bytes: [u8; 16] = [
|
||||||
0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xAA, 0xBB, 0xCC, 0xDD, 0xEE,
|
0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xAA, 0xBB, 0xCC, 0xDD, 0xEE,
|
||||||
|
|||||||
@@ -27,6 +27,7 @@
|
|||||||
|
|
||||||
pub mod boil;
|
pub mod boil;
|
||||||
pub mod content;
|
pub mod content;
|
||||||
|
pub mod crypto;
|
||||||
pub mod host_certs;
|
pub mod host_certs;
|
||||||
pub mod keys;
|
pub mod keys;
|
||||||
pub mod provider;
|
pub mod provider;
|
||||||
|
|||||||
+4
-20
@@ -72,7 +72,7 @@
|
|||||||
//! that final gate — the per-match magic check no longer protects the
|
//! that final gate — the per-match magic check no longer protects the
|
||||||
//! variant path.
|
//! variant path.
|
||||||
|
|
||||||
use super::content::aes_ecb_decrypt;
|
use super::crypto::{aes_ecb_decrypt, aes_g};
|
||||||
use super::types::DeviceKey;
|
use super::types::DeviceKey;
|
||||||
|
|
||||||
// ── Public constants ──────────────────────────────────────────────────────
|
// ── Public constants ──────────────────────────────────────────────────────
|
||||||
@@ -203,23 +203,6 @@ pub(crate) fn variant_key_data(records: &[MkbRecord]) -> Option<&[u8]> {
|
|||||||
.map(|r| r.body.as_slice())
|
.map(|r| r.body.as_slice())
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── AES-G ────────────────────────────────────────────────────────────────
|
|
||||||
|
|
||||||
/// AES-G(x1, x2) = AES-128D(x1, x2) XOR x2. [C] §2.1.3 (note: uses AES-128**D**).
|
|
||||||
///
|
|
||||||
/// The Media Key Variant chain uses AES-G to derive both the variant
|
|
||||||
/// number (`Kvn = AES-G(Kp, Nonce)`) and the Volume Unique Key
|
|
||||||
/// (`Kvu = AES-G(Km, VID)`). See [`super::keys::derive_vuk`] for the
|
|
||||||
/// classical VUK form — the math is identical, this exposes it as a
|
|
||||||
/// neutral primitive for the variant chain.
|
|
||||||
fn aes_g(x1: &[u8; 16], x2: &[u8; 16]) -> [u8; 16] {
|
|
||||||
let mut out = aes_ecb_decrypt(x1, x2);
|
|
||||||
for i in 0..16 {
|
|
||||||
out[i] ^= x2[i];
|
|
||||||
}
|
|
||||||
out
|
|
||||||
}
|
|
||||||
|
|
||||||
// ── Subset-difference walk that exposes (Kp, uv) ──────────────────────────
|
// ── Subset-difference walk that exposes (Kp, uv) ──────────────────────────
|
||||||
|
|
||||||
// `calc_v_mask` and `calc_pk_from_dk` (and the AES-G3 seed step they ride
|
// `calc_v_mask` and `calc_pk_from_dk` (and the AES-G3 seed step they ride
|
||||||
@@ -564,7 +547,8 @@ mod tests {
|
|||||||
// These three live in `super::keys` now (consolidated SD-walk helpers);
|
// These three live in `super::keys` now (consolidated SD-walk helpers);
|
||||||
// `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::keys::{aesg3, calc_pk_from_dk};
|
use super::super::crypto::aesg3;
|
||||||
|
use super::super::keys::calc_pk_from_dk;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn calc_pk_from_dk_terminates_on_nonconvergent_mask() {
|
fn calc_pk_from_dk_terminates_on_nonconvergent_mask() {
|
||||||
@@ -743,7 +727,7 @@ mod tests {
|
|||||||
///
|
///
|
||||||
/// Returns (records, dk, planted_kp, planted_kmp).
|
/// Returns (records, dk, planted_kp, planted_kmp).
|
||||||
fn synthetic_variant_setup(kmp15: u8) -> (Vec<MkbRecord>, DeviceKey, [u8; 16], [u8; 16]) {
|
fn synthetic_variant_setup(kmp15: u8) -> (Vec<MkbRecord>, DeviceKey, [u8; 16], [u8; 16]) {
|
||||||
use crate::aacs::content::aes_ecb_encrypt;
|
use crate::aacs::crypto::aes_ecb_encrypt;
|
||||||
|
|
||||||
// Build header.
|
// Build header.
|
||||||
let mut mkb = vec![
|
let mut mkb = vec![
|
||||||
|
|||||||
+2
-2
@@ -1129,13 +1129,13 @@ mod tests {
|
|||||||
// the per-unit key so the recovered plaintext header matches.
|
// the per-unit key so the recovered plaintext header matches.
|
||||||
unit[0] |= 0xC0;
|
unit[0] |= 0xC0;
|
||||||
let header: [u8; 16] = unit[..16].try_into().unwrap();
|
let header: [u8; 16] = unit[..16].try_into().unwrap();
|
||||||
let derived = crate::aacs::content::aes_ecb_encrypt(unit_key, &header);
|
let derived = crate::aacs::crypto::aes_ecb_encrypt(unit_key, &header);
|
||||||
let mut k = [0u8; 16];
|
let mut k = [0u8; 16];
|
||||||
for i in 0..16 {
|
for i in 0..16 {
|
||||||
k[i] = derived[i] ^ header[i];
|
k[i] = derived[i] ^ header[i];
|
||||||
}
|
}
|
||||||
let cipher = Aes128::new(GenericArray::from_slice(&k));
|
let cipher = Aes128::new(GenericArray::from_slice(&k));
|
||||||
let mut prev = crate::aacs::content::AACS_IV;
|
let mut prev = crate::aacs::crypto::AACS_IV;
|
||||||
let num_blocks = (aacs::ALIGNED_UNIT_LEN - 16) / 16;
|
let num_blocks = (aacs::ALIGNED_UNIT_LEN - 16) / 16;
|
||||||
for i in 0..num_blocks {
|
for i in 0..num_blocks {
|
||||||
let off = 16 + i * 16;
|
let off = 16 + i * 16;
|
||||||
|
|||||||
+2
-2
@@ -1101,13 +1101,13 @@ mod tests {
|
|||||||
// Flag encrypted via CPI bits (byte 0) before key derivation.
|
// Flag encrypted via CPI bits (byte 0) before key derivation.
|
||||||
unit[0] |= 0xC0;
|
unit[0] |= 0xC0;
|
||||||
let header: [u8; 16] = unit[..16].try_into().unwrap();
|
let header: [u8; 16] = unit[..16].try_into().unwrap();
|
||||||
let derived = crate::aacs::content::aes_ecb_encrypt(unit_key, &header);
|
let derived = crate::aacs::crypto::aes_ecb_encrypt(unit_key, &header);
|
||||||
let mut k = [0u8; 16];
|
let mut k = [0u8; 16];
|
||||||
for i in 0..16 {
|
for i in 0..16 {
|
||||||
k[i] = derived[i] ^ header[i];
|
k[i] = derived[i] ^ header[i];
|
||||||
}
|
}
|
||||||
let cipher = Aes128::new(GenericArray::from_slice(&k));
|
let cipher = Aes128::new(GenericArray::from_slice(&k));
|
||||||
let mut prev = crate::aacs::content::AACS_IV;
|
let mut prev = crate::aacs::crypto::AACS_IV;
|
||||||
let blocks = (crate::aacs::ALIGNED_UNIT_LEN - 16) / 16;
|
let blocks = (crate::aacs::ALIGNED_UNIT_LEN - 16) / 16;
|
||||||
for i in 0..blocks {
|
for i in 0..blocks {
|
||||||
let o = 16 + i * 16;
|
let o = 16 + i * 16;
|
||||||
|
|||||||
+2
-1
@@ -5220,7 +5220,8 @@ mod tests {
|
|||||||
/// unit algorithm — ECB-derive the per-unit key, then AES-CBC encrypt the
|
/// unit algorithm — ECB-derive the per-unit key, then AES-CBC encrypt the
|
||||||
/// body with the fixed AACS IV.
|
/// body with the fixed AACS IV.
|
||||||
fn encrypt_unit_for_test(clear: &[u8], uk: &[u8; 16]) -> Vec<u8> {
|
fn encrypt_unit_for_test(clear: &[u8], uk: &[u8; 16]) -> Vec<u8> {
|
||||||
use crate::aacs::content::{AACS_IV, ALIGNED_UNIT_LEN};
|
use crate::aacs::content::ALIGNED_UNIT_LEN;
|
||||||
|
use crate::aacs::crypto::AACS_IV;
|
||||||
use aes::Aes128;
|
use aes::Aes128;
|
||||||
use aes::cipher::{BlockEncrypt, KeyInit, generic_array::GenericArray};
|
use aes::cipher::{BlockEncrypt, KeyInit, generic_array::GenericArray};
|
||||||
let mut unit = clear[..ALIGNED_UNIT_LEN].to_vec();
|
let mut unit = clear[..ALIGNED_UNIT_LEN].to_vec();
|
||||||
|
|||||||
+2
-2
@@ -523,13 +523,13 @@ mod tests {
|
|||||||
use aes::cipher::{BlockEncrypt, KeyInit, generic_array::GenericArray};
|
use aes::cipher::{BlockEncrypt, KeyInit, generic_array::GenericArray};
|
||||||
unit[0] |= 0xC0; // CPI flag => reads as encrypted
|
unit[0] |= 0xC0; // CPI flag => reads as encrypted
|
||||||
let header: [u8; 16] = unit[..16].try_into().unwrap();
|
let header: [u8; 16] = unit[..16].try_into().unwrap();
|
||||||
let derived = crate::aacs::content::aes_ecb_encrypt(unit_key, &header);
|
let derived = crate::aacs::crypto::aes_ecb_encrypt(unit_key, &header);
|
||||||
let mut k = [0u8; 16];
|
let mut k = [0u8; 16];
|
||||||
for i in 0..16 {
|
for i in 0..16 {
|
||||||
k[i] = derived[i] ^ header[i];
|
k[i] = derived[i] ^ header[i];
|
||||||
}
|
}
|
||||||
let cipher = Aes128::new(GenericArray::from_slice(&k));
|
let cipher = Aes128::new(GenericArray::from_slice(&k));
|
||||||
let mut prev = crate::aacs::content::AACS_IV;
|
let mut prev = crate::aacs::crypto::AACS_IV;
|
||||||
for i in 0..(ALIGNED_UNIT_LEN - 16) / 16 {
|
for i in 0..(ALIGNED_UNIT_LEN - 16) / 16 {
|
||||||
let off = 16 + i * 16;
|
let off = 16 + i * 16;
|
||||||
for j in 0..16 {
|
for j in 0..16 {
|
||||||
|
|||||||
@@ -1267,13 +1267,13 @@ mod tests {
|
|||||||
// CPI bits on byte 0 so it reads as encrypted; set before key derivation.
|
// CPI bits on byte 0 so it reads as encrypted; set before key derivation.
|
||||||
unit[0] |= 0xC0;
|
unit[0] |= 0xC0;
|
||||||
let header: [u8; 16] = unit[..16].try_into().unwrap();
|
let header: [u8; 16] = unit[..16].try_into().unwrap();
|
||||||
let derived = crate::aacs::content::aes_ecb_encrypt(unit_key, &header);
|
let derived = crate::aacs::crypto::aes_ecb_encrypt(unit_key, &header);
|
||||||
let mut k = [0u8; 16];
|
let mut k = [0u8; 16];
|
||||||
for i in 0..16 {
|
for i in 0..16 {
|
||||||
k[i] = derived[i] ^ header[i];
|
k[i] = derived[i] ^ header[i];
|
||||||
}
|
}
|
||||||
let cipher = Aes128::new(GenericArray::from_slice(&k));
|
let cipher = Aes128::new(GenericArray::from_slice(&k));
|
||||||
let mut prev = crate::aacs::content::AACS_IV;
|
let mut prev = crate::aacs::crypto::AACS_IV;
|
||||||
let blocks = (crate::aacs::ALIGNED_UNIT_LEN - 16) / 16;
|
let blocks = (crate::aacs::ALIGNED_UNIT_LEN - 16) / 16;
|
||||||
for i in 0..blocks {
|
for i in 0..blocks {
|
||||||
let o = 16 + i * 16;
|
let o = 16 + i * 16;
|
||||||
|
|||||||
Reference in New Issue
Block a user