disc: make bus-key unavailability self-explaining in the log

When a bus-encrypted disc's handshake yields no read_data_key, the gate
logged a bare "bus_key_unavailable" with no indication of WHY — turning
every occurrence into archaeology (is the bus key not-attempted, or did
the read fail?).

Now:
- read_data_keys failure is captured (error code) instead of swallowed by
  `.ok()`, and logged at the handshake with its consequence.
- HandshakeResult carries `read_data_key_err: Option<u16>` so the gate
  distinguishes "never attempted" (None — VID-only/OEM path) from "read
  FAILED" (a code), and the bus_key_unavailable warn now reports the code
  plus whether a Volume ID was present.

No behavior change — purely diagnostic. The handshake_ok debug also now
reports has_volume_id.
This commit is contained in:
Matthew Jackson
2026-06-29 13:33:09 -07:00
parent 9e6af4a729
commit 263950622f
+38 -4
View File
@@ -11,6 +11,12 @@ use crate::udf;
pub(super) struct HandshakeResult { pub(super) struct HandshakeResult {
pub volume_id: [u8; 16], pub volume_id: [u8; 16],
pub read_data_key: Option<[u8; 16]>, pub read_data_key: Option<[u8; 16]>,
/// When `read_data_key` is `None` because the bus-key read FAILED (as opposed
/// to a path that never attempts it), the error code from `read_data_keys`.
/// Carried so the downstream bus-key gate can log WHY the bus key is missing
/// instead of a bare "unavailable" — the difference between a diagnosable log
/// and archaeology.
pub read_data_key_err: Option<u16>,
} }
/// In-tree AACS host-certificate cert-auth "unlocker" — the Drive-level peer of /// In-tree AACS host-certificate cert-auth "unlocker" — the Drive-level peer of
@@ -97,18 +103,33 @@ impl AacsCertUnlocker<'_> {
return Err(UnlockError::VidUnavailable); return Err(UnlockError::VidUnavailable);
} }
}; };
let read_data_key = aacs::handshake::read_data_keys(session, &mut auth) let (read_data_key, read_data_key_err) =
.ok() match aacs::handshake::read_data_keys(session, &mut auth) {
.map(|(rdk, _)| rdk); Ok((rdk, _)) => (Some(rdk), None),
Err(e) => {
tracing::debug!(
target: "freemkv::disc",
phase = "handshake_read_data_key_failed",
cert_index = idx,
error_code = e.code(),
"auth + VID read OK, but the drive served no read_data_key (bus key); \
a bus-encrypted disc stays undecryptable until it does"
);
(None, Some(e.code()))
}
};
tracing::debug!( tracing::debug!(
target: "freemkv::disc", target: "freemkv::disc",
phase = "handshake_ok", phase = "handshake_ok",
cert_index = idx, cert_index = idx,
has_volume_id = volume_id != [0u8; 16],
has_read_data_key = read_data_key.is_some(), has_read_data_key = read_data_key.is_some(),
"AACS bus-auth handshake complete"
); );
return Ok(HandshakeResult { return Ok(HandshakeResult {
volume_id, volume_id,
read_data_key, read_data_key,
read_data_key_err,
}); });
} }
Err(e) => { Err(e) => {
@@ -270,6 +291,9 @@ impl Disc {
Some(HandshakeResult { Some(HandshakeResult {
volume_id, volume_id,
read_data_key: None, read_data_key: None,
// OEM/VID-only path never attempts the bus-key read — None here
// is "not attempted", not "failed".
read_data_key_err: None,
}), }),
None, None,
); );
@@ -351,10 +375,16 @@ impl Disc {
// BD is not bus-encrypted, so `bus_encryption` is false and the gate is // BD is not bus-encrypted, so `bus_encryption` is false and the gate is
// skipped (its `read_data_key` is legitimately absent). // skipped (its `read_data_key` is legitimately absent).
if bus_encryption && handshake.is_some_and(|h| h.read_data_key.is_none()) { if bus_encryption && handshake.is_some_and(|h| h.read_data_key.is_none()) {
let h = handshake.expect("is_some_and matched");
tracing::warn!( tracing::warn!(
target: "freemkv::disc", target: "freemkv::disc",
phase = "bus_key_unavailable", phase = "bus_key_unavailable",
"Disc declares bus encryption but the handshake produced no read_data_key; a VID-only/OEM unlock cannot remove bus encryption. Refusing to proceed with a key that would decrypt to garbage." read_data_key_err = ?h.read_data_key_err,
has_volume_id = h.volume_id != [0u8; 16],
"Disc declares bus encryption but the drive served no read_data_key (bus key). \
read_data_key_err=None ⇒ the unlock path never attempted it (VID-only/OEM); \
a code ⇒ the bus-key read FAILED. Either way bus encryption can't be removed — \
refusing to emit a key that would decrypt to garbage."
); );
return Err(Error::AacsBusKeyUnavailable); return Err(Error::AacsBusKeyUnavailable);
} }
@@ -785,6 +815,7 @@ mod tests {
let hs = HandshakeResult { let hs = HandshakeResult {
volume_id: vid, volume_id: vid,
read_data_key: Some(rdk), read_data_key: Some(rdk),
read_data_key_err: None,
}; };
let st = Disc::resolve_vid_only(&udf, &mut disc, Some(&hs)).expect("state"); let st = Disc::resolve_vid_only(&udf, &mut disc, Some(&hs)).expect("state");
assert_eq!(st.volume_id, vid); assert_eq!(st.volume_id, vid);
@@ -829,6 +860,7 @@ mod tests {
let hs = HandshakeResult { let hs = HandshakeResult {
volume_id: [0x11u8; 16], volume_id: [0x11u8; 16],
read_data_key: None, read_data_key: None,
read_data_key_err: None,
}; };
let err = Disc::resolve_vid_only(&udf, &mut disc, Some(&hs)) let err = Disc::resolve_vid_only(&udf, &mut disc, Some(&hs))
.expect_err("bus-encrypted disc with no bus key must hard-error"); .expect_err("bus-encrypted disc with no bus key must hard-error");
@@ -843,6 +875,7 @@ mod tests {
let hs = HandshakeResult { let hs = HandshakeResult {
volume_id: [0x11u8; 16], volume_id: [0x11u8; 16],
read_data_key: Some([0x22u8; 16]), read_data_key: Some([0x22u8; 16]),
read_data_key_err: None,
}; };
let st = Disc::resolve_vid_only(&udf, &mut disc, Some(&hs)).expect("bus key present → ok"); let st = Disc::resolve_vid_only(&udf, &mut disc, Some(&hs)).expect("bus key present → ok");
assert!(st.bus_encryption); assert!(st.bus_encryption);
@@ -869,6 +902,7 @@ mod tests {
let hs = HandshakeResult { let hs = HandshakeResult {
volume_id: [0x11u8; 16], volume_id: [0x11u8; 16],
read_data_key: None, read_data_key: None,
read_data_key_err: None,
}; };
let st = Disc::resolve_vid_only(&udf, &mut disc, Some(&hs)).expect("AACS 1.0 → ok"); let st = Disc::resolve_vid_only(&udf, &mut disc, Some(&hs)).expect("AACS 1.0 → ok");
assert!(!st.bus_encryption); assert!(!st.bus_encryption);