aacs: remove the mod.rs facade — module paths are the public API
Delete the pub-use re-export facade; consumers now import from the owning module (aacs::content::decrypt_unit, aacs::mkb::MkbType, aacs::derive::derive_vuk, aacs::boil::mk_from_dk, aacs::resolve::resolve_keys_v2, ...). Internal callers repointed accordingly. Path-only change; logic hash identical (95fb9924); 2210 tests green.
This commit is contained in:
@@ -10,8 +10,8 @@
|
||||
pub fn collect_host_certs(
|
||||
opts: &crate::disc::ScanOptions,
|
||||
mkb: Option<u32>,
|
||||
) -> Vec<crate::aacs::HostCert> {
|
||||
let mut host_certs: Vec<crate::aacs::HostCert> = Vec::new();
|
||||
) -> Vec<crate::aacs::types::HostCert> {
|
||||
let mut host_certs: Vec<crate::aacs::types::HostCert> = Vec::new();
|
||||
if let Some(c) = &opts.credentials {
|
||||
host_certs.extend(c.host_certs.iter().cloned());
|
||||
}
|
||||
|
||||
+15
-57
@@ -50,62 +50,21 @@ pub const PATH_MKB_RW: &str = "/AACS/MKB_RW.inf";
|
||||
pub const PATH_CONTENT_CERT: &str = "/AACS/Content000.cer";
|
||||
pub const PATH_CONTENT_CERT_ALT: &str = "/AACS/Content001.cer";
|
||||
|
||||
// Boil-down derivation primitives (thin newtypes + wrappers over the crypto).
|
||||
pub use boil::{
|
||||
KeyCandidate, MediaKey, ProcessingKey, ResolvedChain, UnitKey, Vid, Vuk, mk_from_dk,
|
||||
mk_from_pk, resolve_candidate, uk_from_vuk, vuk_from_mk,
|
||||
};
|
||||
// Structured, English-free resolution trace.
|
||||
pub use trace::{KeyNode, KeyOutcome, KeyStep, ResolutionTrace, UnlockOutcome, UnlockStep};
|
||||
|
||||
// Explicit re-exports — only items needed by external consumers and sibling crate modules.
|
||||
// AES primitives (aes_ecb_encrypt, aes_ecb_decrypt, aes_cbc_decrypt) are pub(crate) in decrypt.rs.
|
||||
pub use content::{
|
||||
ALIGNED_UNIT_LEN, ALIGNED_UNIT_SECTORS, UnitKeyResult, aacs_unit_encrypted,
|
||||
aacs_unit_needs_decrypt, aacs_unit_still_ciphertext, decrypt_bus, decrypt_unit,
|
||||
decrypt_unit_checked, decrypt_unit_full, decrypt_unit_try_keys, fill_null_ts_unit,
|
||||
is_unit_aligned, ts_packet_total, ts_sync_count, ts_sync_destroyed, unit_is_clean_ps,
|
||||
unit_is_clean_ts, unit_key_validates,
|
||||
};
|
||||
// `probe` is a reproduction-harness helper (see keys.rs), not part of the
|
||||
// documented 1.0 surface; keep it reachable but off the rendered docs so we
|
||||
// don't commit semver stability to test primitives.
|
||||
#[doc(hidden)]
|
||||
pub use derive::probe;
|
||||
pub use derive::{decrypt_unit_key, derive_vuk};
|
||||
pub use derive::{
|
||||
derive_media_key_and_pk_from_dk, derive_media_key_from_dk, derive_media_key_from_pk,
|
||||
recover_dk_position,
|
||||
};
|
||||
pub use inf::{
|
||||
ContentCert, UnitKeyFile, disc_hash, disc_hash_hex, parse_content_cert, parse_unit_key_ro,
|
||||
read_mkb_from_drive,
|
||||
};
|
||||
pub use mkb::{
|
||||
AACS_MAJOR_BD, AACS_MAJOR_UHD, AacsVersion, MKB_20_CATEGORY_C, MKB_21_CATEGORY_C,
|
||||
MKB_TYPE_3_RECORDABLE, MKB_TYPE_4_PRERECORDED, MKB_TYPE_10_CLASS_II, MkbRecord, MkbType,
|
||||
mkb_content_len, mkb_is_uhd, mkb_type, mkb_type_raw, mkb_version, trim_mkb, walk_mkb,
|
||||
};
|
||||
pub use provider::KeyProvider;
|
||||
pub use resolve::{
|
||||
ResolveContext, ResolveFailure, ResolvedKeys, resolve_keys_v1, resolve_keys_v2,
|
||||
resolve_keys_v21, resolve_keys_with_reason,
|
||||
};
|
||||
pub use types::{DeviceKey, DiscEntry, HostCert};
|
||||
pub use variant::{
|
||||
KEY_CORRECTION_DATA_PLACEHOLDER, MediaKeyVariantError, ProcessingKeyMatch,
|
||||
derive_media_key_variant, is_variant_mkb, variant_nonce, walk_processing_key,
|
||||
};
|
||||
// No facade: the module structure IS the public API. Consumers import from the
|
||||
// owning module directly — e.g. `aacs::content::decrypt_unit`, `aacs::mkb::MkbType`,
|
||||
// `aacs::derive::derive_vuk`, `aacs::boil::mk_from_dk`, `aacs::resolve::resolve_keys_v2`.
|
||||
// The `derive::probe` reproduction harness stays reachable via its module path.
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
//! Re-export surface guards. The module's public API is the set of
|
||||
//! `pub use` items above. A regression that drops or renames an export
|
||||
//! (the class of bug that shipped in 0.31.0 by silently changing a
|
||||
//! surface) breaks compilation of these references, so they act as a
|
||||
//! compile-time contract for the crate's AACS surface.
|
||||
//! Surface guards. The public API is the module tree itself (no facade).
|
||||
//! Touching one representative item per module keeps these as a
|
||||
//! compile-time contract that the module paths stay stable.
|
||||
|
||||
use super::*;
|
||||
use super::content::{ALIGNED_UNIT_LEN, ts_sync_destroyed};
|
||||
use super::inf::{disc_hash, disc_hash_hex};
|
||||
use super::mkb::{AacsVersion, mkb_content_len, walk_mkb};
|
||||
use super::variant::{KEY_CORRECTION_DATA_PLACEHOLDER, is_variant_mkb};
|
||||
|
||||
#[test]
|
||||
fn aligned_unit_len_is_three_2048_byte_sectors() {
|
||||
@@ -134,14 +93,13 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn public_helpers_are_callable_through_the_facade() {
|
||||
// Touch a representative function from each re-export group so a
|
||||
// dropped/renamed export fails to compile. These are smoke calls, not
|
||||
// behavioural assertions (behaviour is covered in each module).
|
||||
fn public_helpers_are_callable_by_module_path() {
|
||||
// Touch a representative function from each module so a dropped/renamed
|
||||
// item fails to compile. Smoke calls, not behavioural assertions.
|
||||
let _ = ts_sync_destroyed(&[0u8; ALIGNED_UNIT_LEN]);
|
||||
let _ = mkb_content_len(&[]);
|
||||
let _ = is_variant_mkb(&walk_mkb(&[]));
|
||||
let _ = disc_hash_hex(&disc_hash(b"x"));
|
||||
let _ = mk_from_pk(&[[0u8; 16]], &[]);
|
||||
let _ = super::boil::mk_from_pk(&[[0u8; 16]], &[]);
|
||||
}
|
||||
}
|
||||
|
||||
+15
-15
@@ -1068,7 +1068,7 @@ mod tests {
|
||||
disc_entry: None,
|
||||
};
|
||||
|
||||
let providers: &[&dyn super::super::KeyProvider] = &[&keydb];
|
||||
let providers: &[&dyn super::super::provider::KeyProvider] = &[&keydb];
|
||||
let ctx = ResolveContext {
|
||||
unit_key_ro: &uk_ro,
|
||||
content_cert: None,
|
||||
@@ -1111,7 +1111,7 @@ mod tests {
|
||||
};
|
||||
|
||||
let vid = [0u8; 16];
|
||||
let providers: &[&dyn super::super::KeyProvider] = &[&keydb];
|
||||
let providers: &[&dyn super::super::provider::KeyProvider] = &[&keydb];
|
||||
let ctx = ResolveContext {
|
||||
unit_key_ro: &uk_ro,
|
||||
content_cert: None,
|
||||
@@ -1153,7 +1153,7 @@ mod tests {
|
||||
};
|
||||
|
||||
let vid = [0u8; 16];
|
||||
let providers: &[&dyn super::super::KeyProvider] = &[&keydb];
|
||||
let providers: &[&dyn super::super::provider::KeyProvider] = &[&keydb];
|
||||
let ctx = ResolveContext {
|
||||
unit_key_ro: &uk_ro,
|
||||
content_cert: None,
|
||||
@@ -1193,7 +1193,7 @@ mod tests {
|
||||
};
|
||||
|
||||
let vid = [0u8; 16];
|
||||
let providers: &[&dyn super::super::KeyProvider] = &[&keydb];
|
||||
let providers: &[&dyn super::super::provider::KeyProvider] = &[&keydb];
|
||||
let ctx = ResolveContext {
|
||||
unit_key_ro: &uk_ro,
|
||||
content_cert: None,
|
||||
@@ -1240,7 +1240,7 @@ mod tests {
|
||||
media_keys: vec![km],
|
||||
disc_entry: None,
|
||||
};
|
||||
let providers: &[&dyn super::super::KeyProvider] = &[&keydb];
|
||||
let providers: &[&dyn super::super::provider::KeyProvider] = &[&keydb];
|
||||
let ctx = ResolveContext {
|
||||
unit_key_ro: &uk_ro,
|
||||
content_cert: None,
|
||||
@@ -1627,7 +1627,7 @@ mod tests {
|
||||
mkb.extend_from_slice(&[0x2f, 0x00, 0x00, 0x14]);
|
||||
mkb.extend_from_slice(&[0x55; 16]);
|
||||
|
||||
let providers: &[&dyn super::super::KeyProvider] = &[&keydb];
|
||||
let providers: &[&dyn super::super::provider::KeyProvider] = &[&keydb];
|
||||
let ctx = ResolveContext {
|
||||
unit_key_ro: &uk_ro,
|
||||
content_cert: None,
|
||||
@@ -1663,7 +1663,7 @@ mod tests {
|
||||
}),
|
||||
};
|
||||
let mkb = vec![0x10, 0x00, 0x00, 0x08, 0, 0, 0, 1];
|
||||
let providers: &[&dyn super::super::KeyProvider] = &[&keydb];
|
||||
let providers: &[&dyn super::super::provider::KeyProvider] = &[&keydb];
|
||||
let ctx = ResolveContext {
|
||||
unit_key_ro: &uk_ro,
|
||||
content_cert: None,
|
||||
@@ -1697,7 +1697,7 @@ mod tests {
|
||||
let mut cc = vec![0u8; 20];
|
||||
cc[0] = 0x10;
|
||||
cc[1] = 0x80;
|
||||
let providers: &[&dyn super::super::KeyProvider] = &[&keydb];
|
||||
let providers: &[&dyn super::super::provider::KeyProvider] = &[&keydb];
|
||||
let ctx = ResolveContext {
|
||||
unit_key_ro: &uk_ro,
|
||||
content_cert: Some(&cc),
|
||||
@@ -1731,7 +1731,7 @@ mod tests {
|
||||
unit_keys: Vec::new(),
|
||||
}),
|
||||
};
|
||||
let providers: &[&dyn super::super::KeyProvider] = &[&keydb];
|
||||
let providers: &[&dyn super::super::provider::KeyProvider] = &[&keydb];
|
||||
let ctx = ResolveContext {
|
||||
unit_key_ro: &data,
|
||||
content_cert: None,
|
||||
@@ -1769,7 +1769,7 @@ mod tests {
|
||||
unit_keys: Vec::new(),
|
||||
}),
|
||||
};
|
||||
let providers: &[&dyn super::super::KeyProvider] = &[&keydb];
|
||||
let providers: &[&dyn super::super::provider::KeyProvider] = &[&keydb];
|
||||
let ctx = ResolveContext {
|
||||
unit_key_ro: &uk_ro,
|
||||
content_cert: None,
|
||||
@@ -1786,7 +1786,7 @@ mod tests {
|
||||
fn resolve_keys_returns_none_when_no_provider_has_anything() {
|
||||
// Empty provider array + VID present + no MKB → all paths miss → None.
|
||||
let uk_ro = minimal_unit_key_ro();
|
||||
let providers: &[&dyn super::super::KeyProvider] = &[];
|
||||
let providers: &[&dyn super::super::provider::KeyProvider] = &[];
|
||||
let ctx = ResolveContext {
|
||||
unit_key_ro: &uk_ro,
|
||||
content_cert: None,
|
||||
@@ -1969,7 +1969,7 @@ mod tests {
|
||||
#[test]
|
||||
fn classify_processing_keys_zero_vid_is_vid_unavailable() {
|
||||
let prov = material_provider(Vec::new(), vec![[0u8; 16]]);
|
||||
let providers: &[&dyn super::super::KeyProvider] = &[&prov];
|
||||
let providers: &[&dyn super::super::provider::KeyProvider] = &[&prov];
|
||||
let uk_ro = minimal_unit_key_ro();
|
||||
let ctx = ResolveContext {
|
||||
unit_key_ro: &uk_ro,
|
||||
@@ -1994,7 +1994,7 @@ mod tests {
|
||||
#[test]
|
||||
fn classify_vid_present_with_material_is_no_material_not_vid() {
|
||||
let prov = material_provider(vec![one_device_key()], vec![[0u8; 16]]);
|
||||
let providers: &[&dyn super::super::KeyProvider] = &[&prov];
|
||||
let providers: &[&dyn super::super::provider::KeyProvider] = &[&prov];
|
||||
let uk_ro = minimal_unit_key_ro();
|
||||
let ctx = ResolveContext {
|
||||
unit_key_ro: &uk_ro,
|
||||
@@ -2017,7 +2017,7 @@ mod tests {
|
||||
#[test]
|
||||
fn classify_vid_present_no_material_is_no_material() {
|
||||
let prov = material_provider(Vec::new(), Vec::new());
|
||||
let providers: &[&dyn super::super::KeyProvider] = &[&prov];
|
||||
let providers: &[&dyn super::super::provider::KeyProvider] = &[&prov];
|
||||
let uk_ro = minimal_unit_key_ro();
|
||||
let ctx = ResolveContext {
|
||||
unit_key_ro: &uk_ro,
|
||||
@@ -2055,7 +2055,7 @@ mod tests {
|
||||
unit_keys: Vec::new(),
|
||||
}),
|
||||
};
|
||||
let providers: &[&dyn super::super::KeyProvider] = &[&keydb];
|
||||
let providers: &[&dyn super::super::provider::KeyProvider] = &[&keydb];
|
||||
let ctx = ResolveContext {
|
||||
unit_key_ro: &uk_ro,
|
||||
content_cert: None,
|
||||
|
||||
+48
-44
@@ -244,7 +244,7 @@ fn decrypt_sectors_impl(
|
||||
// Strip CPS-unit IDs — the decrypt primitives only want the raw key bytes.
|
||||
let raw_keys: Vec<[u8; 16]> = unit_keys.iter().map(|(_, k)| *k).collect();
|
||||
let rdk: Option<[u8; 16]> = *read_data_key;
|
||||
let unit_len = aacs::ALIGNED_UNIT_LEN;
|
||||
let unit_len = aacs::content::ALIGNED_UNIT_LEN;
|
||||
// AACS decrypts whole 6144-byte aligned units. The live mux path
|
||||
// (mux/disc.rs::fill_extents) issues 1- or 2-sector reads at every
|
||||
// extent tail, so a buffer is commonly NOT a multiple of the unit
|
||||
@@ -288,8 +288,8 @@ fn decrypt_sectors_impl(
|
||||
};
|
||||
if partial_in_content {
|
||||
let partial = &buf[buf.len() - partial_len..];
|
||||
let packets = aacs::ts_packet_total(partial);
|
||||
if packets > 0 && aacs::ts_sync_count(partial) <= packets / 2 {
|
||||
let packets = aacs::content::ts_packet_total(partial);
|
||||
if packets > 0 && aacs::content::ts_sync_count(partial) <= packets / 2 {
|
||||
return Err(crate::error::Error::DecryptFailed);
|
||||
}
|
||||
}
|
||||
@@ -326,7 +326,7 @@ fn decrypt_sectors_impl(
|
||||
// must happen first — it's a shared layer on top that is key-independent
|
||||
// across all CPS units on the disc.
|
||||
let decrypt_one = |chunk: &mut [u8]| {
|
||||
if chunk.len() != unit_len || !aacs::aacs_unit_needs_decrypt(chunk) {
|
||||
if chunk.len() != unit_len || !aacs::content::aacs_unit_needs_decrypt(chunk) {
|
||||
return;
|
||||
}
|
||||
// Save original bytes so we can restore if no key validates.
|
||||
@@ -335,7 +335,7 @@ fn decrypt_sectors_impl(
|
||||
// Build a bus-decrypted copy to try unit keys against, or work
|
||||
// in-place when there is no bus layer.
|
||||
if let Some(ref rdk_key) = rdk {
|
||||
aacs::decrypt_bus(chunk, rdk_key);
|
||||
aacs::content::decrypt_bus(chunk, rdk_key);
|
||||
}
|
||||
|
||||
// Reorder the key iterator: try the cached hint first, then fall
|
||||
@@ -349,7 +349,7 @@ fn decrypt_sectors_impl(
|
||||
// Work on a per-key copy so a failing attempt doesn't
|
||||
// clobber the bus-decrypted base we'll retry on.
|
||||
let mut attempt: Vec<u8> = chunk.to_vec();
|
||||
if aacs::decrypt_unit(&mut attempt, key) {
|
||||
if aacs::content::decrypt_unit(&mut attempt, key) {
|
||||
chunk.copy_from_slice(&attempt);
|
||||
last_key_idx.store(idx, Ordering::Relaxed);
|
||||
return;
|
||||
@@ -478,7 +478,7 @@ mod tests {
|
||||
/// not left scrambled.
|
||||
#[test]
|
||||
fn nav_file_unit_survives_decrypt_attempt() {
|
||||
let mut unit = vec![0u8; aacs::ALIGNED_UNIT_LEN];
|
||||
let mut unit = vec![0u8; aacs::content::ALIGNED_UNIT_LEN];
|
||||
unit[0] = b'M';
|
||||
unit[1] = b'P';
|
||||
unit[2] = b'L';
|
||||
@@ -528,7 +528,7 @@ mod tests {
|
||||
let mut u = 0;
|
||||
while u < len {
|
||||
v[u] |= 0xC0;
|
||||
u += aacs::ALIGNED_UNIT_LEN;
|
||||
u += aacs::content::ALIGNED_UNIT_LEN;
|
||||
}
|
||||
v
|
||||
}
|
||||
@@ -561,7 +561,7 @@ mod tests {
|
||||
unit_keys: vec![(0, [0xAB; 16])],
|
||||
read_data_key: None,
|
||||
};
|
||||
let original = scrambled_region(aacs::ALIGNED_UNIT_LEN);
|
||||
let original = scrambled_region(aacs::content::ALIGNED_UNIT_LEN);
|
||||
|
||||
// base_lba 0, content = [(100,10)] ⇒ the unit at LBA 0 is OUTSIDE content.
|
||||
let mut buf = original.clone();
|
||||
@@ -581,7 +581,7 @@ mod tests {
|
||||
decrypt_sectors_in_content(&mut buf2, &mut keys, 0, 100, &[(100, 10)]).unwrap();
|
||||
assert_eq!(
|
||||
dropped2,
|
||||
aacs::ALIGNED_UNIT_LEN,
|
||||
aacs::content::ALIGNED_UNIT_LEN,
|
||||
"an undecryptable CONTENT unit IS counted as loss"
|
||||
);
|
||||
}
|
||||
@@ -594,12 +594,12 @@ mod tests {
|
||||
unit_keys: vec![(0, [0xAB; 16])],
|
||||
read_data_key: None,
|
||||
};
|
||||
let mut buf = scrambled_region(2 * aacs::ALIGNED_UNIT_LEN);
|
||||
let mut buf = scrambled_region(2 * aacs::content::ALIGNED_UNIT_LEN);
|
||||
// unit0 @ LBA 0 (clear/skip), unit1 @ LBA 3 (content). Content = [(3,3)].
|
||||
let dropped = decrypt_sectors_in_content(&mut buf, &mut keys, 0, 0, &[(3, 3)]).unwrap();
|
||||
assert_eq!(
|
||||
dropped,
|
||||
aacs::ALIGNED_UNIT_LEN,
|
||||
aacs::content::ALIGNED_UNIT_LEN,
|
||||
"only the in-content unit (unit1) is checked; clear unit0 is skipped"
|
||||
);
|
||||
}
|
||||
@@ -613,7 +613,7 @@ mod tests {
|
||||
read_data_key: None,
|
||||
};
|
||||
let mut keys_u = keys_g.clone();
|
||||
let original = scrambled_region(aacs::ALIGNED_UNIT_LEN);
|
||||
let original = scrambled_region(aacs::content::ALIGNED_UNIT_LEN);
|
||||
let mut g = original.clone();
|
||||
let mut u = original.clone();
|
||||
let gated = decrypt_sectors_in_content(&mut g, &mut keys_g, 0, 0, &[(0, 3)]).unwrap();
|
||||
@@ -662,7 +662,7 @@ mod tests {
|
||||
unit_keys: vec![(0, [0xAB; 16])],
|
||||
read_data_key: None,
|
||||
};
|
||||
let original = scrambled_region(aacs::ALIGNED_UNIT_LEN);
|
||||
let original = scrambled_region(aacs::content::ALIGNED_UNIT_LEN);
|
||||
let mut buf = original.clone();
|
||||
let dropped = decrypt_sectors_in_content(&mut buf, &mut keys, 0, 0, &[]).unwrap();
|
||||
assert_eq!(
|
||||
@@ -680,7 +680,7 @@ mod tests {
|
||||
unit_keys: vec![(0, [0xAB; 16])],
|
||||
read_data_key: None,
|
||||
};
|
||||
let original = clear_ts_region(aacs::ALIGNED_UNIT_LEN);
|
||||
let original = clear_ts_region(aacs::content::ALIGNED_UNIT_LEN);
|
||||
let mut buf = original.clone();
|
||||
let dropped = decrypt_sectors_in_content(&mut buf, &mut keys, 0, 0, &[(0, 3)]).unwrap();
|
||||
assert_eq!(dropped, 0, "a clear in-content unit is not ciphertext");
|
||||
@@ -691,7 +691,7 @@ mod tests {
|
||||
#[test]
|
||||
fn content_gate_none_keys_is_noop() {
|
||||
let mut keys = DecryptKeys::None;
|
||||
let original = scrambled_region(aacs::ALIGNED_UNIT_LEN);
|
||||
let original = scrambled_region(aacs::content::ALIGNED_UNIT_LEN);
|
||||
let mut buf = original.clone();
|
||||
let dropped = decrypt_sectors_in_content(&mut buf, &mut keys, 0, 0, &[(0, 3)]).unwrap();
|
||||
assert_eq!(dropped, 0);
|
||||
@@ -721,7 +721,7 @@ mod tests {
|
||||
unit_keys: vec![(0, [0xAB; 16])],
|
||||
read_data_key: None,
|
||||
};
|
||||
let u = aacs::ALIGNED_UNIT_LEN;
|
||||
let u = aacs::content::ALIGNED_UNIT_LEN;
|
||||
let mut buf = vec![0u8; 3 * u];
|
||||
buf[..u].copy_from_slice(&scrambled_region(u)); // unit0 @ LBA0 scrambled
|
||||
buf[u..2 * u].copy_from_slice(&clear_ts_region(u)); // unit1 @ LBA3 clear
|
||||
@@ -738,10 +738,14 @@ mod tests {
|
||||
unit_keys: vec![(0, [0xAB; 16])],
|
||||
read_data_key: None,
|
||||
};
|
||||
let mut buf = scrambled_region(2 * aacs::ALIGNED_UNIT_LEN);
|
||||
let mut buf = scrambled_region(2 * aacs::content::ALIGNED_UNIT_LEN);
|
||||
// unit0 @ LBA0 content, unit1 @ LBA3 out. Content = [(0,3)].
|
||||
let dropped = decrypt_sectors_in_content(&mut buf, &mut keys, 0, 0, &[(0, 3)]).unwrap();
|
||||
assert_eq!(dropped, aacs::ALIGNED_UNIT_LEN, "only unit0 counts");
|
||||
assert_eq!(
|
||||
dropped,
|
||||
aacs::content::ALIGNED_UNIT_LEN,
|
||||
"only unit0 counts"
|
||||
);
|
||||
}
|
||||
|
||||
/// The trailing-partial reject is ALSO content-gated: a scrambled partial
|
||||
@@ -755,7 +759,7 @@ mod tests {
|
||||
};
|
||||
// One full clear unit + a scrambled single-sector partial, all OUTSIDE
|
||||
// content → the partial must be tolerated (Ok), not DecryptFailed.
|
||||
let mut buf = clear_ts_region(aacs::ALIGNED_UNIT_LEN);
|
||||
let mut buf = clear_ts_region(aacs::content::ALIGNED_UNIT_LEN);
|
||||
buf.extend_from_slice(&scrambled_region(2048));
|
||||
// content far away → both the full unit and the partial are non-content.
|
||||
let res = decrypt_sectors_in_content(&mut buf, &mut keys, 0, 0, &[(1000, 3)]);
|
||||
@@ -776,7 +780,7 @@ mod tests {
|
||||
read_data_key: None,
|
||||
};
|
||||
// One full scrambled unit + a 2048-byte (single-sector) CLEAR tail.
|
||||
let unit = scrambled_region(aacs::ALIGNED_UNIT_LEN);
|
||||
let unit = scrambled_region(aacs::content::ALIGNED_UNIT_LEN);
|
||||
let tail = clear_ts_region(2048);
|
||||
let mut buf = unit;
|
||||
buf.extend_from_slice(&tail);
|
||||
@@ -784,7 +788,7 @@ mod tests {
|
||||
decrypt_sectors(&mut buf, &mut keys, 0).expect("clear trailing partial is Ok");
|
||||
|
||||
assert_eq!(
|
||||
&buf[aacs::ALIGNED_UNIT_LEN..],
|
||||
&buf[aacs::content::ALIGNED_UNIT_LEN..],
|
||||
&tail[..],
|
||||
"clear trailing partial unit must be left unchanged"
|
||||
);
|
||||
@@ -801,7 +805,7 @@ mod tests {
|
||||
read_data_key: None,
|
||||
};
|
||||
// One full unit + a 4096-byte (two-sector) SCRAMBLED tail.
|
||||
let unit = clear_ts_region(aacs::ALIGNED_UNIT_LEN);
|
||||
let unit = clear_ts_region(aacs::content::ALIGNED_UNIT_LEN);
|
||||
let tail = scrambled_region(4096);
|
||||
let mut buf = unit;
|
||||
buf.extend_from_slice(&tail);
|
||||
@@ -835,7 +839,7 @@ mod tests {
|
||||
unit_keys: vec![(0, [0xAB; 16])],
|
||||
read_data_key: None,
|
||||
};
|
||||
let mut buf = clear_ts_region(aacs::ALIGNED_UNIT_LEN * 2);
|
||||
let mut buf = clear_ts_region(aacs::content::ALIGNED_UNIT_LEN * 2);
|
||||
let snapshot = buf.clone();
|
||||
|
||||
decrypt_sectors(&mut buf, &mut keys, 0).expect("exact-multiple buffer is Ok");
|
||||
@@ -1092,7 +1096,7 @@ mod tests {
|
||||
unit_keys: vec![(0, [0xAB; 16])],
|
||||
read_data_key: None,
|
||||
};
|
||||
let mut buf = clear_ts_region(aacs::ALIGNED_UNIT_LEN);
|
||||
let mut buf = clear_ts_region(aacs::content::ALIGNED_UNIT_LEN);
|
||||
let err = decrypt_sectors(&mut buf, &mut keys, 5)
|
||||
.expect_err("unit_key_idx 5 is out of range for a 1-key list");
|
||||
assert_eq!(
|
||||
@@ -1112,7 +1116,7 @@ mod tests {
|
||||
unit_keys: vec![],
|
||||
read_data_key: None,
|
||||
};
|
||||
let mut buf = clear_ts_region(aacs::ALIGNED_UNIT_LEN);
|
||||
let mut buf = clear_ts_region(aacs::content::ALIGNED_UNIT_LEN);
|
||||
let err = decrypt_sectors(&mut buf, &mut keys, 0).expect_err("empty unit_keys must error");
|
||||
assert_eq!(err.code(), crate::error::Error::DecryptFailed.code());
|
||||
}
|
||||
@@ -1120,7 +1124,7 @@ mod tests {
|
||||
// ── Multi-CPS-unit key selection ──────────────────────────────────────
|
||||
|
||||
/// Encrypt an aligned unit with the AACS algorithm run in reverse so that
|
||||
/// `aacs::decrypt_unit` with the same key recovers the plaintext. Mirrors
|
||||
/// `aacs::content::decrypt_unit` with the same key recovers the plaintext. Mirrors
|
||||
/// the `aacs_encrypt_unit` helper in `aacs::content::tests`.
|
||||
fn aacs_encrypt_unit_for_test(unit: &mut [u8], unit_key: &[u8; 16]) {
|
||||
use aes::Aes128;
|
||||
@@ -1136,7 +1140,7 @@ mod tests {
|
||||
}
|
||||
let cipher = Aes128::new(GenericArray::from_slice(&k));
|
||||
let mut prev = crate::aacs::crypto::AACS_IV;
|
||||
let num_blocks = (aacs::ALIGNED_UNIT_LEN - 16) / 16;
|
||||
let num_blocks = (aacs::content::ALIGNED_UNIT_LEN - 16) / 16;
|
||||
for i in 0..num_blocks {
|
||||
let off = 16 + i * 16;
|
||||
for j in 0..16 {
|
||||
@@ -1153,9 +1157,9 @@ mod tests {
|
||||
/// (offset 4 + k*192) so `ts_sync_destroyed` reports false and
|
||||
/// `decrypt_unit` verifies it as clear after decryption.
|
||||
fn clear_ts_unit() -> Vec<u8> {
|
||||
let mut unit = vec![0u8; aacs::ALIGNED_UNIT_LEN];
|
||||
let mut unit = vec![0u8; aacs::content::ALIGNED_UNIT_LEN];
|
||||
let mut off = 4;
|
||||
while off < aacs::ALIGNED_UNIT_LEN {
|
||||
while off < aacs::content::ALIGNED_UNIT_LEN {
|
||||
unit[off] = 0x47;
|
||||
off += 192;
|
||||
}
|
||||
@@ -1171,7 +1175,7 @@ mod tests {
|
||||
/// garbage for content under key ≥ 1. The fix tries every key and
|
||||
/// accepts the one whose output passes the TS-sync verify.
|
||||
///
|
||||
/// Grounding: `for idx in try_order { … if aacs::decrypt_unit(&mut attempt, key) { … } }`
|
||||
/// Grounding: `for idx in try_order { … if aacs::content::decrypt_unit(&mut attempt, key) { … } }`
|
||||
/// Mutation: revert to the pre-fix `decrypt_unit_full(chunk, &uk, …)` where
|
||||
/// `uk = raw_keys[unit_key_idx]` (always key 0) → the unit comes out as
|
||||
/// garbled bytes that still look scrambled, failing the `!ts_sync_destroyed`
|
||||
@@ -1185,7 +1189,7 @@ mod tests {
|
||||
let mut unit = clear_ts_unit();
|
||||
aacs_encrypt_unit_for_test(&mut unit, &key1);
|
||||
assert!(
|
||||
aacs::ts_sync_destroyed(&unit),
|
||||
aacs::content::ts_sync_destroyed(&unit),
|
||||
"encrypted unit must look scrambled before decrypt"
|
||||
);
|
||||
|
||||
@@ -1199,13 +1203,13 @@ mod tests {
|
||||
decrypt_sectors(&mut buf, &mut keys, 0).expect("multi-CPS decrypt must succeed");
|
||||
|
||||
assert!(
|
||||
!aacs::ts_sync_destroyed(&buf),
|
||||
!aacs::content::ts_sync_destroyed(&buf),
|
||||
"unit encrypted under key1 must be fully decrypted (TS syncs restored)"
|
||||
);
|
||||
// Every sync position must carry 0x47.
|
||||
assert_eq!(
|
||||
aacs::ts_sync_count(&buf),
|
||||
aacs::ts_packet_total(&buf),
|
||||
aacs::content::ts_sync_count(&buf),
|
||||
aacs::content::ts_packet_total(&buf),
|
||||
"all TS sync bytes must be restored after decrypting under key1"
|
||||
);
|
||||
}
|
||||
@@ -1230,12 +1234,12 @@ mod tests {
|
||||
let mut buf = unit;
|
||||
decrypt_sectors(&mut buf, &mut keys, 0).expect("single-key disc must decrypt");
|
||||
assert!(
|
||||
!aacs::ts_sync_destroyed(&buf),
|
||||
!aacs::content::ts_sync_destroyed(&buf),
|
||||
"single-key disc: TS syncs must be restored"
|
||||
);
|
||||
assert_eq!(
|
||||
aacs::ts_sync_count(&buf),
|
||||
aacs::ts_packet_total(&buf),
|
||||
aacs::content::ts_sync_count(&buf),
|
||||
aacs::content::ts_packet_total(&buf),
|
||||
"all TS sync bytes must be restored for single-key disc"
|
||||
);
|
||||
}
|
||||
@@ -1263,7 +1267,7 @@ mod tests {
|
||||
aacs_encrypt_unit_for_test(&mut unit, &real_key);
|
||||
let ciphertext = unit.clone();
|
||||
assert!(
|
||||
aacs::ts_sync_destroyed(&unit),
|
||||
aacs::content::ts_sync_destroyed(&unit),
|
||||
"encrypted unit must look scrambled going in"
|
||||
);
|
||||
|
||||
@@ -1277,7 +1281,7 @@ mod tests {
|
||||
|
||||
assert_eq!(
|
||||
dropped,
|
||||
aacs::ALIGNED_UNIT_LEN,
|
||||
aacs::content::ALIGNED_UNIT_LEN,
|
||||
"the whole scrambled unit must be reported as dropped when no key validates"
|
||||
);
|
||||
assert_eq!(
|
||||
@@ -1306,7 +1310,7 @@ mod tests {
|
||||
aacs_encrypt_unit_for_test(&mut unit_b, &wrong);
|
||||
let unit_b_ciphertext = unit_b.clone();
|
||||
|
||||
let mut buf = Vec::with_capacity(2 * aacs::ALIGNED_UNIT_LEN);
|
||||
let mut buf = Vec::with_capacity(2 * aacs::content::ALIGNED_UNIT_LEN);
|
||||
buf.extend_from_slice(&unit_a);
|
||||
buf.extend_from_slice(&unit_b);
|
||||
|
||||
@@ -1318,15 +1322,15 @@ mod tests {
|
||||
|
||||
assert_eq!(
|
||||
dropped,
|
||||
aacs::ALIGNED_UNIT_LEN,
|
||||
aacs::content::ALIGNED_UNIT_LEN,
|
||||
"exactly one unit's worth of bytes must be reported dropped"
|
||||
);
|
||||
assert!(
|
||||
!aacs::ts_sync_destroyed(&buf[..aacs::ALIGNED_UNIT_LEN]),
|
||||
!aacs::content::ts_sync_destroyed(&buf[..aacs::content::ALIGNED_UNIT_LEN]),
|
||||
"the decryptable unit must come out clear"
|
||||
);
|
||||
assert_eq!(
|
||||
&buf[aacs::ALIGNED_UNIT_LEN..],
|
||||
&buf[aacs::content::ALIGNED_UNIT_LEN..],
|
||||
&unit_b_ciphertext[..],
|
||||
"the undecryptable unit must be restored to ciphertext"
|
||||
);
|
||||
|
||||
+26
-23
@@ -86,9 +86,9 @@ impl AacsCertUnlocker<'_> {
|
||||
// MKB generation (best-effort) — forwarded to each source's
|
||||
// `host_certs(mkb)` so a source MAY select a generation-appropriate cert
|
||||
// (the default impl ignores it). A read failure leaves it `None`.
|
||||
let mkb_gen = aacs::read_mkb_from_drive(session.scsi_mut())
|
||||
let mkb_gen = aacs::inf::read_mkb_from_drive(session.scsi_mut())
|
||||
.ok()
|
||||
.and_then(|m| aacs::mkb_version(&m));
|
||||
.and_then(|m| aacs::mkb::mkb_version(&m));
|
||||
|
||||
// Host certs are keysource-served, never compiled in — unioned from the
|
||||
// explicit `DriveCredentials` and the key-source layer. With ZERO certs
|
||||
@@ -152,10 +152,10 @@ fn unlock_error_to_error(e: &CertUnlockFailure) -> Error {
|
||||
}
|
||||
}
|
||||
|
||||
/// Map a [`CertUnlockFailure`] to a structured [`crate::aacs::UnlockOutcome`]
|
||||
/// Map a [`CertUnlockFailure`] to a structured [`crate::aacs::trace::UnlockOutcome`]
|
||||
/// for the resolution trace (English-free).
|
||||
fn cert_unlock_outcome(e: &CertUnlockFailure) -> crate::aacs::UnlockOutcome {
|
||||
use crate::aacs::UnlockOutcome;
|
||||
fn cert_unlock_outcome(e: &CertUnlockFailure) -> crate::aacs::trace::UnlockOutcome {
|
||||
use crate::aacs::trace::UnlockOutcome;
|
||||
use freemkv_unlock::UnlockError;
|
||||
match e {
|
||||
CertUnlockFailure::NoHostCert { mkb } => UnlockOutcome::NoUsableHostCert { mkb: *mkb },
|
||||
@@ -230,7 +230,10 @@ impl Disc {
|
||||
/// `mkb` is the disc's MKB generation when known, forwarded to each source's
|
||||
/// [`crate::KeySource::host_certs`] so a source MAY return only
|
||||
/// generation-appropriate certs (the default ignores it).
|
||||
fn collect_host_certs(opts: &ScanOptions, mkb: Option<u32>) -> Vec<crate::aacs::HostCert> {
|
||||
fn collect_host_certs(
|
||||
opts: &ScanOptions,
|
||||
mkb: Option<u32>,
|
||||
) -> Vec<crate::aacs::types::HostCert> {
|
||||
// Delegates to the shared cert primitive (the external freemkv-unlock-aacs
|
||||
// plugin uses the same one). Kept as a thin Disc method so the existing
|
||||
// collect_host_certs_* unit tests and call sites are unchanged.
|
||||
@@ -312,14 +315,14 @@ impl Disc {
|
||||
.read_file(reader, crate::aacs::PATH_UNIT_KEY_RO)
|
||||
.or_else(|_| udf_fs.read_file(reader, crate::aacs::PATH_UNIT_KEY_RO_DUPLICATE))
|
||||
.map_err(|_| Error::AacsNoKeys)?;
|
||||
let dh = aacs::disc_hash(&uk_ro_data);
|
||||
let dh = aacs::inf::disc_hash(&uk_ro_data);
|
||||
|
||||
let cc = udf_fs
|
||||
.read_file(reader, crate::aacs::PATH_CONTENT_CERT)
|
||||
.or_else(|_| udf_fs.read_file(reader, crate::aacs::PATH_CONTENT_CERT_ALT))
|
||||
.ok()
|
||||
.as_deref()
|
||||
.and_then(aacs::parse_content_cert);
|
||||
.and_then(aacs::inf::parse_content_cert);
|
||||
let bus_encryption = cc.as_ref().map(|c| c.bus_encryption).unwrap_or(false);
|
||||
// No-cert default = UHD (V20 stride), matching `read_aacs_version` so the
|
||||
// scanned `AacsState.version` and the out-of-band fetch agree. A wrong
|
||||
@@ -328,7 +331,7 @@ impl Disc {
|
||||
let version = cc
|
||||
.as_ref()
|
||||
.map(|c| c.version.major())
|
||||
.unwrap_or(aacs::AACS_MAJOR_UHD);
|
||||
.unwrap_or(aacs::mkb::AACS_MAJOR_UHD);
|
||||
|
||||
// Bus-encryption gate (wrong-keys guard). A bus-encrypted disc (Content
|
||||
// Certificate bus-encryption bit set) carries bus encryption on its
|
||||
@@ -394,12 +397,12 @@ impl Disc {
|
||||
Vec::new()
|
||||
}
|
||||
};
|
||||
let mkb_ver = aacs::mkb_version(&mkb_bytes);
|
||||
let mkb_ver = aacs::mkb::mkb_version(&mkb_bytes);
|
||||
|
||||
tracing::debug!(
|
||||
target: "freemkv::disc",
|
||||
phase = "scan_aacs_vid_only",
|
||||
disc_hash = %aacs::disc_hash_hex(&dh),
|
||||
disc_hash = %aacs::inf::disc_hash_hex(&dh),
|
||||
version,
|
||||
bus_encryption,
|
||||
has_vid = handshake.is_some(),
|
||||
@@ -410,7 +413,7 @@ impl Disc {
|
||||
version,
|
||||
bus_encryption,
|
||||
mkb_version: mkb_ver,
|
||||
disc_hash: aacs::disc_hash_hex(&dh),
|
||||
disc_hash: aacs::inf::disc_hash_hex(&dh),
|
||||
key_source: KeyOrigin::ExternalUk,
|
||||
vuk: None,
|
||||
unit_keys: vec![],
|
||||
@@ -687,14 +690,14 @@ mod tests {
|
||||
let st = Disc::resolve_vid_only(&udf, &mut disc, None).expect("state");
|
||||
assert_eq!(
|
||||
st.version,
|
||||
aacs::AACS_MAJOR_UHD,
|
||||
aacs::mkb::AACS_MAJOR_UHD,
|
||||
"no cert → default UHD (major 2)"
|
||||
);
|
||||
assert!(!st.bus_encryption);
|
||||
}
|
||||
|
||||
/// disc_hash is SHA1 of the Unit_Key_RO.inf bytes, hex with 0x prefix
|
||||
/// and uppercase (aacs::disc_hash + disc_hash_hex). The state's
|
||||
/// and uppercase (aacs::inf::disc_hash + disc_hash_hex). The state's
|
||||
/// disc_hash must match independently computing it over the same bytes.
|
||||
#[test]
|
||||
fn resolve_vid_only_disc_hash_is_sha1_of_unit_key_ro() {
|
||||
@@ -710,7 +713,7 @@ mod tests {
|
||||
}],
|
||||
);
|
||||
let st = Disc::resolve_vid_only(&udf, &mut disc, None).expect("state");
|
||||
let expected = aacs::disc_hash_hex(&aacs::disc_hash(&uk));
|
||||
let expected = aacs::inf::disc_hash_hex(&aacs::inf::disc_hash(&uk));
|
||||
assert_eq!(st.disc_hash, expected);
|
||||
assert!(st.disc_hash.starts_with("0x"));
|
||||
// uk_ro must be stashed verbatim for the external resolver.
|
||||
@@ -746,7 +749,7 @@ mod tests {
|
||||
// Real record stream is the single 16-byte type-0x10 record.
|
||||
assert_eq!(
|
||||
st.mkb.len(),
|
||||
aacs::mkb_content_len(&mkb),
|
||||
aacs::mkb::mkb_content_len(&mkb),
|
||||
"MKB must be trimmed to record-stream length, not the zero-pad"
|
||||
);
|
||||
assert_eq!(st.mkb.len(), 16);
|
||||
@@ -943,7 +946,7 @@ mod tests {
|
||||
// disc_hash must be computed over the DUPLICATE bytes.
|
||||
assert_eq!(
|
||||
st.disc_hash,
|
||||
aacs::disc_hash_hex(&aacs::disc_hash(&uk)),
|
||||
aacs::inf::disc_hash_hex(&aacs::inf::disc_hash(&uk)),
|
||||
"fallback must hash the DUPLICATE Unit_Key_RO.inf"
|
||||
);
|
||||
assert_eq!(st.uk_ro, uk);
|
||||
@@ -965,8 +968,8 @@ mod tests {
|
||||
// the route fails gracefully (AacsNoHostCert), never panics.
|
||||
// ---------------------------------------------------------------
|
||||
|
||||
fn fake_cert(tag: u8) -> aacs::HostCert {
|
||||
aacs::HostCert {
|
||||
fn fake_cert(tag: u8) -> aacs::types::HostCert {
|
||||
aacs::types::HostCert {
|
||||
private_key: [tag; 20],
|
||||
certificate: vec![tag; 92],
|
||||
private_key_v2: None,
|
||||
@@ -975,15 +978,15 @@ mod tests {
|
||||
}
|
||||
|
||||
/// A minimal in-test KeySource that yields no keys but a fixed cert list.
|
||||
struct CertSource(Vec<aacs::HostCert>);
|
||||
struct CertSource(Vec<aacs::types::HostCert>);
|
||||
impl crate::KeySource for CertSource {
|
||||
fn get_uk(
|
||||
&self,
|
||||
_ctx: &dyn crate::keysource::ResolveCtx,
|
||||
) -> Result<Vec<crate::aacs::UnitKey>> {
|
||||
) -> Result<Vec<crate::aacs::boil::UnitKey>> {
|
||||
Ok(Vec::new())
|
||||
}
|
||||
fn host_certs(&self, _mkb: Option<u32>) -> Vec<aacs::HostCert> {
|
||||
fn host_certs(&self, _mkb: Option<u32>) -> Vec<aacs::types::HostCert> {
|
||||
self.0.clone()
|
||||
}
|
||||
}
|
||||
@@ -1077,7 +1080,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn cert_unlock_outcome_maps_to_structured_trace_step() {
|
||||
use crate::aacs::UnlockOutcome;
|
||||
use crate::aacs::trace::UnlockOutcome;
|
||||
use freemkv_unlock::UnlockError;
|
||||
// The libfreemkv-side no-cert case carries the MKB generation.
|
||||
assert_eq!(
|
||||
|
||||
+6
-6
@@ -1083,13 +1083,13 @@ mod tests {
|
||||
}
|
||||
|
||||
/// Build a clear 6144-byte AACS unit (TS syncs at the 192-byte BD-TS
|
||||
/// stride) then encrypt it under `unit_key` so `aacs::decrypt_unit`
|
||||
/// stride) then encrypt it under `unit_key` so `aacs::content::decrypt_unit`
|
||||
/// recovers it cleanly (zero decrypt loss). Mirrors the encrypt helper in
|
||||
/// `sector/decrypting.rs` tests. `tag` distinguishes two units' payloads.
|
||||
fn encrypt_aacs_unit(unit_key: &[u8; 16], tag: u8) -> Vec<u8> {
|
||||
use aes::Aes128;
|
||||
use aes::cipher::{BlockEncrypt, KeyInit, generic_array::GenericArray};
|
||||
let mut unit = vec![0u8; crate::aacs::ALIGNED_UNIT_LEN];
|
||||
let mut unit = vec![0u8; crate::aacs::content::ALIGNED_UNIT_LEN];
|
||||
let mut off = 4;
|
||||
while off < unit.len() {
|
||||
unit[off] = 0x47; // TS sync
|
||||
@@ -1108,7 +1108,7 @@ mod tests {
|
||||
}
|
||||
let cipher = Aes128::new(GenericArray::from_slice(&k));
|
||||
let mut prev = crate::aacs::crypto::AACS_IV;
|
||||
let blocks = (crate::aacs::ALIGNED_UNIT_LEN - 16) / 16;
|
||||
let blocks = (crate::aacs::content::ALIGNED_UNIT_LEN - 16) / 16;
|
||||
for i in 0..blocks {
|
||||
let o = 16 + i * 16;
|
||||
for j in 0..16 {
|
||||
@@ -1124,7 +1124,7 @@ mod tests {
|
||||
|
||||
/// The plaintext that `encrypt_aacs_unit(_, tag)` decrypts back to.
|
||||
fn clear_aacs_unit(tag: u8) -> Vec<u8> {
|
||||
let mut unit = vec![0u8; crate::aacs::ALIGNED_UNIT_LEN];
|
||||
let mut unit = vec![0u8; crate::aacs::content::ALIGNED_UNIT_LEN];
|
||||
let mut off = 4;
|
||||
while off < unit.len() {
|
||||
unit[off] = 0x47;
|
||||
@@ -1600,10 +1600,10 @@ mod tests {
|
||||
/// own batch starts are always unit-aligned; anchoring a later extent
|
||||
/// against the FIRST extent's base mis-aligns whenever the extents' starts
|
||||
/// differ by a non-multiple of 3 sectors. This is the exact arithmetic the
|
||||
/// decrypt-on-read gate (`aacs::is_unit_aligned`) performs.
|
||||
/// decrypt-on-read gate (`aacs::content::is_unit_aligned`) performs.
|
||||
#[test]
|
||||
fn per_extent_base_is_aligned_first_extent_base_is_not() {
|
||||
use crate::aacs::is_unit_aligned;
|
||||
use crate::aacs::content::is_unit_aligned;
|
||||
let ext_a_start = 7000u32; // first extent abs LBA
|
||||
let ext_b_start = 7004u32; // second extent abs LBA (Δ4 — not mult of 3)
|
||||
|
||||
|
||||
+30
-30
@@ -1413,7 +1413,7 @@ impl KeyOrigin {
|
||||
#[derive(Default, Clone)]
|
||||
pub struct DriveCredentials {
|
||||
/// Host certificate(s) + private key(s) for the SCSI AACS handshake.
|
||||
pub host_certs: Vec<crate::aacs::HostCert>,
|
||||
pub host_certs: Vec<crate::aacs::types::HostCert>,
|
||||
}
|
||||
|
||||
/// Options for disc scanning.
|
||||
@@ -1781,8 +1781,8 @@ impl Disc {
|
||||
Ok((inf, mkb, version))
|
||||
}
|
||||
|
||||
/// AACS major version ([`crate::aacs::AACS_MAJOR_BD`] /
|
||||
/// [`crate::aacs::AACS_MAJOR_UHD`]) from the content certificate. Drives the
|
||||
/// AACS major version ([`crate::aacs::mkb::AACS_MAJOR_BD`] /
|
||||
/// [`crate::aacs::mkb::AACS_MAJOR_UHD`]) from the content certificate. Drives the
|
||||
/// `Unit_Key_RO.inf` parse stride (48-byte V10 vs 64-byte V20/V21), so the
|
||||
/// out-of-band key-fetch path parses `enc_title_keys` at the right stride (a
|
||||
/// server VUK then derives the correct unit keys).
|
||||
@@ -1798,7 +1798,7 @@ impl Disc {
|
||||
.or_else(|_| udf_fs.read_file(reader, crate::aacs::PATH_CONTENT_CERT_ALT))
|
||||
.ok()
|
||||
.as_deref()
|
||||
.and_then(crate::aacs::parse_content_cert)
|
||||
.and_then(crate::aacs::inf::parse_content_cert)
|
||||
{
|
||||
Some(c) => c.version.major(),
|
||||
None => {
|
||||
@@ -1808,7 +1808,7 @@ impl Disc {
|
||||
"no readable AACS content certificate; defaulting to the V20/UHD \
|
||||
Unit_Key_RO stride (a VUK-from-server path would otherwise mis-stride)"
|
||||
);
|
||||
crate::aacs::AACS_MAJOR_UHD
|
||||
crate::aacs::mkb::AACS_MAJOR_UHD
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1817,7 +1817,7 @@ impl Disc {
|
||||
///
|
||||
/// `MKB_RO.inf` / `MKB_RW.inf` are allocated to a fixed ~128 MiB and
|
||||
/// zero-padded; the actual record stream is a few MiB. We read a bounded
|
||||
/// prefix, find the record-stream length via [`crate::aacs::mkb_content_len`]
|
||||
/// prefix, find the record-stream length via [`crate::aacs::mkb::mkb_content_len`]
|
||||
/// and return exactly that, growing the prefix if the records run past it.
|
||||
/// This avoids reading 100+ MiB of padding on every scan AND avoids the
|
||||
/// `read_file` `MAX_FILE_BYTES` cap that (since 0.31.0) rejected the padded
|
||||
@@ -1833,13 +1833,13 @@ impl Disc {
|
||||
.read_file_prefix(reader, crate::aacs::PATH_MKB_RO, want)
|
||||
.or_else(|_| udf_fs.read_file_prefix(reader, crate::aacs::PATH_MKB_RW, want))
|
||||
.map_err(|_| Error::AacsNoKeys)?;
|
||||
let n = crate::aacs::mkb_content_len(&buf);
|
||||
let n = crate::aacs::mkb::mkb_content_len(&buf);
|
||||
// `n` strictly inside `buf` => the record walk reached the padding
|
||||
// boundary (full content captured). `buf` shorter than `want` =>
|
||||
// the whole file is already read. Otherwise the records may run
|
||||
// past the prefix — grow and retry, bounded by MAX_BYTES.
|
||||
if (n > 0 && n < buf.len()) || buf.len() < want || want >= MAX_BYTES {
|
||||
return Ok(crate::aacs::trim_mkb(buf));
|
||||
return Ok(crate::aacs::mkb::trim_mkb(buf));
|
||||
}
|
||||
want = (want * 2).min(MAX_BYTES);
|
||||
}
|
||||
@@ -2274,7 +2274,7 @@ impl Disc {
|
||||
/// else (UDF filesystem, BDMV nav, PLAYLIST/CLIPINF) is always clear.
|
||||
///
|
||||
/// The in-read decrypt-verify gate (`DecryptingSectorSource`) uses this so it
|
||||
/// never consults [`ts_sync_destroyed`](crate::aacs::ts_sync_destroyed) about
|
||||
/// never consults [`ts_sync_destroyed`](crate::aacs::content::ts_sync_destroyed) about
|
||||
/// non-content bytes — filesystem data has no TS sync and would otherwise be
|
||||
/// mistaken for ciphertext (the first-2-GB false-positive this fixes).
|
||||
///
|
||||
@@ -2567,9 +2567,9 @@ impl Disc {
|
||||
} else if self.encrypted && self.css.is_none() {
|
||||
self.aacs = Some(AacsState {
|
||||
version: if self.format == DiscFormat::Uhd {
|
||||
crate::aacs::AACS_MAJOR_UHD
|
||||
crate::aacs::mkb::AACS_MAJOR_UHD
|
||||
} else {
|
||||
crate::aacs::AACS_MAJOR_BD
|
||||
crate::aacs::mkb::AACS_MAJOR_BD
|
||||
},
|
||||
bus_encryption: self.format == DiscFormat::Uhd,
|
||||
mkb_version: None,
|
||||
@@ -2667,7 +2667,7 @@ impl Disc {
|
||||
Key::Processing(pks) => supplied.processing_keys = pks,
|
||||
Key::Media(mks) => supplied.media_keys = mks,
|
||||
Key::Volume(vuk) => {
|
||||
supplied.disc_entry = Some(crate::aacs::DiscEntry {
|
||||
supplied.disc_entry = Some(crate::aacs::types::DiscEntry {
|
||||
disc_hash: aacs.disc_hash.clone(),
|
||||
title: String::new(),
|
||||
media_key: None,
|
||||
@@ -2685,8 +2685,8 @@ impl Disc {
|
||||
let uk_ro = aacs.uk_ro.clone();
|
||||
let version_u8 = aacs.version;
|
||||
|
||||
let provider_refs: [&dyn crate::aacs::KeyProvider; 1] = [&supplied];
|
||||
let ctx = crate::aacs::ResolveContext {
|
||||
let provider_refs: [&dyn crate::aacs::provider::KeyProvider; 1] = [&supplied];
|
||||
let ctx = crate::aacs::resolve::ResolveContext {
|
||||
unit_key_ro: &uk_ro,
|
||||
content_cert: None,
|
||||
volume_id: &volume_id,
|
||||
@@ -2700,7 +2700,7 @@ impl Disc {
|
||||
// reason-preserving wrapper threads the no-key cause out so the
|
||||
// decrypt gate can report E7017 (had derivation material but no VID)
|
||||
// vs E7022 (no usable material) instead of a flat AacsKeyRejected.
|
||||
let resolved = crate::aacs::resolve_keys_with_reason(&ctx, version_u8)
|
||||
let resolved = crate::aacs::resolve::resolve_keys_with_reason(&ctx, version_u8)
|
||||
.map_err(|_reason| crate::error::Error::AacsKeyRejected)?;
|
||||
|
||||
if resolved.unit_keys.is_empty() {
|
||||
@@ -3203,7 +3203,7 @@ impl Disc {
|
||||
// decrypts and is AACS-keyed. Region read-starts are aligned DOWN to a
|
||||
// unit boundary in the loop below; a fresh sweep starts at LBA 0 (already
|
||||
// aligned), so alignment only bites on resume NonTried regions.
|
||||
const UNIT_SECTORS: u16 = (crate::aacs::ALIGNED_UNIT_LEN / 2048) as u16; // 3
|
||||
const UNIT_SECTORS: u16 = (crate::aacs::content::ALIGNED_UNIT_LEN / 2048) as u16; // 3
|
||||
if decrypt_is_aacs && batch % UNIT_SECTORS != 0 {
|
||||
batch = batch.saturating_add(UNIT_SECTORS - (batch % UNIT_SECTORS));
|
||||
}
|
||||
@@ -3285,7 +3285,7 @@ impl Disc {
|
||||
// sweep's NonTried region starts at 0, already unit-aligned; this only
|
||||
// shifts resume regions that begin mid-unit.
|
||||
let mut pos = if decrypt_is_aacs {
|
||||
let unit_bytes = crate::aacs::ALIGNED_UNIT_LEN as u64;
|
||||
let unit_bytes = crate::aacs::content::ALIGNED_UNIT_LEN as u64;
|
||||
region_pos - (region_pos % unit_bytes)
|
||||
} else {
|
||||
region_pos
|
||||
@@ -4240,8 +4240,8 @@ mod tests {
|
||||
/// `sector::decrypting::tests::aacs_unaligned_start_lba_rejected`.
|
||||
#[test]
|
||||
fn aacs_sweep_batch_and_region_are_unit_aligned() {
|
||||
const UNIT_SECTORS: u16 = (crate::aacs::ALIGNED_UNIT_LEN / 2048) as u16; // 3
|
||||
let unit_bytes = crate::aacs::ALIGNED_UNIT_LEN as u64; // 6144
|
||||
const UNIT_SECTORS: u16 = (crate::aacs::content::ALIGNED_UNIT_LEN / 2048) as u16; // 3
|
||||
let unit_bytes = crate::aacs::content::ALIGNED_UNIT_LEN as u64; // 6144
|
||||
|
||||
// (a) Batch rounding: ecc_sectors() for UHD/BD is 32, not a multiple of 3.
|
||||
// The decrypting-AACS path rounds it up to the next multiple of 3 (33).
|
||||
@@ -4700,7 +4700,7 @@ mod tests {
|
||||
// The resolver classifies a device-keys-but-zero-VID context as
|
||||
// `VidUnavailable`; that reason rides on `aacs_error`.
|
||||
let supplied = crate::aacs::provider::SuppliedKey {
|
||||
device_keys: vec![crate::aacs::DeviceKey {
|
||||
device_keys: vec![crate::aacs::types::DeviceKey {
|
||||
key: [0x11; 16],
|
||||
node: 1,
|
||||
uv: 1,
|
||||
@@ -4710,14 +4710,14 @@ mod tests {
|
||||
media_keys: Vec::new(),
|
||||
disc_entry: None,
|
||||
};
|
||||
let provider_refs: [&dyn crate::aacs::KeyProvider; 1] = [&supplied];
|
||||
let provider_refs: [&dyn crate::aacs::provider::KeyProvider; 1] = [&supplied];
|
||||
// A minimal but parseable Unit_Key_RO.inf (uk_pos=32, zero unit keys)
|
||||
// so resolution proceeds to the path-try logic and fails for lack of a
|
||||
// VID — not because the .inf failed to parse.
|
||||
let mut uk_ro = vec![0u8; 40];
|
||||
uk_ro[0..4].copy_from_slice(&32u32.to_be_bytes()); // uk_pos = 32
|
||||
// num_unit_keys = 0 (BE16) at uk_pos -> parses to an empty key file.
|
||||
let ctx = crate::aacs::ResolveContext {
|
||||
let ctx = crate::aacs::resolve::ResolveContext {
|
||||
unit_key_ro: &uk_ro,
|
||||
content_cert: None,
|
||||
volume_id: &[0u8; 16], // the "no VID" sentinel
|
||||
@@ -4725,8 +4725,8 @@ mod tests {
|
||||
mkb: None,
|
||||
};
|
||||
assert_eq!(
|
||||
crate::aacs::resolve_keys_with_reason(&ctx, 2).err(),
|
||||
Some(crate::aacs::ResolveFailure::VidUnavailable),
|
||||
crate::aacs::resolve::resolve_keys_with_reason(&ctx, 2).err(),
|
||||
Some(crate::aacs::resolve::ResolveFailure::VidUnavailable),
|
||||
"device keys + zero VID must classify as VidUnavailable"
|
||||
);
|
||||
|
||||
@@ -4750,8 +4750,8 @@ mod tests {
|
||||
media_keys: Vec::new(),
|
||||
disc_entry: None,
|
||||
};
|
||||
let provider_refs_none: [&dyn crate::aacs::KeyProvider; 1] = [&supplied_none];
|
||||
let ctx_none = crate::aacs::ResolveContext {
|
||||
let provider_refs_none: [&dyn crate::aacs::provider::KeyProvider; 1] = [&supplied_none];
|
||||
let ctx_none = crate::aacs::resolve::ResolveContext {
|
||||
unit_key_ro: &uk_ro,
|
||||
content_cert: None,
|
||||
volume_id: &[0u8; 16],
|
||||
@@ -4759,8 +4759,8 @@ mod tests {
|
||||
mkb: None,
|
||||
};
|
||||
assert_eq!(
|
||||
crate::aacs::resolve_keys_with_reason(&ctx_none, 2).err(),
|
||||
Some(crate::aacs::ResolveFailure::NoMaterial),
|
||||
crate::aacs::resolve::resolve_keys_with_reason(&ctx_none, 2).err(),
|
||||
Some(crate::aacs::resolve::ResolveFailure::NoMaterial),
|
||||
"no key material must classify as NoMaterial"
|
||||
);
|
||||
|
||||
@@ -5030,8 +5030,8 @@ mod tests {
|
||||
let vuk = [0x5au8; 16];
|
||||
let enc0 = [0x12u8; 16];
|
||||
let enc1 = [0x34u8; 16];
|
||||
let exp0 = crate::aacs::decrypt_unit_key(&vuk, &enc0);
|
||||
let exp1 = crate::aacs::decrypt_unit_key(&vuk, &enc1);
|
||||
let exp0 = crate::aacs::derive::decrypt_unit_key(&vuk, &enc0);
|
||||
let exp1 = crate::aacs::derive::decrypt_unit_key(&vuk, &enc1);
|
||||
|
||||
let mut disc = make_test_disc(1000, "UHD");
|
||||
disc.encrypted = true;
|
||||
|
||||
+10
-9
@@ -7,7 +7,7 @@
|
||||
//! grid (clips can start off the 6144 grid and fragment across UDF extents). So
|
||||
//! this gate BUFFERS the disc-absolute read stream and re-ALIGNS it into
|
||||
//! clip-file units, then applies the standards-correct
|
||||
//! [`crate::aacs::unit_is_clean_ts`] gate (libaacs `_verify_ts`, all-32 syncs).
|
||||
//! [`crate::aacs::content::unit_is_clean_ts`] gate (libaacs `_verify_ts`, all-32 syncs).
|
||||
//!
|
||||
//! FAIL-SAFE CONTRACT (this sits in the middle of every read, so it must never
|
||||
//! break a good read): the gate can ONLY downgrade a unit it is *confident* is
|
||||
@@ -25,7 +25,8 @@
|
||||
|
||||
use std::collections::{HashMap, VecDeque};
|
||||
|
||||
use crate::aacs::{self, ALIGNED_UNIT_LEN};
|
||||
use crate::aacs::content::ALIGNED_UNIT_LEN;
|
||||
use crate::aacs::{self};
|
||||
use crate::consts::SECTOR_BYTES_U64;
|
||||
use crate::decrypt::DecryptKeys;
|
||||
use crate::sector::KeyFetch;
|
||||
@@ -57,7 +58,7 @@ pub enum ContainerKind {
|
||||
Ts,
|
||||
/// HD-DVD `.evo` — MPEG-2 program stream (pack-start `00 00 01 BA`).
|
||||
/// NOT yet enabled by enumeration; present so adding HD-DVD is a one-mapping
|
||||
/// change. See [`crate::aacs::unit_is_clean_ps`] for the (unvalidated) check.
|
||||
/// change. See [`crate::aacs::content::unit_is_clean_ps`] for the (unvalidated) check.
|
||||
Ps,
|
||||
}
|
||||
|
||||
@@ -187,8 +188,8 @@ impl UnitVerifier {
|
||||
/// The post-decrypt structural check for a clip's container.
|
||||
fn accept_for(&self, clip: u32) -> fn(&[u8]) -> bool {
|
||||
match self.containers[clip as usize] {
|
||||
ContainerKind::Ts => aacs::unit_is_clean_ts,
|
||||
ContainerKind::Ps => aacs::unit_is_clean_ps,
|
||||
ContainerKind::Ts => aacs::content::unit_is_clean_ts,
|
||||
ContainerKind::Ps => aacs::content::unit_is_clean_ps,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -301,8 +302,8 @@ impl UnitVerifier {
|
||||
}
|
||||
|
||||
/// Can this fully-assembled unit be decrypted + verified? `accept` is the
|
||||
/// container's strict structural check ([`aacs::unit_is_clean_ts`] for TS,
|
||||
/// [`aacs::unit_is_clean_ps`] for PS) — the only format-specific part; the
|
||||
/// container's strict structural check ([`aacs::content::unit_is_clean_ts`] for TS,
|
||||
/// [`aacs::content::unit_is_clean_ps`] for PS) — the only format-specific part; the
|
||||
/// AACS crypto is container-agnostic. Returns the 3-state [`Decryptability`].
|
||||
fn decryptability(
|
||||
&mut self,
|
||||
@@ -318,7 +319,7 @@ impl UnitVerifier {
|
||||
// ENCRYPTED units that no key opens are ever flagged. (A real bad READ of
|
||||
// clear content is still caught by the normal SCSI read-error path; this
|
||||
// gate just won't false-flag it.)
|
||||
if !aacs::aacs_unit_encrypted(raw) {
|
||||
if !aacs::content::aacs_unit_encrypted(raw) {
|
||||
return if accept(raw) {
|
||||
Decryptability::Decryptable
|
||||
} else {
|
||||
@@ -363,7 +364,7 @@ impl UnitVerifier {
|
||||
fn try_keys(&self, raw: &[u8; ALIGNED_UNIT_LEN], accept: fn(&[u8]) -> bool) -> bool {
|
||||
for k in &self.keys {
|
||||
let mut scratch = *raw;
|
||||
if aacs::decrypt_unit_checked(&mut scratch, k, accept) {
|
||||
if aacs::content::decrypt_unit_checked(&mut scratch, k, accept) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
+25
-23
@@ -3,12 +3,12 @@
|
||||
//! libfreemkv performs NO key lookup. An application resolves a disc's keys
|
||||
//! through one or more [`KeySource`]s, each an adapter over a backing store (a
|
||||
//! keydb file, a key server, the mapfile cache). A source's job is to return the
|
||||
//! disc's terminal **Unit Keys** ([`crate::aacs::UnitKey`]). It knows what
|
||||
//! disc's terminal **Unit Keys** ([`crate::aacs::boil::UnitKey`]). It knows what
|
||||
//! material it holds (a DK / MK / VUK / pre-decrypted UK) and what it must fetch
|
||||
//! from the disc (VID, MKB, encrypted title keys, content samples) to get there;
|
||||
//! it orchestrates the derivation by calling libfreemkv's own boil-down crypto
|
||||
//! primitives ([`crate::aacs::mk_from_dk`] / [`crate::aacs::vuk_from_mk`] /
|
||||
//! [`crate::aacs::uk_from_vuk`]) through the [`ResolveCtx`] handed to it.
|
||||
//! primitives ([`crate::aacs::boil::mk_from_dk`] / [`crate::aacs::boil::vuk_from_mk`] /
|
||||
//! [`crate::aacs::boil::uk_from_vuk`]) through the [`ResolveCtx`] handed to it.
|
||||
//!
|
||||
//! libfreemkv still OWNS the crypto: the boil-down primitives and the AES live
|
||||
//! here. A source owns only PATH ORCHESTRATION — deciding which primitive to
|
||||
@@ -17,7 +17,8 @@
|
||||
//! keeping key *policy* (which store, which order, online vs local) out of the
|
||||
//! library.
|
||||
|
||||
use crate::aacs::{HostCert, UnitKey, Vid};
|
||||
use crate::aacs::boil::{UnitKey, Vid};
|
||||
use crate::aacs::types::HostCert;
|
||||
use crate::disc::Key;
|
||||
use crate::error::Error;
|
||||
|
||||
@@ -74,8 +75,8 @@ pub trait ResolveCtx {
|
||||
/// Raw MKB bytes (may be empty when not captured).
|
||||
fn mkb(&self) -> Result<&[u8], Error>;
|
||||
/// The disc's encrypted title keys, parsed from `Unit_Key_RO.inf` the same
|
||||
/// way the library's resolver parses them ([`crate::aacs::parse_unit_key_ro`]),
|
||||
/// in on-disc order. Feed straight into [`crate::aacs::uk_from_vuk`].
|
||||
/// way the library's resolver parses them ([`crate::aacs::inf::parse_unit_key_ro`]),
|
||||
/// in on-disc order. Feed straight into [`crate::aacs::boil::uk_from_vuk`].
|
||||
fn enc_title_keys(&self) -> Result<&[[u8; 16]], Error>;
|
||||
/// Up to `n` encrypted on-disc content sample units, for a source that
|
||||
/// validates a candidate server-side against real ciphertext.
|
||||
@@ -113,7 +114,8 @@ impl<'a> DiscInputsCtx<'a> {
|
||||
/// title keys — the parse failure is swallowed here, not surfaced as an
|
||||
/// error.
|
||||
pub fn new(inputs: &'a DiscInputs) -> Self {
|
||||
use crate::aacs::{AacsVersion, parse_unit_key_ro};
|
||||
use crate::aacs::inf::parse_unit_key_ro;
|
||||
use crate::aacs::mkb::AacsVersion;
|
||||
let enc_keys = if inputs.unit_key_ro.is_empty() {
|
||||
Vec::new()
|
||||
} else {
|
||||
@@ -164,8 +166,8 @@ impl ResolveCtx for DiscInputsCtx<'_> {
|
||||
/// holds, orchestrates the derivation down to Unit Keys using the library's
|
||||
/// boil-down crypto primitives — never re-implementing AES. A source that holds
|
||||
/// pre-decrypted Unit Keys returns them directly; one that holds a VUK calls
|
||||
/// [`crate::aacs::uk_from_vuk`]; one that holds device keys calls
|
||||
/// [`crate::aacs::mk_from_dk`] → [`crate::aacs::vuk_from_mk`] → `uk_from_vuk`.
|
||||
/// [`crate::aacs::boil::uk_from_vuk`]; one that holds device keys calls
|
||||
/// [`crate::aacs::boil::mk_from_dk`] → [`crate::aacs::boil::vuk_from_mk`] → `uk_from_vuk`.
|
||||
///
|
||||
/// Returning an empty `Vec` means "no key for this disc from this source"; an
|
||||
/// `Err` means the source itself failed (I/O, parse, network). The caller
|
||||
@@ -209,7 +211,7 @@ pub fn resolve_and_apply(
|
||||
}
|
||||
|
||||
/// Like [`resolve_and_apply`] but also returns a structured
|
||||
/// [`crate::aacs::ResolutionTrace`] recording, per source, what happened — for
|
||||
/// [`crate::aacs::trace::ResolutionTrace`] recording, per source, what happened — for
|
||||
/// applications to render. ZERO English; the trace is typed enums only.
|
||||
///
|
||||
/// One-shot per source: each source's [`KeySource::get_uk`] is called exactly
|
||||
@@ -219,8 +221,8 @@ pub fn resolve_and_apply(
|
||||
/// success — so a wrong/partial key set is rejected and the loop continues.
|
||||
///
|
||||
/// CPS-unit numbering: a source returns Unit Keys carrying the POSITIONAL index
|
||||
/// from [`crate::aacs::uk_from_vuk`]; the library's canonical CPS-unit number is
|
||||
/// `position + 1` (matching [`crate::aacs::parse_unit_key_ro`]'s `(i + 1)`), so
|
||||
/// from [`crate::aacs::boil::uk_from_vuk`]; the library's canonical CPS-unit number is
|
||||
/// `position + 1` (matching [`crate::aacs::inf::parse_unit_key_ro`]'s `(i + 1)`), so
|
||||
/// the committed `AacsState.unit_keys` is byte-identical to the library-resolved
|
||||
/// path. The number is cosmetic for descramble (the decrypt path strips it and
|
||||
/// tries every key) but is kept faithful to the resolver's convention.
|
||||
@@ -228,10 +230,10 @@ pub fn resolve_and_apply_traced(
|
||||
sources: &[Box<dyn KeySource>],
|
||||
inputs: &DiscInputs,
|
||||
disc: &mut crate::Disc,
|
||||
) -> (bool, crate::aacs::ResolutionTrace) {
|
||||
) -> (bool, crate::aacs::trace::ResolutionTrace) {
|
||||
use crate::aacs::trace::{KeyNode, KeyOutcome, KeyStep};
|
||||
|
||||
let mut trace = crate::aacs::ResolutionTrace::new();
|
||||
let mut trace = crate::aacs::trace::ResolutionTrace::new();
|
||||
|
||||
// The ctx parses Unit_Key_RO.inf at the stride for `inputs.version` (the
|
||||
// disc's own AACS major), so the stride is the disc's single source of truth.
|
||||
@@ -341,7 +343,7 @@ pub fn key_fetch(
|
||||
/// `start_lba`), which the library owns. A key source is *handed* these bytes
|
||||
/// via `DiscInputs.samples`; it never reads the disc itself.
|
||||
///
|
||||
/// "Encrypted" is decided by [`crate::aacs::ts_sync_destroyed`] — the SAME
|
||||
/// "Encrypted" is decided by [`crate::aacs::content::ts_sync_destroyed`] — the SAME
|
||||
/// predicate the decrypt gate uses — so all sides agree. A clip opens with clear
|
||||
/// navigation units (PAT/PMT, menus); only the feature body is scrambled, and a
|
||||
/// clear unit proves nothing, so this collects only scrambled ones — probing
|
||||
@@ -352,7 +354,7 @@ pub fn read_encrypted_units(
|
||||
title: &crate::disc::DiscTitle,
|
||||
n: usize,
|
||||
) -> Vec<Vec<u8>> {
|
||||
use crate::aacs::{ALIGNED_UNIT_LEN, ALIGNED_UNIT_SECTORS, ts_sync_destroyed};
|
||||
use crate::aacs::content::{ALIGNED_UNIT_LEN, ALIGNED_UNIT_SECTORS, ts_sync_destroyed};
|
||||
const CHUNK_UNITS: u32 = 15; // 45 sectors/read — under the drive transfer cap
|
||||
// Probe several evenly-spaced points across EACH extent rather than only the
|
||||
// midpoint-and-forward: a title whose encrypted feature starts late, or whose
|
||||
@@ -413,7 +415,7 @@ pub fn read_encrypted_units(
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::aacs::UnitKey;
|
||||
use crate::aacs::boil::UnitKey;
|
||||
use std::sync::{Arc, Mutex};
|
||||
|
||||
// ── KeySource default-method behaviour ────────────────────────────────────
|
||||
@@ -454,7 +456,7 @@ mod tests {
|
||||
let inputs = DiscInputs {
|
||||
disc_hash: "0xABC".into(),
|
||||
volume_id: [0u8; 16],
|
||||
version: crate::aacs::AACS_MAJOR_BD,
|
||||
version: crate::aacs::mkb::AACS_MAJOR_BD,
|
||||
mkb: vec![1, 2, 3],
|
||||
unit_key_ro: uk_ro,
|
||||
samples: vec![vec![9u8; 4], vec![8u8; 4], vec![7u8; 4]],
|
||||
@@ -510,7 +512,7 @@ mod tests {
|
||||
let inputs = DiscInputs {
|
||||
disc_hash: "0x00".into(),
|
||||
volume_id: [0u8; 16],
|
||||
version: crate::aacs::AACS_MAJOR_UHD,
|
||||
version: crate::aacs::mkb::AACS_MAJOR_UHD,
|
||||
mkb: Vec::new(),
|
||||
unit_key_ro: Vec::new(),
|
||||
samples: Vec::new(),
|
||||
@@ -531,7 +533,7 @@ mod tests {
|
||||
DiscInputs {
|
||||
disc_hash: String::new(),
|
||||
volume_id: [0u8; 16],
|
||||
version: crate::aacs::AACS_MAJOR_UHD,
|
||||
version: crate::aacs::mkb::AACS_MAJOR_UHD,
|
||||
mkb: Vec::new(),
|
||||
unit_key_ro: Vec::new(),
|
||||
samples: Vec::new(),
|
||||
@@ -621,7 +623,7 @@ mod tests {
|
||||
});
|
||||
|
||||
let cb = key_fetch(empty_inputs(), make);
|
||||
let samples = vec![vec![0xEEu8; crate::aacs::ALIGNED_UNIT_LEN]];
|
||||
let samples = vec![vec![0xEEu8; crate::aacs::content::ALIGNED_UNIT_LEN]];
|
||||
let got = cb(&samples);
|
||||
assert_eq!(
|
||||
got,
|
||||
@@ -643,7 +645,7 @@ mod tests {
|
||||
/// finds the early scrambled band.
|
||||
#[test]
|
||||
fn read_encrypted_units_finds_scrambled_content_off_the_midpoint() {
|
||||
use crate::aacs::{ALIGNED_UNIT_LEN, ALIGNED_UNIT_SECTORS, ts_sync_destroyed};
|
||||
use crate::aacs::content::{ALIGNED_UNIT_LEN, ALIGNED_UNIT_SECTORS, ts_sync_destroyed};
|
||||
use crate::error::Result;
|
||||
use crate::sector::SectorSource;
|
||||
|
||||
@@ -725,7 +727,7 @@ mod tests {
|
||||
/// prior single-key fixtures passed regardless of stride.
|
||||
#[test]
|
||||
fn disc_inputs_ctx_parses_unit_keys_at_the_version_stride() {
|
||||
use crate::aacs::{AACS_MAJOR_BD, AACS_MAJOR_UHD};
|
||||
use crate::aacs::mkb::{AACS_MAJOR_BD, AACS_MAJOR_UHD};
|
||||
const UK_POS: usize = 64;
|
||||
let mut inf = vec![0u8; 200];
|
||||
inf[0..4].copy_from_slice(&(UK_POS as u32).to_be_bytes()); // uk_pos
|
||||
|
||||
+1
-1
@@ -366,7 +366,7 @@ impl TsDemuxer {
|
||||
|
||||
// P3/B1 CONCEALMENT MARKER. The decrypt layer fills an undecryptable
|
||||
// aligned unit with NULL-TS packets (PID 0x1FFF) that carry an
|
||||
// adaptation-field discontinuity_indicator (see `aacs::fill_null_ts_unit`).
|
||||
// adaptation-field discontinuity_indicator (see `aacs::content::fill_null_ts_unit`).
|
||||
// This is the authoritative loss signal — unlike a tracked PID's 4-bit
|
||||
// continuity_counter it is CC-INDEPENDENT, so it survives a loss that is
|
||||
// an exact multiple of 16 packets and a loss at the very start of a PID
|
||||
|
||||
+44
-39
@@ -146,7 +146,7 @@ pub struct DecryptingSectorSource<S: SectorSource> {
|
||||
scratch: Vec<u8>,
|
||||
/// MUX loss-concealment switch (P3 / Edit-2). When `true`, a content unit
|
||||
/// that genuinely won't decrypt is NOT a read failure: it is overwritten with
|
||||
/// valid NULL TS packets ([`crate::aacs::fill_null_ts_unit`]), tallied into
|
||||
/// valid NULL TS packets ([`crate::aacs::content::fill_null_ts_unit`]), tallied into
|
||||
/// [`decrypt_dropped`](Self::decrypt_dropped), logged loud with its LBA, and
|
||||
/// the read returns `Ok` so the mux KEEPS GOING (it can never abort over an
|
||||
/// undecryptable unit). This is the spec's "decrypt-verify is a RIP gate, not
|
||||
@@ -197,7 +197,7 @@ impl<S: SectorSource> DecryptingSectorSource<S> {
|
||||
/// (sorted/merged `(start_lba, sector_count)` — see
|
||||
/// [`Disc::encrypted_content_ranges`](crate::Disc::encrypted_content_ranges)).
|
||||
/// Units outside content (UDF filesystem / BDMV nav) pass through untouched,
|
||||
/// so [`ts_sync_destroyed`](crate::aacs::ts_sync_destroyed) is never consulted
|
||||
/// so [`ts_sync_destroyed`](crate::aacs::content::ts_sync_destroyed) is never consulted
|
||||
/// about non-content bytes. Whole-disc readers (sweep / patch) set this; the
|
||||
/// mux leaves it unset because it only ever reads title extents.
|
||||
pub fn with_content_ranges(mut self, ranges: Arc<[(u32, u32)]>) -> Self {
|
||||
@@ -301,14 +301,14 @@ impl<S: SectorSource> DecryptingSectorSource<S> {
|
||||
content: Option<&[(u32, u32)]>,
|
||||
prev_dropped: usize,
|
||||
) -> usize {
|
||||
let unit_len = crate::aacs::ALIGNED_UNIT_LEN;
|
||||
let unit_len = crate::aacs::content::ALIGNED_UNIT_LEN;
|
||||
// Gather up to MAX_FETCH_SAMPLES still-scrambled aligned units — the
|
||||
// exact on-disc ciphertext no held key could open. A trailing partial
|
||||
// unit (chunks_exact remainder) can't be a whole scrambled unit, so
|
||||
// skipping it is correct.
|
||||
let mut samples: Vec<Vec<u8>> = Vec::new();
|
||||
for chunk in buf.chunks_exact(unit_len) {
|
||||
if crate::aacs::aacs_unit_needs_decrypt(chunk) {
|
||||
if crate::aacs::content::aacs_unit_needs_decrypt(chunk) {
|
||||
samples.push(chunk.to_vec());
|
||||
if samples.len() >= MAX_FETCH_SAMPLES {
|
||||
break;
|
||||
@@ -391,7 +391,7 @@ impl<S: SectorSource> DecryptingSectorSource<S> {
|
||||
content: Option<&[(u32, u32)]>,
|
||||
keys: &DecryptKeys,
|
||||
) {
|
||||
let unit_len = crate::aacs::ALIGNED_UNIT_LEN;
|
||||
let unit_len = crate::aacs::content::ALIGNED_UNIT_LEN;
|
||||
let unit_sectors = (unit_len / 2048) as u32;
|
||||
// Only AACS produces decrypt-verify failures; None / CSS never reach here
|
||||
// with a non-zero dropped count.
|
||||
@@ -415,12 +415,12 @@ impl<S: SectorSource> DecryptingSectorSource<S> {
|
||||
// A unit that decrypted is no longer sync-destroyed; a CPI-clear or
|
||||
// non-content unit is gated out. Only undecryptable in-content units
|
||||
// that are flagged encrypted carry signal.
|
||||
if !in_content || !crate::aacs::aacs_unit_needs_decrypt(chunk) {
|
||||
if !in_content || !crate::aacs::content::aacs_unit_needs_decrypt(chunk) {
|
||||
continue;
|
||||
}
|
||||
let all_zero = chunk.iter().all(|&b| b == 0);
|
||||
let ts_sync = crate::aacs::ts_sync_count(chunk);
|
||||
let ts_total = crate::aacs::ts_packet_total(chunk);
|
||||
let ts_sync = crate::aacs::content::ts_sync_count(chunk);
|
||||
let ts_total = crate::aacs::content::ts_packet_total(chunk);
|
||||
let mut seen = [false; 256];
|
||||
for &b in chunk {
|
||||
seen[b as usize] = true;
|
||||
@@ -431,10 +431,10 @@ impl<S: SectorSource> DecryptingSectorSource<S> {
|
||||
for (_, k) in unit_keys.iter() {
|
||||
let mut attempt = chunk.to_vec();
|
||||
if let Some(ref rdk_key) = rdk {
|
||||
crate::aacs::decrypt_bus(&mut attempt, rdk_key);
|
||||
crate::aacs::content::decrypt_bus(&mut attempt, rdk_key);
|
||||
}
|
||||
crate::aacs::decrypt_unit(&mut attempt, k);
|
||||
let s = crate::aacs::ts_sync_count(&attempt);
|
||||
crate::aacs::content::decrypt_unit(&mut attempt, k);
|
||||
let s = crate::aacs::content::ts_sync_count(&attempt);
|
||||
if s > best_sync {
|
||||
best_sync = s;
|
||||
}
|
||||
@@ -497,7 +497,7 @@ impl<S: SectorSource> SectorSource for DecryptingSectorSource<S> {
|
||||
// units (else its readable units are wrongly rejected → "Decryption
|
||||
// failed" on exactly those titles).
|
||||
if matches!(self.keys, DecryptKeys::Aacs { .. })
|
||||
&& !crate::aacs::is_unit_aligned(lba, self.unit_base)
|
||||
&& !crate::aacs::content::is_unit_aligned(lba, self.unit_base)
|
||||
{
|
||||
return Err(crate::error::Error::DecryptFailed);
|
||||
}
|
||||
@@ -585,7 +585,7 @@ impl<S: SectorSource> SectorSource for DecryptingSectorSource<S> {
|
||||
// rip stays fail-loud. Ciphertext is never passed downstream: it is
|
||||
// replaced by null packets, not emitted.
|
||||
if self.tolerate_decrypt_loss && !self.verify_only {
|
||||
let unit_len = crate::aacs::ALIGNED_UNIT_LEN;
|
||||
let unit_len = crate::aacs::content::ALIGNED_UNIT_LEN;
|
||||
let mut concealed = 0usize;
|
||||
let mut first_lba = lba;
|
||||
for (i, chunk) in buf[..n].chunks_mut(unit_len).enumerate() {
|
||||
@@ -604,11 +604,12 @@ impl<S: SectorSource> SectorSource for DecryptingSectorSource<S> {
|
||||
// un-restored ciphertext. In-content gating already happened in
|
||||
// `decrypt_buf`, which restored only failed units to ciphertext;
|
||||
// clear nav and decrypted tails pass through clean.
|
||||
if crate::aacs::aacs_unit_still_ciphertext(chunk) {
|
||||
if crate::aacs::content::aacs_unit_still_ciphertext(chunk) {
|
||||
if concealed == 0 {
|
||||
first_lba = lba + (i as u32) * crate::aacs::ALIGNED_UNIT_SECTORS;
|
||||
first_lba =
|
||||
lba + (i as u32) * crate::aacs::content::ALIGNED_UNIT_SECTORS;
|
||||
}
|
||||
crate::aacs::fill_null_ts_unit(chunk);
|
||||
crate::aacs::content::fill_null_ts_unit(chunk);
|
||||
concealed += 1;
|
||||
}
|
||||
}
|
||||
@@ -631,8 +632,10 @@ impl<S: SectorSource> SectorSource for DecryptingSectorSource<S> {
|
||||
// flags, loudly. Cryptographically unreachable in practice.
|
||||
let mut forced = 0usize;
|
||||
for chunk in buf[..n].chunks_mut(unit_len) {
|
||||
if chunk.len() == unit_len && crate::aacs::aacs_unit_needs_decrypt(chunk) {
|
||||
crate::aacs::fill_null_ts_unit(chunk);
|
||||
if chunk.len() == unit_len
|
||||
&& crate::aacs::content::aacs_unit_needs_decrypt(chunk)
|
||||
{
|
||||
crate::aacs::content::fill_null_ts_unit(chunk);
|
||||
forced += 1;
|
||||
}
|
||||
}
|
||||
@@ -1253,12 +1256,12 @@ mod tests {
|
||||
}
|
||||
|
||||
/// Build a clear 6144-byte AACS unit (TS syncs at the BD-TS stride) then
|
||||
/// encrypt it under `unit_key` so `aacs::decrypt_unit` recovers it. Mirrors
|
||||
/// encrypt it under `unit_key` so `aacs::content::decrypt_unit` recovers it. Mirrors
|
||||
/// the encrypt helper in `crate::decrypt`'s tests.
|
||||
fn encrypt_aacs_unit(unit_key: &[u8; 16]) -> Vec<u8> {
|
||||
use aes::Aes128;
|
||||
use aes::cipher::{BlockEncrypt, KeyInit, generic_array::GenericArray};
|
||||
let mut unit = vec![0u8; crate::aacs::ALIGNED_UNIT_LEN];
|
||||
let mut unit = vec![0u8; crate::aacs::content::ALIGNED_UNIT_LEN];
|
||||
let mut off = 4;
|
||||
while off < unit.len() {
|
||||
unit[off] = 0x47;
|
||||
@@ -1274,7 +1277,7 @@ mod tests {
|
||||
}
|
||||
let cipher = Aes128::new(GenericArray::from_slice(&k));
|
||||
let mut prev = crate::aacs::crypto::AACS_IV;
|
||||
let blocks = (crate::aacs::ALIGNED_UNIT_LEN - 16) / 16;
|
||||
let blocks = (crate::aacs::content::ALIGNED_UNIT_LEN - 16) / 16;
|
||||
for i in 0..blocks {
|
||||
let o = 16 + i * 16;
|
||||
for j in 0..16 {
|
||||
@@ -1347,7 +1350,7 @@ mod tests {
|
||||
);
|
||||
assert_eq!(
|
||||
loss.load(Ordering::Relaxed),
|
||||
crate::aacs::ALIGNED_UNIT_LEN as u64,
|
||||
crate::aacs::content::ALIGNED_UNIT_LEN as u64,
|
||||
"the undecryptable unit is tallied as loss before the read errors"
|
||||
);
|
||||
|
||||
@@ -1358,7 +1361,7 @@ mod tests {
|
||||
);
|
||||
assert_eq!(
|
||||
loss.load(Ordering::Relaxed),
|
||||
2 * crate::aacs::ALIGNED_UNIT_LEN as u64,
|
||||
2 * crate::aacs::content::ALIGNED_UNIT_LEN as u64,
|
||||
"loss must accumulate across reads"
|
||||
);
|
||||
|
||||
@@ -1392,7 +1395,7 @@ mod tests {
|
||||
// One unit encrypted under real_key, plus one trailing CLEAR (TS-sync)
|
||||
// unit so we can confirm conceal touches ONLY the undecryptable unit.
|
||||
let enc = encrypt_aacs_unit(&real_key);
|
||||
let mut clear = vec![0u8; crate::aacs::ALIGNED_UNIT_LEN];
|
||||
let mut clear = vec![0u8; crate::aacs::content::ALIGNED_UNIT_LEN];
|
||||
let mut o = 4;
|
||||
while o < clear.len() {
|
||||
clear[o] = 0x47;
|
||||
@@ -1441,13 +1444,13 @@ mod tests {
|
||||
// The undecryptable unit is tallied as loss.
|
||||
assert_eq!(
|
||||
loss.load(Ordering::Relaxed),
|
||||
crate::aacs::ALIGNED_UNIT_LEN as u64,
|
||||
crate::aacs::content::ALIGNED_UNIT_LEN as u64,
|
||||
"the concealed unit is still counted as loss"
|
||||
);
|
||||
|
||||
// Unit 0 is now valid NULL TS packets — sync 0x47 at every 192-byte
|
||||
// stride (offset 4), PID 0x1FFF — and carries no ciphertext.
|
||||
let unit0 = &buf[..crate::aacs::ALIGNED_UNIT_LEN];
|
||||
let unit0 = &buf[..crate::aacs::content::ALIGNED_UNIT_LEN];
|
||||
let mut off = 0;
|
||||
while off + 192 <= unit0.len() {
|
||||
assert_eq!(unit0[off + 4], 0x47, "null packet sync at {off}");
|
||||
@@ -1456,12 +1459,13 @@ mod tests {
|
||||
off += 192;
|
||||
}
|
||||
assert!(
|
||||
!crate::aacs::ts_sync_destroyed(unit0),
|
||||
!crate::aacs::content::ts_sync_destroyed(unit0),
|
||||
"concealed unit reads as well-formed TS, not scrambled"
|
||||
);
|
||||
|
||||
// Unit 1 (clear) passed through untouched.
|
||||
let unit1 = &buf[crate::aacs::ALIGNED_UNIT_LEN..2 * crate::aacs::ALIGNED_UNIT_LEN];
|
||||
let unit1 = &buf
|
||||
[crate::aacs::content::ALIGNED_UNIT_LEN..2 * crate::aacs::content::ALIGNED_UNIT_LEN];
|
||||
assert_eq!(unit1, &clear[..], "the clear unit is left exactly as read");
|
||||
}
|
||||
|
||||
@@ -1496,7 +1500,7 @@ mod tests {
|
||||
// The byte-exact expected post-decrypt form of unit B (independent decrypt).
|
||||
let mut expected_tail = good_tail.clone();
|
||||
assert!(
|
||||
crate::aacs::decrypt_unit(&mut expected_tail, &good_key),
|
||||
crate::aacs::content::decrypt_unit(&mut expected_tail, &good_key),
|
||||
"padding-tail must decrypt under good_key"
|
||||
);
|
||||
|
||||
@@ -1542,12 +1546,12 @@ mod tests {
|
||||
// ONLY the genuinely-undecryptable unit A is tallied / concealed.
|
||||
assert_eq!(
|
||||
loss.load(Ordering::Relaxed),
|
||||
crate::aacs::ALIGNED_UNIT_LEN as u64,
|
||||
crate::aacs::content::ALIGNED_UNIT_LEN as u64,
|
||||
"exactly one unit (the undecryptable one) is counted as loss"
|
||||
);
|
||||
|
||||
// Unit A → NULL TS (concealed).
|
||||
let unit0 = &buf[..crate::aacs::ALIGNED_UNIT_LEN];
|
||||
let unit0 = &buf[..crate::aacs::content::ALIGNED_UNIT_LEN];
|
||||
let mut off = 0;
|
||||
while off + 192 <= unit0.len() {
|
||||
assert_eq!(unit0[off + 4], 0x47, "unit A null packet sync at {off}");
|
||||
@@ -1562,7 +1566,8 @@ mod tests {
|
||||
// Unit B → the GOOD decrypted padding tail, byte-for-byte intact (NOT
|
||||
// overwritten with NULL TS). This is the silent-data-loss the old
|
||||
// majority-vote predicate caused.
|
||||
let unit1 = &buf[crate::aacs::ALIGNED_UNIT_LEN..2 * crate::aacs::ALIGNED_UNIT_LEN];
|
||||
let unit1 = &buf
|
||||
[crate::aacs::content::ALIGNED_UNIT_LEN..2 * crate::aacs::content::ALIGNED_UNIT_LEN];
|
||||
assert_eq!(
|
||||
unit1,
|
||||
&expected_tail[..],
|
||||
@@ -1587,8 +1592,8 @@ mod tests {
|
||||
/// `mux::ts` reads as a concealed gap.
|
||||
#[test]
|
||||
fn null_ts_fill_is_well_formed_and_invisible_to_real_pids() {
|
||||
let mut unit = vec![0xAAu8; crate::aacs::ALIGNED_UNIT_LEN];
|
||||
crate::aacs::fill_null_ts_unit(&mut unit);
|
||||
let mut unit = vec![0xAAu8; crate::aacs::content::ALIGNED_UNIT_LEN];
|
||||
crate::aacs::content::fill_null_ts_unit(&mut unit);
|
||||
// 32 packets, each: sync 0x47, PID 0x1FFF, adaptation-only (0b10) with a
|
||||
// discontinuity_indicator in the adaptation field.
|
||||
let mut off = 0;
|
||||
@@ -1679,7 +1684,7 @@ mod tests {
|
||||
"callback must be invoked once with the failing unit"
|
||||
);
|
||||
assert!(
|
||||
crate::aacs::ts_sync_destroyed(&got[0]),
|
||||
crate::aacs::content::ts_sync_destroyed(&got[0]),
|
||||
"the sample handed to the callback is the still-scrambled ciphertext"
|
||||
);
|
||||
assert_eq!(
|
||||
@@ -1703,7 +1708,7 @@ mod tests {
|
||||
);
|
||||
assert_eq!(
|
||||
nocb_loss.load(Ordering::Relaxed),
|
||||
crate::aacs::ALIGNED_UNIT_LEN as u64,
|
||||
crate::aacs::content::ALIGNED_UNIT_LEN as u64,
|
||||
"without a fetch callback the undecryptable unit is loss"
|
||||
);
|
||||
}
|
||||
@@ -1784,7 +1789,7 @@ mod tests {
|
||||
"fetch fired for BOTH units — the dry result for A did not latch off B"
|
||||
);
|
||||
assert!(
|
||||
!crate::aacs::ts_sync_destroyed(&buf2),
|
||||
!crate::aacs::content::ts_sync_destroyed(&buf2),
|
||||
"unit B is decrypted after its on-demand fetch"
|
||||
);
|
||||
}
|
||||
@@ -1899,7 +1904,7 @@ mod tests {
|
||||
let mut u = 0;
|
||||
while u < bytes {
|
||||
buf[u] |= 0xC0;
|
||||
u += crate::aacs::ALIGNED_UNIT_LEN;
|
||||
u += crate::aacs::content::ALIGNED_UNIT_LEN;
|
||||
}
|
||||
Ok(bytes)
|
||||
}
|
||||
@@ -1998,7 +2003,7 @@ mod tests {
|
||||
let mut u = 0;
|
||||
while u < b {
|
||||
buf[u] |= 0xC0; // CPI bits → reads as encrypted
|
||||
u += crate::aacs::ALIGNED_UNIT_LEN;
|
||||
u += crate::aacs::content::ALIGNED_UNIT_LEN;
|
||||
}
|
||||
Ok(b)
|
||||
}
|
||||
|
||||
@@ -89,7 +89,7 @@ impl fu::scsi::ScsiTransport for ScsiAdapter<'_> {
|
||||
}
|
||||
|
||||
/// Map libfreemkv's host certs (keysource-collected) to the unlock contract's.
|
||||
pub(crate) fn map_host_certs(certs: &[crate::aacs::HostCert]) -> Vec<fu::HostCert> {
|
||||
pub(crate) fn map_host_certs(certs: &[crate::aacs::types::HostCert]) -> Vec<fu::HostCert> {
|
||||
certs
|
||||
.iter()
|
||||
.map(|c| fu::HostCert {
|
||||
|
||||
Reference in New Issue
Block a user