mediatek-optical 1.0.0

MediaTek MT1959/MT1939 optical drive (BD/DVD) protocol: command encoding, identity and flash sequences over any SCSI transport, chip detection, hardware families, AES-CMAC integrity, family-gated updates and OEM image reconstruction. no_std by default.
Documentation
//! The AES-128-ECB `enc` transport envelope.
//!
//! Some update paths stream the image AES-128-ECB encrypted under a fixed,
//! public transport key. This is a transport encoding, not a signature: the
//! drive authenticates the decrypted image by its integrity table
//! (`crate::image::cmac`). Receiver-bound preparation chooses the wire encoding
//! from firmware evidence; [`encrypt`] is the low-level codec primitive.
//!
//! Requires the `enc` feature.

use aes::cipher::generic_array::GenericArray;
use aes::cipher::{BlockDecrypt, BlockEncrypt, KeyInit};
use aes::Aes128;

/// The public AES-128 transport key.
pub const KEY: [u8; 16] = [
    0x5e, 0x9e, 0x4f, 0x00, 0x94, 0xef, 0x20, 0xab, 0x52, 0xe3, 0x5e, 0x73, 0x6a, 0xcb, 0x23, 0x24,
];

/// AES block size; the envelope covers whole blocks only.
pub const BLOCK: usize = 16;

/// `data` is not a whole number of AES blocks.
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct LengthError(pub usize);

impl core::fmt::Display for LengthError {
    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
        write!(
            f,
            "enc: length {} is not a multiple of the {BLOCK}-byte AES block",
            self.0
        )
    }
}

#[cfg(feature = "std")]
impl std::error::Error for LengthError {}

fn check(data: &[u8]) -> Result<(), LengthError> {
    if data.len() % BLOCK != 0 {
        return Err(LengthError(data.len()));
    }
    Ok(())
}

/// Encrypt `data` in place.
pub fn encrypt(data: &mut [u8]) -> Result<(), LengthError> {
    check(data)?;
    let cipher = Aes128::new(GenericArray::from_slice(&KEY));
    for block in data.chunks_mut(BLOCK) {
        cipher.encrypt_block(GenericArray::from_mut_slice(block));
    }
    Ok(())
}

/// Decrypt `data` in place.
pub fn decrypt(data: &mut [u8]) -> Result<(), LengthError> {
    check(data)?;
    let cipher = Aes128::new(GenericArray::from_slice(&KEY));
    for block in data.chunks_mut(BLOCK) {
        cipher.decrypt_block(GenericArray::from_mut_slice(block));
    }
    Ok(())
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn round_trips_and_rejects_partial_blocks() {
        let mut data = [0u8; 32];
        data[0] = 1;
        let original = data;
        encrypt(&mut data).unwrap();
        assert_ne!(data, original);
        // ECB: identical plaintext blocks give identical ciphertext blocks.
        let mut zeros = [0u8; 32];
        encrypt(&mut zeros).unwrap();
        assert_eq!(zeros[..16], zeros[16..]);
        decrypt(&mut data).unwrap();
        assert_eq!(data, original);
        assert_eq!(encrypt(&mut [0u8; 15]), Err(LengthError(15)));
    }
}