1.2.0: single MKB framing walker + AACS resolve hardening

- mkb_records() as the one record-framing iterator; rebuild walk_mkb,
  find_record_body, mkb_find_subdiff_records, mkb_content_len, mkb_version,
  mkb_type_raw, mkb_find_mk_dv on it (D4).
- resolve_vid_only / read_aacs_version default to UHD (+warn) on a missing
  content cert instead of BD; route MKB through bounded read_mkb_content.
- AacsVersion major()/from_major() + AACS_MAJOR_BD/UHD as the stride source;
  table + stride-discriminating regression tests.
- read_encrypted_units probes 8 evenly-spaced points per extent (off-midpoint
  scrambled content now sampled); decrypt source-zero mask uses PKT.
This commit is contained in:
Matthew Jackson
2026-06-28 22:28:09 -07:00
parent 4d6f5c0a98
commit a731e7b26b
6 changed files with 175 additions and 123 deletions
+5 -1
View File
@@ -327,10 +327,14 @@ impl Disc {
.as_deref()
.and_then(aacs::parse_content_cert);
let bus_encryption = cc.as_ref().map(|c| c.bus_encryption).unwrap_or(false);
// No-cert default = UHD (V20 stride), matching `read_aacs_version` so the
// scanned `AacsState.version` and the out-of-band fetch agree. A wrong
// stride on the main resolve path fails loudly (sample validation) rather
// than silently, so the conservative V20 default is safe here too.
let version = cc
.as_ref()
.map(|c| c.version.major())
.unwrap_or(aacs::AACS_MAJOR_BD);
.unwrap_or(aacs::AACS_MAJOR_UHD);
// OEM bus-key gate (wrong-keys guard). A bus-encrypted disc (Content
// Certificate bus-encryption bit set) still carries bus encryption on
+20 -5
View File
@@ -1614,17 +1614,32 @@ impl Disc {
/// [`crate::aacs::AACS_MAJOR_UHD`]) from the content certificate. Drives the
/// `Unit_Key_RO.inf` parse stride (48-byte V10 vs 64-byte V20/V21), so the
/// out-of-band key-fetch path parses `enc_title_keys` at the right stride (a
/// server VUK then derives the correct unit keys). Defaults to BD (V10) when
/// no content certificate is present.
/// server VUK then derives the correct unit keys).
///
/// When no content certificate is readable/parseable, defaults to **UHD
/// (V20, 64-byte stride)** — the conservative choice the pre-1.2.0 fetch path
/// hardcoded — and logs it: a wrong stride here folds a server VUK against
/// mis-strided title keys (silent wrong unit keys), so a missing cert must
/// not quietly pick the V10 stride for a UHD disc.
fn read_aacs_version(reader: &mut dyn SectorSource, udf_fs: &udf::UdfFs) -> u8 {
udf_fs
match udf_fs
.read_file(reader, crate::aacs::PATH_CONTENT_CERT)
.or_else(|_| udf_fs.read_file(reader, crate::aacs::PATH_CONTENT_CERT_ALT))
.ok()
.as_deref()
.and_then(crate::aacs::parse_content_cert)
.map(|c| c.version.major())
.unwrap_or(crate::aacs::AACS_MAJOR_BD)
{
Some(c) => c.version.major(),
None => {
tracing::warn!(
target: "freemkv::disc",
phase = "scan_aacs_version",
"no readable AACS content certificate; defaulting to the V20/UHD \
Unit_Key_RO stride (a VUK-from-server path would otherwise mis-stride)"
);
crate::aacs::AACS_MAJOR_UHD
}
}
}
/// Read the AACS MKB's real record stream — NOT its zero padding.