Previously used all-zeros AES key when unit_key_idx was out of range, producing silently corrupted output. Now returns DecryptFailed error. Also wired --raw flag through InputOptions → set_raw() on streams.
83 lines
2.5 KiB
Rust
83 lines
2.5 KiB
Rust
//! Decrypt-on-read layer.
|
|
//!
|
|
//! Decrypts sectors in-place using resolved keys from disc scanning.
|
|
//! Handles AACS 1.0, AACS 2.0, and CSS transparently.
|
|
//! The caller never sees encrypted data unless explicitly bypassed.
|
|
|
|
use crate::aacs;
|
|
use crate::css;
|
|
|
|
/// Resolved decryption state from disc scanning.
|
|
/// Passed to `decrypt_sectors()` — the caller doesn't need to know
|
|
/// which encryption scheme is in use.
|
|
#[derive(Clone)]
|
|
pub enum DecryptKeys {
|
|
/// No encryption on this disc.
|
|
None,
|
|
/// AACS (Blu-ray / UHD). Unit keys + optional read data key.
|
|
Aacs {
|
|
unit_keys: Vec<(u32, [u8; 16])>,
|
|
read_data_key: Option<[u8; 16]>,
|
|
},
|
|
/// CSS (DVD). Title key for sector descrambling.
|
|
Css { title_key: [u8; 5] },
|
|
}
|
|
|
|
impl DecryptKeys {
|
|
/// True if there are keys to decrypt with.
|
|
pub fn is_encrypted(&self) -> bool {
|
|
!matches!(self, DecryptKeys::None)
|
|
}
|
|
}
|
|
|
|
/// Decrypt a buffer of sectors in-place.
|
|
///
|
|
/// For AACS: processes in 6144-byte aligned units (3 sectors).
|
|
/// For CSS: processes per 2048-byte sector.
|
|
/// For None: no-op.
|
|
///
|
|
/// `unit_key_idx` selects which AACS unit key to use (0 for most discs).
|
|
///
|
|
/// Returns `Err` if decryption was expected but keys are missing or invalid.
|
|
/// Never produces silently corrupted output.
|
|
pub fn decrypt_sectors(
|
|
buf: &mut [u8],
|
|
keys: &DecryptKeys,
|
|
unit_key_idx: usize,
|
|
) -> Result<(), crate::error::Error> {
|
|
match keys {
|
|
DecryptKeys::None => {}
|
|
DecryptKeys::Aacs {
|
|
unit_keys,
|
|
read_data_key,
|
|
} => {
|
|
let uk = match unit_keys.get(unit_key_idx) {
|
|
Some((_, k)) => *k,
|
|
None => {
|
|
return Err(crate::error::Error::DecryptFailed {
|
|
reason: format!(
|
|
"unit_key_idx {} out of range (have {} keys)",
|
|
unit_key_idx,
|
|
unit_keys.len()
|
|
),
|
|
});
|
|
}
|
|
};
|
|
let rdk = read_data_key.as_ref();
|
|
let unit_len = aacs::ALIGNED_UNIT_LEN;
|
|
|
|
for chunk in buf.chunks_mut(unit_len) {
|
|
if chunk.len() == unit_len && aacs::is_unit_encrypted(chunk) {
|
|
aacs::decrypt_unit_full(chunk, &uk, rdk);
|
|
}
|
|
}
|
|
}
|
|
DecryptKeys::Css { title_key } => {
|
|
for chunk in buf.chunks_mut(2048) {
|
|
css::lfsr::descramble_sector(title_key, chunk);
|
|
}
|
|
}
|
|
}
|
|
Ok(())
|
|
}
|