v0.1.1
Other
- Phase C: Drive-Host AKE + ECDSA-secp160r1 + Bus Key (AACS Common 0.953 §4.3)
- Phase B — SCSI MMC drive command layer (REPORT_KEY / SEND_KEY / READ_DISC_STRUCTURE)
- parse |-leader DK / PK / HC / DC / DISCID-scoped records (Phase A)
- disc_id is SHA-1(Unit_Key_RO.inf), not SHA-1(Volume_ID)
- disc_id_for_volume_id — SHA-1(volume_id) for KEYDB.cfg lookup
- fmt + tests: align integration tests with permissive parse
- tolerate sector-padding zeros after the End-of-MKB record
Added — Phase C: Drive-Host Authentication & Key Exchange (AKE)
New ec, ecdsa, and ake modules implementing the AACS Common
Final 0.953 §4.3 "AACS Drive Authentication Algorithm" (Figure 4-9)
end-to-end on top of the Phase B MMC layer. All cryptography is
clean-room from the spec's published math (Table 2-1 curve parameters,
§2.3 ECDSA, §2.1.5 SHA-1, §2.1.6 CMAC); no external crypto-library
source (RustCrypto / OpenSSL / libaacs / …) was consulted. The
openssl CLI was used only as an opaque test-vector oracle, and the
ECDSA path is additionally cross-checked bit-exact against an
independent Python big-int reference.
ecmodule — a 160-bit big-integer (U160, fiveu32limbs)
and short-Weierstrass curve overGF(p)for the AACS curve
(Table 2-1:a = -3, primep, base pointG, ordern). Affine
add/double/is_on_curveplus a Jacobian-coordinate
mul_scalar(single final inversion). 40-byte EC-point encoding
x(20) || y(20).ecdsamodule —AACS_Sign/AACS_Verify(X9.62 / FIPS
186-2) with a clean-room FIPS 180-2 SHA-1 digest; 40-byter || s
signatures. A deterministickhelper (AES-H based, not RFC 6979)
makes the synthetic test handshake reproducible. SHA-1 validated
against the FIPS 180-2abc/ empty / two-block vectors; the full
sign/verify against an independent Python reference vector.aes::aes_128_cmac— AES-128-CMAC (NIST SP 800-38B), validated
against the SP 800-38B Appendix D.1 example MACs (empty / 1-block /
partial / full-message).akemodule:Certificate::parse+verify_signaturefor the 92-byte Drive
(Table 4-1) / Host (Table 4-2) certificates
(Type Flags Length ID Reserved PubKey(40) Sig(40)), with the
Cert_sig = bytes 52..91signed overCert = bytes 0..51.build_signed_certificateto mint synthetic LA-signed certs.host_authenticate— the §4.3 Host state machine driving any
DriveCommandtransport through AGID → Host Cert Challenge →
Drive Cert Challenge → Drive Key → Host Key → Bus Key.DriveAuthState— the §4.3 drive side (verify Host Cert + Hsig,
signDsigoverHn || Dv, deriveDk·Hv), wired into the
Phase BMockDrivevia its newauthfield.bus_key_from_point—BK = [x-coordinate of shared point] lsb_128(§4.3 steps 28/29).read_verified_volume_id— §4.4 Volume ID transfer with
Dm = CMAC(BK, Volume_ID)host-side verification.
- New
AacsErrorvariants:
DriveCertSignatureInvalid,HostCertSignatureInvalid,
DriveSignatureInvalid,HostSignatureInvalid,VolumeIdMacInvalid. - New integration suite
tests/synth_phasec_ake.rs(5 tests): full
synthetic-cert AKE round-trip with matching Host/Drive Bus Keys,
§4.4 Volume ID verification, and rejection of a wrong-LA Drive cert,
a wrong-LA Host cert, and a tampered Volume ID MAC.
Note on the Bus Key KDF. The task brief mentioned a possible
"AES-G / SHA-1 KDF" for the Bus Key; AACS Common Final 0.953 §4.3
steps 28/29 in fact define the Bus Key directly as the least
significant 128 bits of the x-coordinate of the shared ECDH point
(Dk·Hv / Hk·Dv) — no AES-G/SHA-1 post-derivation. This module
implements per the spec; the §4.4+ ID transfers then key AES-CMAC
under that Bus Key.
Added — Phase B: SCSI MMC drive-command wire layer
New mmc module implementing the byte-level encoding/parsing for the
three SCSI MMC commands an AACS host needs to converse with a Licensed
Drive. Wire formats are taken from the publicly-hosted T10 working
drafts now staged at docs/container/aacs/mmc/ (MMC-6 r02g, SPC-3 r23)
cross-referenced against AACS Common Final 0.953 §4.1–§4.14. No
external library source (libaacs / libbluray / etc.) was consulted.
- Typed CDB constructors, each emitting a 12-byte
[u8; 12]block
per MMC-6 Tables 381 / 513 / 599:ReportKey::{aacs_agid, aacs_drive_cert_challenge, aacs_drive_key, aacs_drive_cert, aacs_invalidate_agid}.SendKey::{aacs_host_cert_challenge, aacs_host_key, aacs_invalidate_agid}.ReadDiscStructure::{aacs_volume_id, aacs_media_serial, aacs_media_key_block_pack}.parse_cdb()inverse for each, used by the synthetic mock drive
and by tests.
- AACS sub-payload codecs for the AKE round-trip:
parse_report_key_agid/parse_report_key_drive_cert_chal/
parse_report_key_drive_key/parse_report_key_drive_cert—
drive-to-host responses per AACS Common Tables 4-7, 4-8, 4-9 and
MMC-6 Table 531.parse_volume_id_response— 36-byte READ DISC STRUCTURE
Format 0x80 reply per AACS Common Table 4-15
([u16=0x0022][rsvd:u16][Volume ID:16][MAC:16]).build_send_key_host_cert_chal/build_send_key_host_keyand
theirparse_*inverses — host-to-drive parameter lists per
AACS Common Tables 4-24, 4-25 (Hn || Cert_handHv || Hsig
respectively).
DriveCommandtrait abstracting the SCSI pass-through surface
so platform-specific back-ends (macOSIOSCSITaskDeviceInterface,
LinuxSG_IO, WindowsIOCTL_SCSI_PASS_THROUGH_DIRECT) can be
written against a single contract once Phase C lands. Carries a
DataDirectionenum +ScsiResponse { status, data }.MockDrivein-process fixture implementingDriveCommand,
populated with a deterministic synthetic Drive Certificate /
Volume ID / nonces so tests can assert exact byte layouts.
Public re-exports added to lib.rs (mmc module + the typed
structures and parsers).
Documentation gaps surfaced
- The workspace
docs/container/aacs/mmc/README.md"AACS commands at
a glance" section confuses two different command surfaces: it lists
AACS Volume ID under "REPORT KEY Key Class=0x02 Key Format 0x12",
but per MMC-6 Table 525 the REPORT KEY Key Class 0x02 Key Format
table only defines 0x00 / 0x01 / 0x02 / 0x20 / 0x21 / 0x38 / 0x3F.
The Volume Identifier in fact ships viaREAD_DISC_STRUCTURE
Format Code 0x80 (MMC-6 Table 384 / AACS Common Table 4-15). This
Phase B implementation follows the spec tables; the README would
benefit from a follow-up edit clarifying which list belongs to
which command.
Tests
- 5 new
mmcunit tests pinning the CDB byte layouts (opcode, Key
Class, Allocation/Parameter-List Length packing, AGID bit packing). - 13 new
tests/synth_phaseb_mmc.rsintegration tests round-tripping
the AGID / Drive Cert Challenge / Drive Key / Drive Cert /
Host Cert Challenge / Host Key / Volume ID / Invalidate-AGID flows
throughMockDrive.
Out of scope (deferred to Phase C/D)
- ECDSA-secp160r1 sign/verify primitives needed for the cryptographic
half of the AKE (Common spec §4.3 steps 9, 16, 18, 25, 27). AES_G/ SHA-1-based Bus Key derivation fromHk*Dv/Dk*Hv.- Actual hardware transport: macOS IOKit, Linux SG_IO, Windows IOCTL
back-ends implementingDriveCommandagainst a real/dev/sr0/
IOSCSITaskDeviceInterface/IOCTL_SCSI_PASS_THROUGH_DIRECT. - Phase D: wiring the AKE + Bus-Key-protected reads into
oxideav-blurayfor unencrypted-at-rest disc playback.
Added — Phase A: KEYDB.cfg |-leader header records
KeyDb::parse now recognises the |-leader record form documented in
docs/container/aacs/keydb-cfg-format.md, in addition to the
pre-existing per-disc <DISC_ID>=V <VUK> lines. New record types:
| DK |Device Key (DEVICE_KEY+DEVICE_NODE+KEY_UV+
KEY_U_MASK_SHIFT) — pins a key into the AACS Subset-Difference
tree. Surfaced viaKeyDb::device_keys().| PK |Processing Key (16-byte AES-128) — the SD-tree walk output
for a specific MKB. Surfaced viaKeyDb::processing_keys().| HC |Host Certificate + private key — 20-byte ECDSA-secp160r1
scalar + variable-length signed cert. Parser validates the embedded
lengthfield (cert offset 2..4, big-endian) against the actual
buffer length per AACS Common Final 0.953 §A.1; exposeshost_id(),
cert_type(),declared_length(). Surfaced via
KeyDb::host_certs().| DC |Drive Certificate + private key (drive side of the
Drive-Host auth). Surfaced viaKeyDb::drive_certs().| DISCID |introduces a per-disc record-set scope; subsequent
| VID |,| VUK |,| MEK |,| TK |,| KCD |rows attach to
it. Surfaced asDiscRecordsviaKeyDb::disc_records()/
KeyDb::disc_record(&id).
KeyDb::vuk_for_disc now also looks through the DISCID-scoped
record map, so both legacy and |-leader files yield the same lookup
behaviour.
New AacsError::HeaderParseError(String) variant for malformed
|-leader lines.
Legacy per-disc lines continue to parse byte-identically (a dedicated
legacy_only_file_unchanged unit test pins this).