Trim MKB to real record length; empty unit-keys means no keys
- mkb_content_len walks the MKB record stream and returns where it ends, so callers can drop the trailing zero padding. MKB files are allocated to a fixed size (~128 MiB) with records at the front; both MKB_RO and MKB_RW can be padded. read_aacs_inputs* now trim to the real records (~few MB) instead of shipping ~128 MiB of zeros. - decrypt_keys() returns None when unit_keys is empty: an AACS state that carries only a VID (out-of-band resolution, pre-key) is 'encrypted, no keys', not a usable empty key set.
This commit is contained in:
@@ -532,6 +532,27 @@ fn find_record_body(mkb: &[u8], rec_type_wanted: u8) -> Option<Vec<u8>> {
|
|||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Real content length of an MKB: the byte offset where the record stream
|
||||||
|
/// ends. MKB files (especially `MKB_RW.inf`, but `MKB_RO.inf` too on some
|
||||||
|
/// discs) are allocated to a fixed size — often ~128 MiB — with the records at
|
||||||
|
/// the front and the rest zero padding. Walking records (type+len) and stopping
|
||||||
|
/// at the first padding byte (`type == 0` / zero-length / overrun) gives the
|
||||||
|
/// actual size so callers can trim off megabytes of zeros before sending or
|
||||||
|
/// archiving. Returns `mkb.len()` only if the whole buffer parsed as records.
|
||||||
|
pub fn mkb_content_len(mkb: &[u8]) -> usize {
|
||||||
|
let mut pos = 0;
|
||||||
|
while pos + 4 <= mkb.len() {
|
||||||
|
let rec_type = mkb[pos];
|
||||||
|
let rec_len = u32::from_be_bytes([0, mkb[pos + 1], mkb[pos + 2], mkb[pos + 3]]) as usize;
|
||||||
|
// A zero type, a zero/short length, or an overrun = records done, padding begun.
|
||||||
|
if rec_type == 0x00 || rec_len < 4 || pos + rec_len > mkb.len() {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
pos += rec_len;
|
||||||
|
}
|
||||||
|
pos
|
||||||
|
}
|
||||||
|
|
||||||
/// Get MKB version from Type and Version Record (type 0x10).
|
/// Get MKB version from Type and Version Record (type 0x10).
|
||||||
/// Version is a BE u32 at offset 8 of the record body (offset 12 from `pos`).
|
/// Version is a BE u32 at offset 8 of the record body (offset 12 from `pos`).
|
||||||
pub fn mkb_version(mkb: &[u8]) -> Option<u32> {
|
pub fn mkb_version(mkb: &[u8]) -> Option<u32> {
|
||||||
@@ -1374,6 +1395,26 @@ mod tests {
|
|||||||
assert_eq!(mkb_version(&mkb), Some(77));
|
assert_eq!(mkb_version(&mkb), Some(77));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn mkb_content_len_trims_trailing_padding() {
|
||||||
|
// Two real records (0x10 type/version + 0x86 verify), then 128 KiB of
|
||||||
|
// zero padding (the fixed-region tail). Content length must stop at the
|
||||||
|
// end of the records, not include the padding.
|
||||||
|
let mut mkb = vec![
|
||||||
|
0x10, 0x00, 0x00, 0x0C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x4D,
|
||||||
|
];
|
||||||
|
mkb.extend_from_slice(&[0x86, 0x00, 0x00, 0x18]);
|
||||||
|
mkb.extend_from_slice(&[0xAB; 16]);
|
||||||
|
mkb.extend_from_slice(&[0x00, 0x00, 0x00, 0x00]);
|
||||||
|
let records_len = mkb.len();
|
||||||
|
mkb.extend(std::iter::repeat(0u8).take(128 * 1024)); // padding
|
||||||
|
assert_eq!(mkb_content_len(&mkb), records_len);
|
||||||
|
// No padding → returns the full length.
|
||||||
|
assert_eq!(mkb_content_len(&mkb[..records_len]), records_len);
|
||||||
|
// Empty → 0.
|
||||||
|
assert_eq!(mkb_content_len(&[]), 0);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn mkb_version_returns_none_on_empty() {
|
fn mkb_version_returns_none_on_empty() {
|
||||||
assert_eq!(mkb_version(&[]), None);
|
assert_eq!(mkb_version(&[]), None);
|
||||||
|
|||||||
+2
-2
@@ -31,8 +31,8 @@ pub use keys::probe;
|
|||||||
pub use keys::{
|
pub use keys::{
|
||||||
AacsVersion, ContentCert, ResolveContext, ResolvedKeys, UnitKeyFile, decrypt_unit_key,
|
AacsVersion, ContentCert, ResolveContext, ResolvedKeys, UnitKeyFile, decrypt_unit_key,
|
||||||
derive_media_key_from_dk, derive_media_key_from_pk, derive_vuk, disc_hash, disc_hash_hex,
|
derive_media_key_from_dk, derive_media_key_from_pk, derive_vuk, disc_hash, disc_hash_hex,
|
||||||
mkb_version, parse_content_cert, parse_unit_key_ro, read_mkb_from_drive, resolve_keys_v1,
|
mkb_content_len, mkb_version, parse_content_cert, parse_unit_key_ro, read_mkb_from_drive,
|
||||||
resolve_keys_v2, resolve_keys_v21,
|
resolve_keys_v1, resolve_keys_v2, resolve_keys_v21,
|
||||||
};
|
};
|
||||||
pub use provider::KeyProvider;
|
pub use provider::KeyProvider;
|
||||||
pub use variants::{
|
pub use variants::{
|
||||||
|
|||||||
+25
-8
@@ -1173,13 +1173,18 @@ impl Disc {
|
|||||||
.read_file(&mut reader, "/AACS/Unit_Key_RO.inf")
|
.read_file(&mut reader, "/AACS/Unit_Key_RO.inf")
|
||||||
.or_else(|_| udf_fs.read_file(&mut reader, "/AACS/DUPLICATE/Unit_Key_RO.inf"))
|
.or_else(|_| udf_fs.read_file(&mut reader, "/AACS/DUPLICATE/Unit_Key_RO.inf"))
|
||||||
.map_err(|_| Error::AacsNoKeys)?;
|
.map_err(|_| Error::AacsNoKeys)?;
|
||||||
// Prefer MKB_RO: it's the real, correctly-sized MKB. MKB_RW is a
|
// Prefer MKB_RO, fall back to MKB_RW, then TRIM to the real record
|
||||||
// fixed ~128 MiB rewritable region that is mostly zero padding — reading
|
// length. Both files are allocated to a fixed ~128 MiB and zero-padded,
|
||||||
// it ships 124 MiB of nothing. Fall back to RW only if RO is absent.
|
// so reading either ships up to ~124 MiB of nothing — trim to the
|
||||||
let mkb = udf_fs
|
// record stream so callers send/store a few MB, not 128 MiB.
|
||||||
|
let mut mkb = udf_fs
|
||||||
.read_file(&mut reader, "/AACS/MKB_RO.inf")
|
.read_file(&mut reader, "/AACS/MKB_RO.inf")
|
||||||
.or_else(|_| udf_fs.read_file(&mut reader, "/AACS/MKB_RW.inf"))
|
.or_else(|_| udf_fs.read_file(&mut reader, "/AACS/MKB_RW.inf"))
|
||||||
.map_err(|_| Error::AacsNoKeys)?;
|
.map_err(|_| Error::AacsNoKeys)?;
|
||||||
|
let n = crate::aacs::mkb_content_len(&mkb);
|
||||||
|
if n > 0 && n < mkb.len() {
|
||||||
|
mkb.truncate(n);
|
||||||
|
}
|
||||||
Ok((inf, mkb))
|
Ok((inf, mkb))
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1194,13 +1199,18 @@ impl Disc {
|
|||||||
.read_file(&mut reader, "/AACS/Unit_Key_RO.inf")
|
.read_file(&mut reader, "/AACS/Unit_Key_RO.inf")
|
||||||
.or_else(|_| udf_fs.read_file(&mut reader, "/AACS/DUPLICATE/Unit_Key_RO.inf"))
|
.or_else(|_| udf_fs.read_file(&mut reader, "/AACS/DUPLICATE/Unit_Key_RO.inf"))
|
||||||
.map_err(|_| Error::AacsNoKeys)?;
|
.map_err(|_| Error::AacsNoKeys)?;
|
||||||
// Prefer MKB_RO: it's the real, correctly-sized MKB. MKB_RW is a
|
// Prefer MKB_RO, fall back to MKB_RW, then TRIM to the real record
|
||||||
// fixed ~128 MiB rewritable region that is mostly zero padding — reading
|
// length. Both files are allocated to a fixed ~128 MiB and zero-padded,
|
||||||
// it ships 124 MiB of nothing. Fall back to RW only if RO is absent.
|
// so reading either ships up to ~124 MiB of nothing — trim to the
|
||||||
let mkb = udf_fs
|
// record stream so callers send/store a few MB, not 128 MiB.
|
||||||
|
let mut mkb = udf_fs
|
||||||
.read_file(&mut reader, "/AACS/MKB_RO.inf")
|
.read_file(&mut reader, "/AACS/MKB_RO.inf")
|
||||||
.or_else(|_| udf_fs.read_file(&mut reader, "/AACS/MKB_RW.inf"))
|
.or_else(|_| udf_fs.read_file(&mut reader, "/AACS/MKB_RW.inf"))
|
||||||
.map_err(|_| Error::AacsNoKeys)?;
|
.map_err(|_| Error::AacsNoKeys)?;
|
||||||
|
let n = crate::aacs::mkb_content_len(&mkb);
|
||||||
|
if n > 0 && n < mkb.len() {
|
||||||
|
mkb.truncate(n);
|
||||||
|
}
|
||||||
Ok((inf, mkb))
|
Ok((inf, mkb))
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1471,6 +1481,13 @@ impl Disc {
|
|||||||
/// Used by disc-to-ISO and other full-disc operations.
|
/// Used by disc-to-ISO and other full-disc operations.
|
||||||
pub fn decrypt_keys(&self) -> crate::decrypt::DecryptKeys {
|
pub fn decrypt_keys(&self) -> crate::decrypt::DecryptKeys {
|
||||||
if let Some(ref aacs) = self.aacs {
|
if let Some(ref aacs) = self.aacs {
|
||||||
|
// An AACS state with NO unit keys is "encrypted, no keys" — e.g.
|
||||||
|
// the VID-only state from out-of-band resolution before a Unit Key
|
||||||
|
// is supplied. Report None so callers treat it as missing keys
|
||||||
|
// (not a usable, empty key set).
|
||||||
|
if aacs.unit_keys.is_empty() {
|
||||||
|
return crate::decrypt::DecryptKeys::None;
|
||||||
|
}
|
||||||
crate::decrypt::DecryptKeys::Aacs {
|
crate::decrypt::DecryptKeys::Aacs {
|
||||||
unit_keys: aacs.unit_keys.clone(),
|
unit_keys: aacs.unit_keys.clone(),
|
||||||
read_data_key: aacs.read_data_key,
|
read_data_key: aacs.read_data_key,
|
||||||
|
|||||||
Reference in New Issue
Block a user