Security - Remove the unsafe Vec::set_len over recycled sector buffers — UB reachable from safe code (GHSA-j8ww-f5fg-9pmh, low). Uses Vec::resize; pooling unchanged. CI / tooling - clippy on stable with the MSRV single-sourced from Cargo.toml; cargo-deny dependency-audit gate with first-party git sources allowed (#15); audience-based comment-guard (#17). Docs - README (keydb ownership, BD-J parser count), drive-access transport/reset notes, mux timeline module-doc links.
6.8 KiB
encrypt.rs — relocated rationale
Overflow prose that would not fit under ci/comment-guard.py's caps,
moved here per file with a // See docs/encrypt.md — <topic> pointer
left at the original site.
HandshakeResult::drive_unlocked
True when the VID came from an unlocker (in freemkv-unlock) that
unlocked the drive. Such a drive serves CLEAR content, so AACS bus
encryption is already removed AT THE DRIVE — the same end state a
successful cert handshake's read_data_key provides, just via
firmware instead of the AKE. The bus-key gate MUST credit this as a
valid bus-removal: bus encryption is unremovable only when NEITHER the
firmware unlocked the drive NOR the cert handshake yielded a bus key.
Without this, a SUCCESSFUL unlock (VID present, read_data_key: None)
paradoxically trips the gate and blocks ALL key resolution (incl. the
online source).
bus_encryption_removed
Single source of truth for "is AACS bus encryption gone for this
scan?". The gate asks ONLY this — if !removed { error } — never
enumerating cases. Bus encryption is gone when ANY of these holds:
the disc never had it; file/ISO reads (content already clear at read
time); an unlocker unlocked the drive (it serves clear content); or
the cert handshake produced the bus key. Add a NEW removal mechanism
HERE, never in the gate.
AacsCertUnlocker
libfreemkv-side driver for the AACS cert route. It owns the host-cert
collection (a keysource concern that stays in libfreemkv) and then
dispatches the actual mutual-auth to the freemkv-unlock AACS
unlocker via the crate::unlock_bridge. The firmware (drive-prep) and
CSS routes dispatch the same way at their own call sites; this one
carries the host certs.
AacsCertUnlocker::authenticate
Runs the host-certificate mutual-auth handshake: collect
non-compiled-in host certs from the key sources + credentials, then
hand them to the AACS unlocker (via the freemkv-unlock dispatch),
which tries each cert (wedge-guarded) and on success yields the
Volume ID + read_data_key (the AACS 2.0 bus key). Returns a
CertUnlockFailure on every no-VID outcome.
unlock_error_to_error
Maps a CertUnlockFailure back to the Error variant
do_handshake_cert has always surfaced, so scan_with's rendering
and the path-1 disc-hash → VUK fallback are byte-for-byte unchanged.
(NoHostCert keeps the <no host cert> sentinel.)
handshake_has_volume_id
Did the cert handshake actually carry a Volume ID? Extracted so it can
be tested as a VALUE. It only ever reaches an operator as the
has_volume_id field of the bus_key_unavailable warn, and asserting
on a tracing field means installing a capturing subscriber — which
is thread-local, while tracing's callsite-interest cache is global.
Those two facts race: the test failed roughly one run in ten under the
full parallel suite while passing every time in isolation, and
serialising the captures was not enough because the cache can be
re-evaluated against the process default rather than the thread-local
dispatch.
A predicate this small does not need a subscriber to verify. The
polarity is the whole point: an == here would tell an operator a VID
was absent on exactly the discs where one was present.
Disc::do_handshake
SCSI handshake — drives the VID-acquisition flow and returns a
structured HandshakeResult for downstream key resolution.
VID acquisition runs through do_handshake_cert, which first uses the
OEM VID a firmware unlocker may have stashed at drive init() (a
drive-functionality capability decoupled from the host cert + HRL) and
falls back to the cert-based mutual-auth handshake (dispatched to the
freemkv-unlock AACS unlocker) when none is present. The cert path
also yields read_data_key, required for AACS 2.0 bus decryption.
Returns (handshake, error):
(Some(_), None)— VID acquired(None, Some(_))— specific failure mode (AacsHostCertRejectedorAacsVidUnavailable)(None, None)— handshake not attempted (no keydb; resolution will proceed with VID=zero and rely on path 1 disc-hash → VUK lookup)
Disc::do_handshake_cert
Cert-based AACS handshake — the cert route for VID acquisition.
Before running the cert mutual-auth, this checks for an OEM Volume ID
a firmware unlocker stashed at drive init(). Such an unlocker
unlocks drive functionality, not just the disc: VID retrieval via
the drive's OEM CDB is a capability separate from unlock. When one
served a VID, we use it and SKIP the cert handshake entirely — the
OEM path gets the VID without the host certificate + HRL, decoupling
VID from the cert chain. The OEM path yields no read_data_key (no
bus-key is derived); AACS 2.0 content needing read_data_key for bus
decryption must still use the cert path, so an unlocker with no OEM
VID capability returns None and we fall through to cert auth
unchanged.
Disc::collect_host_certs
Collect every AACS host cert the caller carries, from BOTH the
explicit DriveCredentials and the key-source layer
(crate::KeySource::host_certs across each source), unioned. Host
certs are keysource-served, never compiled in; this is the one place
the OEM cert route gathers them. An empty result is the graceful
no-cert signal (the caller turns it into Error::AacsNoHostCert).
mkb is the disc's MKB generation when known, forwarded to each
source's crate::KeySource::host_certs so a source MAY return only
generation-appropriate certs (the default ignores it).
Disc::resolve_vid_only
Build a keys-free AACS state that carries only the Volume ID (+
version metadata), for callers that resolve Unit Keys out-of-band and
have disabled the local keydb. The VID is on-disc content read during
the handshake; preserving it here lets the out-of-band path use it. No
keys are present (unit_keys empty, vuk None), so the disc reports
as "encrypted, no keys" until the caller re-scans with a resolved Unit
Key.
build_mkb (test helper)
An MKB with one Type-and-Version record (type 0x10) carrying the version as BE u32 at record offset 8, followed by a recorded EOF record then trailing zero padding. mkb_content_len walks records and stops at the first padding (type 0) byte (aacs/inf.rs).
resolve_vid_only_no_cert_defaults_version_uhd (test)
No content cert at all → version defaults to UHD (major 2), matching
read_aacs_version so the scanned AacsState.version and the
out-of-band fetch agree on the Unit_Key_RO stride (audit #4: a wrong
BD-vs-UHD guess mis-parses unit keys). bus_encryption false
(unreadable → off).
Skipped/deferred test coverage
Tests for read_vid_oem (response parsing) are skipped in this file
since Drive construction requires a live transport; parsing branches
are exercised through read_vid_oem's callers in integration. The
collect_host_certs tests union DriveCredentials with the
key-source layer; empty means the route fails gracefully
(AacsNoHostCert).