Skip to content

v0.1.2

Choose a tag to compare

@MagicalTux MagicalTux released this 30 May 02:35
· 20 commits to master since this release
ae53c04

Other

  • Content Hash Table parse + Hash-Unit integrity verify (BD-Prerecorded §2.3)
  • AACS_Verify integration for §3.2.5.1.2/.3/.8 signature records
  • paraphrase external-implementation citations in src/
  • READ_DISC_STRUCTURE Format 0x81/0x82/0x83 sub-payload constructors + parsers (AACS Common §4.14.3.2 / §4.14.3.3 / §4.14.3.4)
  • Phase D: Type-4 MKB + Key Conversion Data post-processing (AACS Common 0.953 §3.2.5.1.4 + BD-Prerecorded §3.8)

Added — Content Hash Table parsing + Hash-Unit integrity verification (BD-Prerecorded §2.3)

New cht module implementing the Content Hash Table (CHT) integrity
layer per AACS BD-Prerecorded Final 0.953 §2.3 — the per-Hash-Unit
SHA-1 check a Licensed Player runs over \BDMV\STREAM Clip AV data:

  • cht::ContentHashTable::parse(bytes, number_of_digests, number_of_hash_units) -> Result<ContentHashTable> — parses a
    ContentHash00N.tbl per Table 2-2: a header of Number_of_Digests
    12-byte ClipDescriptor records (Starting_HU_Num / Clip_Num /
    HU_Offset_in_Clip, all 32-bit uimsbf) followed by
    Number_of_HashUnits 8-byte (bslbf) Hash Values. The two counts
    are NOT stored in the table file — they come from the per-layer
    Content Certificate (Table 2-1), so the parser takes them as
    arguments. Trailing 00-padding is tolerated (authoring/mastering
    rule).
  • cht::ContentHashTable::verify_hash_unit(index, hash_unit_bytes) -> Result<()> — recomputes Hash_Value = [SHA-1(Hash_Unit)]_lsb_64 (§2.3.2.1, least-significant 8 bytes of
    the 20-byte SHA-1 digest) and compares it to the stored Hash Value.
    Per §2.3.2.1 the encrypted on-disc bytes are hashed, so this
    verifies integrity without a Title Key.
  • cht::hash_value_of_unit(hash_unit) -> [u8; 8] — the standalone
    [SHA-1(·)]_lsb_64 primitive (e.g. for a CHT author).
  • cht::ContentHashTable::{len, is_empty} — a zero-byte CHT is
    valid for a layer with no Clip AV file ≥ 96 Logical Sectors.
  • Size constants: HASH_UNIT_SIZE (96 × 2048 = 196608 bytes =
    exactly 32 Aligned Units, and 7 of them = the spec's 1344 KB
    minimum), LOGICAL_SECTOR_SIZE (2048), LOGICAL_SECTORS_PER_HASH_UNIT
    (96), HASH_VALUE_SIZE (8).

New error variants:

  • AacsError::BadHashUnitLength(usize) — supplied Hash Unit was not
    exactly 196608 bytes.
  • AacsError::ContentHashMismatch { index } — recomputed
    [SHA-1(Hash_Unit)]_lsb_64 did not match the stored Hash Value.

Tests: 9 cht unit tests (size arithmetic, lsb-64 extraction,
header/body roundtrip, tamper / wrong-length / out-of-range / short-
buffer rejection, trailing-padding tolerance, zero-byte table) + 4
tests/synth_cht.rs integration tests (incl. one that builds a Hash
Unit from 32 freshly-encrypted Aligned Units and verifies the
encrypted bytes with no Title Key, then detects a single-byte flip).
All synthetic — no disc-derived material.

No docs gap: §2.3 (Table 2-2 syntax, §2.3.1 Hash-Unit geometry,
§2.3.2.1 [SHA-1(Hash_Unit)]_lsb_64) and the §2 "Logical Sector =
2048 bytes" definition in AACS_Spec_BD_Prerecorded_Final_0_953.pdf
are unambiguous. Standalone (--no-default-features) build still
passes.

Added — AACS LA signature verification on MKB records (§3.2.5.1.2 / §3.2.5.1.3 / §3.2.5.1.8)

Wires the existing ecdsa::verify (AACS_Verify) primitive into the
MKB parser so callers that hold the AACS LA public key can check the
three MKB signatures the spec defines:

  • Mkb::verify_end_of_block_signature(&self, original_bytes, aacs_la_pub) -> Result<()> — verifies the End-of-Media-Key-Block
    Record signature per Common spec §3.2.5.1.8
    (AACS_Verify(AACS_LApub, Signature Data, MKB), where MKB is the
    byte range up to but not including the End-of-MKB record).
  • Mkb::verify_host_revocation_list(&self, original_bytes, aacs_la_pub) -> Result<()> + the parallel
    verify_drive_revocation_list — verify the per-signature-block
    ECDSA signatures inside the HRL / DRL records (Common spec
    §3.2.5.1.2 / §3.2.5.1.3). Each block's signed-data range is the
    cumulative prefix "Type-and-Version Record || HRL/DRL record bytes
    up to the byte immediately preceding this signature", so block N's
    signature transitively also covers blocks 1..=N−1.
  • Mkb::end_of_block_signature: Option<[u8; 40]> + the new
    RevocationSignatureBlock { entries_in_block, entries, signature } struct surfaced on Mkb::host_revocation_blocks /
    drive_revocation_blocks — both expose the raw 40-byte ECDSA
    signature(s) for callers that want to feed them to an external
    verifier.
  • Mkb::type_and_version_raw: Vec<u8> — the on-wire bytes of the
    mandatory Type-and-Version Record (header included), preserved by
    the parser because §3.2.5.1.2 requires it as the first portion of
    the HRL / DRL signed data.

The parser still tolerates revocation blocks whose trailing signature
field is truncated (per §3.2.5.1.2 final paragraph — "hosts are
required to store only the data being signed for the first signature
block, but not required to store the signature itself"); the verifier
surfaces AacsError::MkbSignatureMissing rather than panicking on a
None-signature block.

This crate ships no AACS LA public key. AACS LA distributes
AACS_LApub to licensees only, so the verifier takes a &Point
parameter; callers obtain the key out-of-band. A non-licensed
deployment can still call the verifiers against a self-issued LA
identity (e.g. for end-to-end MKB-authoring tests).

New error variants:

  • AacsError::MkbSignatureMissing — the requested signature record
    was absent or its payload was not the expected 40 bytes.
  • AacsError::MkbSignatureInvalid — the signature was present but
    AACS_Verify rejected it under the supplied public key.

Tests:

  • 8 new mkb unit tests covering: End-of-MKB sign-then-verify
    roundtrip, wrong-key rejection, prefix-tamper rejection, no-record
    → MkbSignatureMissing, non-40-byte payload → MkbSignatureMissing,
    single-block HRL verify roundtrip, DRL wrong-key rejection,
    no-signature-block-stored truncation tolerance, and a two-block
    cumulative-prefix HRL chain.
  • 2 new integration tests in tests/synth_round1_mkb_sig.rs that
    build a full Type-and-Version + HRL + Verify-Media-Key +
    End-of-MKB byte stream through the public API and confirm both
    verifiers accept the legitimate signatures + reject a tampered
    buffer.

No docs gap; the spec text for all three signed-data ranges
(docs/container/aacs/AACS_Spec_Common_Final_0953.pdf §3.2.5.1.2
final paragraph, §3.2.5.1.3 second paragraph, §3.2.5.1.8 second
paragraph) is unambiguous. Standalone (--no-default-features)
build still passes.

Added — READ_DISC_STRUCTURE Format 0x81/0x82/0x83 sub-payloads

Closes the §4.14.3.2 (Pre-recorded Media Serial Number / PMSN),
§4.14.3.3 (Media Identifier), and §4.14.3.4 (Media Key Block Pack)
sub-payload gap that Phase B left as Format-Code constants without
matching CDB constructors, response decoders, or MockDrive service.

  • ReadDiscStructure::aacs_media_id(agid) — CDB builder for
    Format Code 0x82 (Media Identifier) per AACS Common §4.14.3.3
    Table 4-17. Same on-wire layout as the Volume Identifier and PMSN
    reads: 36-byte response with [length=0x0022:u16][reserved:u16] [Media ID:16][MAC:16].
  • parse_media_serial_response + MediaSerialNumberResponse
    — Format 0x81 decoder per Table 4-16. The MAC byte field is
    Dm = CMAC(BK, PMSN) per §4.5 step 3.
  • parse_media_id_response + MediaIdentifierResponse
    Format 0x82 decoder per Table 4-17 / Table 4-15 layout. The MAC
    is Dm = CMAC(BK, MediaID) per §4.6 step 3.
  • parse_mkb_pack_response + MkbPackResponse — Format
    0x83 decoder per Table 4-18, with variable-length pack body up
    to 32,768 bytes. Returns the total_packs count alongside the
    pack data. (Per §4.14.3.4 the MKB itself is not bus-encrypted —
    the spec note is explicit about this.)
  • MockDrive grows four new fields — media_serial_number,
    media_serial_mac, media_identifier, media_id_mac — populated
    with deterministic patterns by with_test_fixture(). The
    READ_DISC_STRUCTURE_OPCODE dispatch arm now serves Format
    0x81 and 0x82 with the same auth-vs-static MAC selection the
    Volume ID path already uses: when auth.bus_key is Some, the
    mock recomputes Dm = CMAC(BK, ID) per §4.5 / §4.6; otherwise it
    returns the fixture-MAC bytes.

New tests:

  • 7 new unit tests in mmc.rs: PMSN/Media-ID CDB-byte-layout
    invariants, response-parser round trips, length-field /
    truncated-payload rejection, and the MKB-pack length-counting
    semantics.
  • 4 new integration tests in tests/synth_phaseb_mmc.rs: PMSN +
    Media-ID MockDrive end-to-end round trips, an unknown-format
    rejection, and an MKB-pack hand-built-wire round trip (since the
    MKB body is not bus-encrypted, hand-stuffed wire bytes are the
    right level for the Phase B sub-payload layer).

No new dependencies, no oxideav-core API change, no docs-gap (all
three sub-payloads are in docs/container/aacs/
AACS_Spec_Common_Final_0953.pdf Tables 4-16 / 4-17 / 4-18). The
crate's standalone (no-default features) build still passes.

Added — Phase D: Type-4 MKB + Key Conversion Data post-processing

Wires the AACS Common spec §3.2.5.1.4 + BD-Prerecorded §3.8 "Type-4
MKB / Media Key Precursor" path into the high-level AacsVolume
pipeline. Type-4 MKBs emit a Media Key Precursor K_mp from the
Subset-Difference walk rather than the Media Key directly; devices
that are required to use Key Conversion Data combine K_mp with the
disc's 16-byte KCD payload via K_m = AES-G(K_mp, KCD) before VUK
derivation. The KCD itself is sourced out-of-band (from the BD-ROM
KCD-Mark, surfaced in oxideav-aacs via the | KCD | row of a
KEYDB.cfg file → DiscRecords::kcd).

  • subdiff::apply_key_conversion_data(kmp, kcd) — the
    K_m = AES-G(K_mp, KCD) primitive. Equivalent to
    aes::aes_g(kmp, kcd); named separately for readability at the
    call site. Re-exported from the crate root.
  • AacsVolume::derive_vuk_from_device_key_with_kcd(device, vol_id, kcd) — Type-4-aware VUK derivation implementing the spec's
    "verify-then-apply-KCD" decision tree:
    1. SD-walk to obtain the raw precursor (or Media Key for Type-3).
    2. If that value already verifies under the Verify Media Key Record
      (the spec's "old MKB" rule, §3.2.5.1.4 final paragraph), adopt
      it as K_m and skip KCD application — even if a KCD was
      supplied.
    3. Otherwise apply AES-G(K_mp, KCD) and re-verify; failure
      surfaces MediaKeyVerificationFailed.
  • AacsVolume::derive_media_key_from_device_key(device)
    exposed cryptographic primitive returning the raw SD-walk output
    (precursor for Type-4, Media Key for Type-3). Lets callers make
    the verify/KCD decision themselves rather than going through the
    higher-level _with_kcd helper.
  • Mkb::is_verified_media_key(km) -> bool — boolean variant of
    the existing verify_media_key, returning false (rather than
    MissingVerifyMediaKeyRecord) when the 0x81 record is absent.
    The Type-4 decision path needs this distinction.
  • MkbType::requires_kcd() + MkbType::as_u32() — predicate
    • wire-format inverse of the parser's from_u32.

derive_vuk_from_device_key is refactored on top of
derive_media_key_from_device_key + the existing
Mkb::verify_media_key so the Type-3 path is byte-identical to the
prior implementation.

New tests/synth_phased_kcd.rs (7 tests) pins:

  • Type-4 + valid KCD round-trip → matching VUK.
  • Type-4 + wrong KCD → MediaKeyVerificationFailed.
  • Type-4 "old MKB" precursor-verifies-directly fallback (KCD ignored
    even when supplied).
  • Type-4 without KCD when KCD was needed → error.
  • Type-3 with a stray KCD argument → KCD ignored.
  • KCD record loads from a synthetic KEYDB.cfg.
  • Synthetic SD configuration sanity check.

Plus 5 new mkb unit tests covering MkbType::requires_kcd,
MkbType::as_u32 round-trip, and is_verified_media_key edges.

No new external dependencies, no docs-gap (Common spec §3.2.5.1.1 +
§3.2.5.1.4 + BD-Prerecorded §3.8 are all in docs/container/aacs/),
no oxideav-core API change. The crate's standalone (no-default
features) build still passes.