mirror of
https://github.com/freemkv/freemkv-unlock.git
synced 2026-10-07 18:13:11 +00:00
74fe85cdd28d37b5accc8171b7b3afd37524c6b9
Two coupled classes. The chain-of-trust hardening makes a genuine AACS LA public key load-bearing (the new ECDSA public-key validation rejects every off-curve key), so the anchor replacement ships in the same commit — with the old off-curve constants and the new guard, verify_cert would reject every certificate. === Chain of trust (forged-trust class) === - aacs_authenticate: reject an unknown drive-cert type instead of falling through. The if/else-if had no else, so a type that was neither 0x01 (AACS 1.0) nor 0x11 (AACS 2.0) skipped LA verification yet still ran the step-6 key-signature check against the drive's OWN key lifted from a cert that was never LA-verified — a rogue bridge could inject an attacker-chosen bus key. Reject at the cert-type gate, before any key is trusted. - ecdsa_verify / ecdsa_verify_p256: validate the public key Q (on-curve, in-field, not the identity) before use. A Q of (0,0) collapsed verification to a form forgeable with no private key. - verify_cert_p256: guard on the real 132-byte P-256 cert length (was 138, which the 132-byte caller never satisfied, so it returned false for 100% of AACS 2.0 drives) and read the signature at the real offsets. === Failure-that-looks-like-success class (dead bus reported as unlocked) === - run_cert_handshake read_data_keys arm: classify a transport fault like its read_volume_id sibling. A bus that died during the data-key read returned Ok(volume_id: Some, read_data_key: None) — a dead bus rendered as success. - LibreDrive::firmware_unlock: a transport fault in probe_disc now aborts with Transport. For the 140/206 profiles without a read_vid_cdb the bus is never touched again, so this swallowed fault was the only dead-bus signal and warn-and-continue reported Ok(drive_unlocked: true) on a dead bus. - mt1959 run_probe pass-1 and pass-2 speed-probe loops: preserve the transport classification instead of flattening to UnlockFailed (pass-1) or silently returning Ok(()) (pass-2 bare break). === AGID + key-material redaction === - aacs2_authenticate_p256: release the AGID on every fallible exit (the v1 twin was fixed; the v2 path still leaked). - Unlocked, DriveProfile: hand-written redacting Debug. Unlocked is the public carrier for bus_key/vid; DriveProfile (reachable via the public catalog) carried the drive firmware image and per-drive vendor CDBs — a derived Debug printed all of it. This crate is unpublished precisely because of that firmware. === Validated AACS Licensing Administrator anchors === Replace the compiled-in AACS 1.0 and 2.0 LA public keys, which were OFF-CURVE (so the new Q-validation rejected every cert signed by them), with the real published keys. Both confirmed on-curve; the 1.0 key also verifies a genuine LA-signed host certificate. The native P-256 handshake stays #[allow(dead_code)] and is NOT wired — a security note documents the cert-type gate + fatal verification that must be added first, once the 2.0 wire layout is confirmed against a real drive cert. Tests: every behavioural fix is pinned with a red-before-green test naming its mutation, including transport-abort coverage for validate, both firmware upload variants, both probe passes, and the probe-disc dead-bus path; the anchor landing is pinned by an on-curve assertion and a genuine-cert verify (both go red if an anchor reverts to the old off-curve value).
freemkv-unlock
The unlock layer for the freemkv toolchain.
An unlocker removes a drive-level bus-encryption barrier so the drive serves readable (de-bus'd / de-scrambled) sectors. Content-key decryption is a separate concern — the consumer's job.
This crate defines the Unlocker contract and a generic SCSI transport
contract, and holds the self-contained unlocker modules. The consumer
(libfreemkv) depends on this crate and
dispatches through all_unlockers(); it never names an individual unlocker, and
clients of libfreemkv are oblivious to unlockers entirely (as they are to the
SCSI layer).
use freemkv_unlock::UnlockError;
// Drive-prep: try each unlocker's feature unlock until one claims the drive.
// `NotApplicable` means "not this unlocker's drive" — move on; a transport
// error means a dead bus — abort. `unlock_bus` follows the same contract for
// removing per-disc bus encryption.
for u in freemkv_unlock::all_unlockers() {
match u.unlock_features(&mut scsi, &ctx) {
Ok(unlocked) => return Ok(unlocked),
Err(UnlockError::NotApplicable) => continue,
Err(e) => return Err(e),
}
}
To remove an unlocker, delete its module directory and its one line in
all_unlockers() — nothing else changes.
License: MIT.
Languages
Rust
100%