fix(css): no provable key is a hard failure, matching AACS
descramble_region descrambled with the key a sector's own crib had just proven stale, whenever the re-crack from that sector also failed. The clear header is not scrambled, so it survives intact: the sector still opens with a valid pack start and passes every structural check the PS demuxer applies. Only the payload is corrupted — exactly where nothing looks. Ok(0) dropped, exit 0. CSS has no external key source. The title key comes only from cracking the data, so on a READABLE sector "no key" is not a missing input, it is recovery failing on bytes we can see. That should never happen, and when it does the answer is not to emit something. Now Error::DecryptFailed — the same verdict the AACS path already gives for a unit no held key opens. Both alternatives to failing are bad data reported as success: descrambled with a rejected key it is garbage behind a valid header, and passed through untouched it is ciphertext where plaintext is meant to be. WHY IT WAS POSSIBLE, which matters more than the fix: There is no single place that owns "what do we do when there is no key". decrypt_sectors_impl looks like the central dispatch, but its AACS arm is a `return Err` stub — AACS decrypts entirely through decrypt_sectors_mapped, a separate top-level path. So CSS decided its own policy inside css/, AACS decided in decrypt.rs and mux/resolve.rs, and nothing held them to the same answer. The asymmetry was not an oversight; it was structurally permitted. How a disc decrypts is one process — resolve a key for this data, apply it, refuse if it cannot be proven. Only the resolve-and-apply step is scheme-specific. Filed as a task: the policy belongs in one orchestrator with the schemes supplying only what genuinely differs. Two tests changed rather than added, both of which pinned the old behaviour: the unit test asserted the sector was descrambled, and the integration test asserted the scramble flag was cleared, which is what descrambling-with-any-key does. Neither established that the result was CORRECT — the fourth bad-test shape.
This commit is contained in:
+25
-10
@@ -24,20 +24,35 @@ fn decrypt_sectors_with_none_keys_is_noop() {
|
||||
|
||||
/// Test: decrypt_sectors with CSS keys descrambles sectors.
|
||||
#[test]
|
||||
fn decrypt_sectors_with_css_keys_works() {
|
||||
fn css_decrypt_of_an_unkeyable_sector_fails_instead_of_emitting_data() {
|
||||
// A scrambled sector whose header is uniformly periodic yields a crib, so
|
||||
// the supplied key IS validated — and this arbitrary key is not the right
|
||||
// one, so the crib check rejects it and the re-crack from this synthetic
|
||||
// body finds nothing.
|
||||
//
|
||||
// CSS has no external key source: the title key comes only from cracking
|
||||
// the data. So "no key" on a readable sector is recovery failing on bytes
|
||||
// we can see, not a missing input — the same condition AACS answers with
|
||||
// DecryptFailed rather than applying a neighbouring unit's key. Emitting
|
||||
// the sector either way is bad data reported as success: descrambled with
|
||||
// the rejected key it is garbage behind an intact clear header, and passed
|
||||
// through untouched it is ciphertext where plaintext is meant to be.
|
||||
//
|
||||
// This test previously asserted the scramble flag was cleared, which pinned
|
||||
// the old behaviour of descrambling with whatever key happened to be held.
|
||||
let mut sector = vec![0xFFu8; 2048];
|
||||
sector[0x14] |= 0x30; // CSS scramble flag, bits 4-5
|
||||
|
||||
// Set CSS scramble flag (bits 4-5 of byte 0x14)
|
||||
sector[0x14] |= 0x30;
|
||||
|
||||
let title_key: [u8; 5] = [0x42, 0x13, 0x37, 0xBE, 0xEF]; // Not used - defined later
|
||||
let title_key: [u8; 5] = [0x42, 0x13, 0x37, 0xBE, 0xEF];
|
||||
let mut keys = DecryptKeys::Css { title_key };
|
||||
|
||||
// Descramble (CSS uses same operation for encrypt/decrypt)
|
||||
libfreemkv::decrypt::decrypt_sectors(&mut sector, &mut keys, 0).unwrap();
|
||||
|
||||
// Flag should be cleared
|
||||
assert_eq!(sector[0x14] & 0x30, 0x00, "CSS flag should be cleared");
|
||||
let err = libfreemkv::decrypt::decrypt_sectors(&mut sector, &mut keys, 0)
|
||||
.expect_err("an unkeyable CSS sector must fail loud");
|
||||
assert_eq!(
|
||||
err.code(),
|
||||
libfreemkv::error::Error::DecryptFailed.code(),
|
||||
"CSS and AACS must give the SAME verdict for 'no provable key'"
|
||||
);
|
||||
}
|
||||
|
||||
/// Test: AACS unit encryption detection works.
|
||||
|
||||
Reference in New Issue
Block a user