aacs: parse HD DVD VTKF title keys at the spec's 36-byte stride

The HD DVD Title Key File (VTKF*.AACS) stores 64 title-key entries of 36 bytes
each — 1-byte BIFO + 3 reserved + 16-byte encrypted key + 16-byte binding MAC —
per AACS "HD DVD and DVD Pre-recorded Book" Table 3-8, confirmed byte-exact
against real discs (Freedom VTKF090, Dukes VTKF000: every 36-byte slot has
BIFO=0x80, a clean key, and a 0xFF binding MAC).

The parser used a 32-byte stride (a 12-byte pad in place of the 16-byte binding
MAC) with flag-based termination. That aligns entry #1 (key at offset 132, where
both strides agree) but drifts +4 bytes per entry after it and never terminates
(the previous entry's 0xFF MAC reads as a set present-flag), so it recovered a
correct key only for single-CPS-unit discs and garbage for CPS unit >=2. Every
multi-title HD DVD (Freedom, Harry Potter) was affected.

Fix: 36-byte stride, iterate the fixed 64 slots, take slots whose BIFO AV_FLG
(bit 7) is set, key at offset 4, slot index = CPS unit (skip empty slots rather
than terminate so a gap can't renumber later keys), and never read the trailing
16-byte TKF MAC as a key. Tests rebuilt on the real layout, including a full
64-entry file.

Also correct the VTKF-selection TODO in mod.rs: the AACS HD DVD Book gives the
selector explicitly (match the TKF's PLAYLIST_NAME field to the active
playlist), not the "validate against an encrypted unit" placeholder.

Reconciled against the new HD DVD reference (freemkv.org/docs/hddvd/); the spec
source is archived in freemkv-private/spec/.
This commit is contained in:
Matthew Jackson
2026-07-20 11:57:13 -07:00
parent c1f1593003
commit 2274423a6f
3 changed files with 111 additions and 57 deletions
+10 -6
View File
@@ -126,12 +126,16 @@ pub(crate) fn role_paths(udf: &crate::udf::UdfFs, role: AacsRole) -> Vec<String>
// VTKF000 (Freedom ships VTKF090 + VTKF100). Sorted for a
// deterministic try order.
//
// TODO(hddvd-encrypted): when a disc carries MULTIPLE VTKF
// variants, the CORRECT one is chosen by validating its
// VUK-derived key against a real encrypted unit — not by
// first-that-reads (all read). Wire that selection here once a
// genuinely encrypted HD DVD image exists to validate against
// (see `content::aacs_unit_encrypted` UNVERIFIED-HDDVD-DECRYPT).
// TODO(hddvd-playlist): each VTKF%%%.AACS is bound to ONE
// playlist (VPLST%%%.XPL) — the AACS HD DVD Book gives the
// selector explicitly: match the TKF's 12-byte PLAYLIST_NAME
// field (bytes 0x10..0x1C) to the playlist of the title being
// decrypted; "unless the names are identical, the Title Keys in
// this TKF must not be used." Today read_first just takes the
// first that reads, which is correct only for a single-playlist
// disc. Thread the active playlist name here (owned by the HD
// DVD enumerator) and pick the name-matched VTKF once a
// multi-playlist encrypted disc is available to validate against.
let mut names: Vec<&str> = dir
.entries
.iter()