The Windows UI needs current winsafe, whose real minimum is 1.89 (its manifest under-declares 1.87 while it uses NonNull::from_ref). Rather than stop at the minimum, this goes to current stable and fixes what that costs. The counter-intuitive result: 1.97 is CHEAPER than 1.89. libfreemkv had 54 clippy errors at 1.89 and 6 at 1.97, because clippy tightened the noisy collapsible_if lint in between. Stopping at the minimum would have been the most expensive choice available. Roughly 47 lints across the eight repos, the large majority auto-fixed: libfreemkv 6, freemkv-engine 14, bdemu 8, freemkv-keysources 7, autorip 6, freemkv-unlock 3, freemkv-i18n 3. The hand-fixed ones are a descending sort to sort_by_key(Reverse), four manual checked-division sites, a loop counter replaced by enumerate, and a loop whose first let-else became a while-let. Worth recording for whoever bumps next: clippy is MSRV-AWARE. Those 54 lints only appear once the crate DECLARES 1.89 or later, because let-chains become available. A bare `cargo +1.89 clippy` against a manifest still pinned at 1.87 reports clean and is meaningless — gate with the real precommit script, which is also the only thing that covers build scripts. The pin still sits below the Mac default, so it keeps doing its job: catching lint drift locally before CI sees it.
132 lines
5.2 KiB
Rust
132 lines
5.2 KiB
Rust
//! 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.
|
|
/// AES-128-CBC encrypt in place under the fixed [`AACS_IV`] — the forward
|
|
/// direction of [`aes_cbc_decrypt`], and its exact inverse.
|
|
///
|
|
/// Constructs the cipher ONCE for the whole slice. Driving this from the
|
|
/// single-block [`aes_ecb_encrypt`] instead rebuilds the AES key schedule per
|
|
/// 16-byte block, which for a 6144-byte aligned unit is 383 redundant key
|
|
/// expansions.
|
|
pub(crate) fn aes_cbc_encrypt(key: &[u8; 16], data: &mut [u8]) {
|
|
debug_assert!(
|
|
data.len().is_multiple_of(16),
|
|
"aes_cbc_encrypt requires a block-aligned slice"
|
|
);
|
|
let cipher = Aes128::new(GenericArray::from_slice(key));
|
|
let num_blocks = data.len() / 16;
|
|
let mut prev = AACS_IV;
|
|
// Forward order: each block is XORed with the PRECEDING ciphertext block.
|
|
for i in 0..num_blocks {
|
|
let offset = i * 16;
|
|
let mut block = [0u8; 16];
|
|
for j in 0..16 {
|
|
block[j] = data[offset + j] ^ prev[j];
|
|
}
|
|
let mut ga = GenericArray::clone_from_slice(&block);
|
|
cipher.encrypt_block(&mut ga);
|
|
data[offset..offset + 16].copy_from_slice(&ga);
|
|
prev.copy_from_slice(&ga);
|
|
}
|
|
}
|
|
|
|
pub(crate) fn aes_cbc_decrypt(key: &[u8; 16], data: &mut [u8]) {
|
|
debug_assert!(
|
|
data.len().is_multiple_of(16),
|
|
"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::derive::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::variant`] (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
|
|
}
|