is_unit_encrypted read the TP_extra copy-control bits (byte 0), which are a copy-permission flag, not an encryption flag. On discs whose sampled units are clear navigation packets (PAT/PMT) those bits can be set while the unit is not scrambled, so a correct Unit Key was used to 'decrypt' already-plaintext data, produced garbage, and the key was wrongly treated as failing. Read the actual flag instead: the TS transport_scrambling_control bits (top two of TS-header byte 3 = byte 7 of the aligned unit, inside the clear seed). AACS encrypts whole aligned units, so this one packet's TSC reflects the unit. decrypt_unit now clears the TSC bits of every packet on the way out so the result is valid unscrambled TS. Tests updated to the TSC flag.
123 lines
3.6 KiB
Rust
123 lines
3.6 KiB
Rust
//! Regression tests for Pass N (patch) fix — decrypt key inversion bug.
|
|
//!
|
|
//! Tests that decrypt_sectors is invoked correctly when opts.decrypt=true.
|
|
//! The 2026-05-03 bug at `libfreemkv/src/disc/mod.rs:1938-1942` inverted
|
|
//! the decrypt key arms, causing patch to pass DecryptKeys::None on encrypted discs.
|
|
|
|
use libfreemkv::{aacs, decrypt::DecryptKeys};
|
|
|
|
/// Test: decrypt_sectors with AACS keys actually decrypts units.
|
|
#[test]
|
|
fn decrypt_sectors_with_aacs_keys_works() {
|
|
// Build an encrypted aligned unit
|
|
let mut unit = vec![0xFFu8; aacs::ALIGNED_UNIT_LEN];
|
|
|
|
// Set encryption flag (bits 6-7 of byte 0)
|
|
unit[0] |= 0xC0;
|
|
|
|
// Fill with recognizable pattern
|
|
for (i, byte) in unit
|
|
.iter_mut()
|
|
.enumerate()
|
|
.take(aacs::ALIGNED_UNIT_LEN)
|
|
.skip(1)
|
|
{
|
|
*byte = ((i * 3 + 7) & 0xFF) as u8;
|
|
}
|
|
|
|
let unit_key: [u8; 16] = [0xAAu8; 16];
|
|
|
|
// Encrypt the unit using AACS algorithm
|
|
aacs::decrypt_unit(&mut unit, &unit_key); // decrypt_unit is idempotent on already-encrypted data
|
|
|
|
// Now we have encrypted data - create DecryptKeys with actual keys
|
|
let keys = DecryptKeys::Aacs {
|
|
unit_keys: vec![(0u32, unit_key)],
|
|
read_data_key: None,
|
|
};
|
|
|
|
// decrypt_sectors should handle this without error
|
|
let result = libfreemkv::decrypt::decrypt_sectors(&mut unit, &keys, 0);
|
|
|
|
assert!(
|
|
result.is_ok(),
|
|
"decrypt_sectors with AACS keys should not error"
|
|
);
|
|
}
|
|
|
|
/// Test: decrypt_sectors with DecryptKeys::None is a no-op.
|
|
#[test]
|
|
fn decrypt_sectors_with_none_keys_is_noop() {
|
|
let mut sector = vec![0x42u8; 2048];
|
|
|
|
let keys = DecryptKeys::None;
|
|
let result = libfreemkv::decrypt::decrypt_sectors(&mut sector, &keys, 0);
|
|
|
|
assert!(result.is_ok());
|
|
assert_eq!(
|
|
§or[..],
|
|
&[0x42u8; 2048][..],
|
|
"DecryptKeys::None should not modify buffer"
|
|
);
|
|
}
|
|
|
|
/// Test: decrypt_sectors with CSS keys descrambles sectors.
|
|
#[test]
|
|
fn decrypt_sectors_with_css_keys_works() {
|
|
let mut sector = vec![0xFFu8; 2048];
|
|
|
|
// 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 keys = DecryptKeys::Css { title_key };
|
|
|
|
// Descramble (CSS uses same operation for encrypt/decrypt)
|
|
libfreemkv::decrypt::decrypt_sectors(&mut sector, &keys, 0).unwrap();
|
|
|
|
// Flag should be cleared
|
|
assert_eq!(sector[0x14] & 0x30, 0x00, "CSS flag should be cleared");
|
|
}
|
|
|
|
/// Test: AACS unit encryption detection works.
|
|
#[test]
|
|
fn aacs_encryption_flag_detection() {
|
|
let mut unit = vec![0u8; aacs::ALIGNED_UNIT_LEN];
|
|
|
|
// The encryption flag is the TS transport_scrambling_control (top two bits
|
|
// of byte 7), not byte 0's copy-control bits.
|
|
assert!(!aacs::is_unit_encrypted(&unit));
|
|
|
|
unit[7] = 0x40; // TSC = 01
|
|
assert!(aacs::is_unit_encrypted(&unit));
|
|
|
|
unit[7] = 0x80; // TSC = 10
|
|
assert!(aacs::is_unit_encrypted(&unit));
|
|
|
|
unit[7] = 0xC0; // TSC = 11
|
|
assert!(aacs::is_unit_encrypted(&unit));
|
|
|
|
// Byte 0 copy-control bits must NOT count as encryption.
|
|
unit[7] = 0x00;
|
|
unit[0] = 0xC0;
|
|
assert!(!aacs::is_unit_encrypted(&unit));
|
|
}
|
|
|
|
/// Test: DecryptKeys::is_encrypted() correctly identifies encrypted state.
|
|
#[test]
|
|
fn decrypt_keys_is_encrypted_variants() {
|
|
let none = DecryptKeys::None;
|
|
assert!(!none.is_encrypted());
|
|
|
|
let aacs = DecryptKeys::Aacs {
|
|
unit_keys: vec![],
|
|
read_data_key: None,
|
|
};
|
|
assert!(aacs.is_encrypted());
|
|
|
|
let css = DecryptKeys::Css {
|
|
title_key: [0u8; 5],
|
|
};
|
|
assert!(css.is_encrypted());
|
|
}
|