Compare commits

...
12 Commits
Author SHA1 Message Date
Matthew Jackson 295046054b Rename the DVD read-unlocker: CSS -> DVD
The unlocker performs the DVD bus-auth that clears the drive's
scrambled-read barrier, letting a protected DVD be read at all. That is
a property of the DVD medium, not of the CSS scheme — the descramble key
is recovered keylessly downstream — so naming it "CSS" conflated the
read-enable with the encryption. Rename the struct to DvdUnlocker, its
registry name to "DVD", and the tests to match. Bump 1.5.2.
2026-07-22 13:48:56 -07:00
Matthew Jackson d339108719 v1.5.1: bump version (unified release) 2026-07-20 17:02:02 -07:00
Matthew Jackson c1447a3dae v1.5.0: bump version (unified release) 2026-07-19 15:37:17 -07:00
Matthew Jackson f073c2c3f9 v1.4.5: bump version (unified release) 2026-07-18 16:07:53 -07:00
Matthew Jackson 9e84eeb284 v1.4.4: bump version (unified release) 2026-07-16 21:43:57 -07:00
Matthew Jackson 5aed39a109 v1.4.3: bump version (unified release) 2026-07-16 21:05:53 -07:00
Matthew Jackson 876943a554 v1.4.2: bump version (unified release) 2026-07-15 19:37:43 -07:00
Matthew Jackson 7c3a46897f v1.4.1: bump version (unified release) 2026-07-14 14:45:21 -07:00
Matthew Jackson b20b38e3d4 Changelog for 1.4.1 2026-07-14 14:43:14 -07:00
Matthew Jackson f2747d5a72 v1.4.0: bump version (unified release) 2026-07-13 19:28:44 -07:00
Matthew Jackson 2877ccd022 Changelog: 1.4.0 (Blu-ray 3D / MVC) 2026-07-13 12:47:03 -07:00
Matthew Jackson af49583865 ld: restore module README with LibreDrive credit to Mike Chen + the MakeMKV team 2026-07-10 14:35:44 -07:00
5 changed files with 86 additions and 22 deletions
+16
View File
@@ -1,5 +1,21 @@
# Changelog
## [1.5.2] — UNRELEASED
### Changed
- The DVD read-unlocker (bus-auth) is renamed `CSS` → `DVD`: it reports the
medium it unlocks, not whether a title-key crack ran. The unlocker report now
reads `DVD: yes` on any DVD.
## [1.4.1] — 2026-07-14
Version sync with the workspace; inherits libfreemkv 1.4.1.
## [1.4.0] — 2026-07-13
Version sync with the workspace; inherits libfreemkv 1.4.0.
## [1.3.2] — 2026-07-10
Version sync with the workspace; inherits libfreemkv 1.3.2.
+1 -1
View File
@@ -1,6 +1,6 @@
[package]
name = "freemkv-unlock"
version = "1.3.2"
version = "1.5.2"
edition = "2024"
rust-version = "1.86"
license = "MIT"
+40 -19
View File
@@ -141,27 +141,38 @@ pub fn unlock_css_reads(scsi: &mut dyn ScsiTransport, lba: u32) -> Result<()> {
r
}
/// The CSS unlocker — the DVD peer of the firmware and AACS-cert unlockers in
/// the uniform [`crate::Unlocker`] registry. It removes the CSS
/// scrambled-read barrier (drive ASF=1) and learns no VID or bus key — the
/// descramble key is recovered keylessly downstream (the Stevenson attack).
pub struct CssUnlocker;
/// The DVD unlocker (registry name `"DVD"`) — the DVD peer of the firmware and
/// AACS-cert unlockers in the uniform [`crate::Unlocker`] registry. It removes
/// the DVD scrambled-read barrier (drive ASF=1) via bus-auth and learns no VID
/// or bus key — the descramble key is recovered keylessly downstream (the
/// Stevenson attack). Named for the medium it unlocks (DVD), not the CSS
/// scheme: the bus-auth is required to read a CSS-protected DVD at all, whether
/// or not any given sector turns out to be scrambled. Lives in the `css` module
/// beside the CSS-scheme primitives it drives.
pub struct DvdUnlocker;
impl CssUnlocker {
impl DvdUnlocker {
pub fn new() -> Self {
CssUnlocker
DvdUnlocker
}
}
impl Default for CssUnlocker {
impl Default for DvdUnlocker {
fn default() -> Self {
Self::new()
}
}
impl crate::Unlocker for CssUnlocker {
impl crate::Unlocker for DvdUnlocker {
fn name(&self) -> &'static str {
"CSS"
// User-facing unlocker label. This unlocker's job is the DVD
// read-enablement bus-auth (it clears the drive's scrambled-read
// barrier and learns no key) — a property of the DVD medium, NOT of
// whether the content happens to be CSS-scrambled. Reporting it as
// "DVD" is honest: on any DVD the bus-auth ran; the CSS descramble
// itself is keyless and handled downstream, so it is not a separate
// "did an unlocker run" signal.
"DVD"
}
/// CSS removes the scrambled-sector barrier (a bus-level concern); it
@@ -179,8 +190,8 @@ impl crate::Unlocker for CssUnlocker {
if !mounted_disc_is_dvd(scsi) {
tracing::debug!(
target: "freemkv::css",
phase = "css_unlocker_not_dvd",
"CssUnlocker invoked on a non-DVD profile; refusing (NotApplicable)"
phase = "dvd_unlocker_not_dvd",
"DvdUnlocker invoked on a non-DVD profile; refusing (NotApplicable)"
);
return Err(crate::UnlockError::NotApplicable);
}
@@ -193,7 +204,7 @@ impl crate::Unlocker for CssUnlocker {
}
/// Transport-level "is the mounted disc a DVD?" probe (GET CONFIGURATION
/// current-profile, DVD family `0x0010..=0x001F`). Lets the CssUnlocker
/// current-profile, DVD family `0x0010..=0x001F`). Lets the DvdUnlocker
/// self-verify against the drive instead of trusting the caller's DiscKind.
fn mounted_disc_is_dvd(scsi: &mut dyn ScsiTransport) -> bool {
// RT=0: the 8-byte feature header carries the Current Profile in bytes 6-7.
@@ -922,9 +933,19 @@ mod tests {
assert_eq!(cdb[9], 0x04, "low byte of 2052-byte transfer");
}
/// CssUnlocker provides bus removal only — it never provides drive features.
/// The unlocker's user-facing name is "DVD" (the medium it read-unlocks),
/// not "CSS" (the scheme). Apps render the unlocker report from this name,
/// so it is a stable contract — the bus-auth ran on any DVD, encrypted or
/// not, and the report must say so rather than conflate it with a CSS crack.
#[test]
fn css_unlocker_provides_no_features() {
fn dvd_unlocker_is_named_dvd() {
use crate::Unlocker;
assert_eq!(DvdUnlocker::new().name(), "DVD");
}
/// DvdUnlocker provides bus removal only — it never provides drive features.
#[test]
fn dvd_unlocker_provides_no_features() {
use crate::scsi::{DataDirection, ScsiResult};
use crate::{DiscKind, DriveId, UnlockCtx, UnlockError, Unlocker};
struct DeadTransport;
@@ -942,16 +963,16 @@ mod tests {
let id = DriveId::default();
let mut t = DeadTransport;
let r =
CssUnlocker::new().unlock_features(&mut t, &UnlockCtx::new(&id, DiscKind::Css, &[]));
DvdUnlocker::new().unlock_features(&mut t, &UnlockCtx::new(&id, DiscKind::Css, &[]));
assert_eq!(r.unwrap_err(), UnlockError::NotApplicable);
}
/// Defense in depth: even when the caller declares `DiscKind::Css`, the
/// CssUnlocker self-verifies against the drive's GET CONFIGURATION profile.
/// DvdUnlocker self-verifies against the drive's GET CONFIGURATION profile.
/// A drive reporting a Blu-ray profile → `NotApplicable`, and NOT a single
/// CSS CDB is issued (no bus-auth fired at a BD).
#[test]
fn css_unlocker_self_guards_against_non_dvd() {
fn dvd_unlocker_self_guards_against_non_dvd() {
use crate::scsi::{DataDirection, ScsiResult};
use crate::{DiscKind, DriveId, UnlockCtx, UnlockError, Unlocker};
@@ -994,7 +1015,7 @@ mod tests {
};
let mut t = BdTransport { non_config_cdbs: 0 };
let r = CssUnlocker::new().unlock_bus(&mut t, &UnlockCtx::new(&id, DiscKind::Css, &[]));
let r = DvdUnlocker::new().unlock_bus(&mut t, &UnlockCtx::new(&id, DiscKind::Css, &[]));
assert_eq!(
r.unwrap_err(),
UnlockError::NotApplicable,
+27
View File
@@ -0,0 +1,27 @@
# ld — the LibreDrive unlocker
The **LibreDrive** unlocker module for freemkv-unlock.
freemkv-unlock defines the generic `Unlocker` contract; this module is one
implementation of it. It recognizes a bundled catalog of supported drives
(`profiles.json`) and, for a matching drive, lifts the drive-level
bus-encryption barrier so the drive serves readable sectors. Content-key
decryption is a separate concern, handled by the consumer.
Clients never name this module directly —
[libfreemkv](https://github.com/freemkv/libfreemkv) dispatches through
`freemkv_unlock::all_unlockers()`, and this module answers `matches()` /
`unlock()` when the drive identity is one it supports.
## Scope: non-persistent unlock only
This module performs only the **non-persistent** unlock — the access state it
sets up lives in drive RAM and is gone on power cycle. The one-time, permanent
drive preparation is the drive owner's own manual step and is **never**
automated here.
## Credits
LibreDrive was created by **Mike Chen** and the **MakeMKV team**. This module
builds on their work — our thanks and full credit to them for the LibreDrive
capabilities.
+2 -2
View File
@@ -115,7 +115,7 @@ pub enum UnlockError {
/// here — that is the consumer's concern, not bus removal.
pub trait Unlocker: Send + Sync {
/// Short, stable identifier for this unlocker (e.g. "LibreDrive", "AACS",
/// "CSS", "Renesas"). The ONE place a name lives — apps render the unlocker
/// "DVD", "Renesas"). The ONE place a name lives — apps render the unlocker
/// report from [`all_unlockers`], never hardcoding names, so adding/removing
/// an unlocker updates every report with no app change.
fn name(&self) -> &'static str;
@@ -167,6 +167,6 @@ pub fn all_unlockers() -> Vec<Box<dyn Unlocker>> {
Box::new(ld::LibreDrive::new()),
Box::new(renesis::Renesis::new()),
Box::new(aacs::AacsCert::new()),
Box::new(css::CssUnlocker::new()),
Box::new(css::DvdUnlocker::new()),
]
}