Test that the Media Key verifier actually rejects a wrong key
`km_verifies` is the gate deciding whether a candidate Media Key belongs to the disc. The MK-pool brute force in resolve.rs runs every candidate through it, so a version that said yes to everything would accept whichever candidate it tried first and the rip would continue with a wrong Media Key — wrong VUK, wrong title keys, garbage plaintext, and no error raised anywhere. Nothing tested it. Whole-crate mutation testing reported `replace km_verifies -> bool with true` as SURVIVING: the body could be replaced with `true` and all 2,556 tests still passed. A verification routine whose verification was itself unverified, in the most safety-critical function in the crate. The implementation is correct — it matches the AACS relation `AES-D(km, mk_dv)[0..8] == 01 23 45 67 89 AB CD EF`. Only the defence was missing. No real key material is required to test it. That relation means a valid record for any chosen km is just `AES-E(km, <the constant> || anything)`, so the fixture is self-contained. The test asserts three things: the key the record was built for verifies; a key differing by ONE BIT does not, which is the assertion that kills the mutant and is a near-miss rather than a random key; and an MKB carrying no verify record does not default to yes, because unverifiable and verified are different answers. Confirmed by reintroducing the exact reported mutation and watching this test fail. This is the first defect found by mutation testing rather than by reading. Eight audit rounds and a security lens that read this file in full all missed it, because it is not a wrong line — it is an absent test, and only an instrument that asks "would anything notice if this were broken?" can see that.
This commit is contained in:
@@ -583,6 +583,70 @@ mod resolve_candidate_tests {
|
|||||||
use super::*;
|
use super::*;
|
||||||
use crate::aacs::crypto::aes_ecb_encrypt;
|
use crate::aacs::crypto::aes_ecb_encrypt;
|
||||||
|
|
||||||
|
/// `km_verifies` is the gate deciding whether a candidate Media Key is the
|
||||||
|
/// disc's. `resolve.rs`'s MK-pool brute force runs every candidate through
|
||||||
|
/// it, so if it said yes to everything the first candidate would be accepted
|
||||||
|
/// and the rip would continue with a wrong Media Key — wrong VUK, wrong
|
||||||
|
/// title keys, garbage plaintext, and no error raised anywhere.
|
||||||
|
///
|
||||||
|
/// Nothing tested it. Whole-crate mutation testing surfaced
|
||||||
|
/// `replace km_verifies -> bool with true` as SURVIVING: the body could be
|
||||||
|
/// replaced by `true` and all 2,556 tests still passed. A verification
|
||||||
|
/// routine whose verification was itself unverified.
|
||||||
|
///
|
||||||
|
/// No real key material is needed. AACS defines the relation as
|
||||||
|
/// `AES-D(km, mk_dv)[0..8] == 01 23 45 67 89 AB CD EF`, so a valid record
|
||||||
|
/// for a chosen `km` is simply `AES-E(km, <that constant> || anything)`.
|
||||||
|
#[test]
|
||||||
|
fn km_verifies_accepts_only_the_key_its_record_was_built_for() {
|
||||||
|
use crate::aacs::mkb::mkb_find_mk_dv;
|
||||||
|
|
||||||
|
const MAGIC: [u8; 8] = [0x01, 0x23, 0x45, 0x67, 0x89, 0xAB, 0xCD, 0xEF];
|
||||||
|
let km: [u8; 16] = [
|
||||||
|
0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xAA, 0xBB, 0xCC, 0xDD,
|
||||||
|
0xEE, 0xFF,
|
||||||
|
];
|
||||||
|
|
||||||
|
let mut plain = [0u8; 16];
|
||||||
|
plain[..8].copy_from_slice(&MAGIC);
|
||||||
|
plain[8..].copy_from_slice(&[0xA5; 8]);
|
||||||
|
let mk_dv = aes_ecb_encrypt(&km, &plain);
|
||||||
|
|
||||||
|
let mut mkb = vec![
|
||||||
|
0x10, 0x00, 0x00, 0x0C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
|
||||||
|
];
|
||||||
|
mkb.extend_from_slice(&[0x81, 0x00, 0x00, 0x18]);
|
||||||
|
mkb.extend_from_slice(&mk_dv);
|
||||||
|
mkb.extend_from_slice(&[0x00, 0x00, 0x00, 0x00]);
|
||||||
|
assert_eq!(
|
||||||
|
mkb_find_mk_dv(&mkb),
|
||||||
|
Some(mk_dv),
|
||||||
|
"fixture malformed — the verify record is not being found at all"
|
||||||
|
);
|
||||||
|
|
||||||
|
assert!(
|
||||||
|
probe::km_verifies(&mkb, &km),
|
||||||
|
"the key the record was built for must verify"
|
||||||
|
);
|
||||||
|
|
||||||
|
// The half that kills the `-> true` mutant. One flipped bit is the
|
||||||
|
// strongest form of wrong key: a near-miss, not a random one.
|
||||||
|
let mut wrong = km;
|
||||||
|
wrong[15] ^= 0x01;
|
||||||
|
assert!(
|
||||||
|
!probe::km_verifies(&mkb, &wrong),
|
||||||
|
"a key differing by ONE BIT must not verify; if it did, the MK-pool \
|
||||||
|
brute force would accept whichever candidate it happened to try first"
|
||||||
|
);
|
||||||
|
|
||||||
|
// No verify record means UNVERIFIABLE, which is not the same as verified.
|
||||||
|
let bare = vec![0x10, 0x00, 0x00, 0x0C, 0, 0, 0, 0, 0, 0, 0, 1];
|
||||||
|
assert!(
|
||||||
|
!probe::km_verifies(&bare, &km),
|
||||||
|
"an MKB with no verify record must not default to yes"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
/// `ResolvedChain.unit_keys` holds raw title-key bytes (the other rungs are
|
/// `ResolvedChain.unit_keys` holds raw title-key bytes (the other rungs are
|
||||||
/// self-redacting `types` newtypes). `Debug` must not leak the title keys.
|
/// self-redacting `types` newtypes). `Debug` must not leak the title keys.
|
||||||
#[test]
|
#[test]
|
||||||
|
|||||||
Reference in New Issue
Block a user