Skip to main content

mediatek_optical/
enc.rs

1//! The AES-128-ECB `enc` transport envelope.
2//!
3//! Some update paths stream the image AES-128-ECB encrypted under a fixed,
4//! public transport key. This is a transport encoding, not a signature: the
5//! drive authenticates the decrypted image by its integrity table
6//! (`crate::image::cmac`). Receiver-bound preparation chooses the wire encoding
7//! from firmware evidence; [`encrypt`] is the low-level codec primitive.
8//!
9//! Requires the `enc` feature.
10
11use aes::cipher::generic_array::GenericArray;
12use aes::cipher::inout::InOutBuf;
13use aes::cipher::{BlockDecrypt, BlockEncrypt, KeyInit};
14use aes::Aes128;
15
16/// The public AES-128 transport key.
17pub const KEY: [u8; 16] = [
18    0x5e, 0x9e, 0x4f, 0x00, 0x94, 0xef, 0x20, 0xab, 0x52, 0xe3, 0x5e, 0x73, 0x6a, 0xcb, 0x23, 0x24,
19];
20
21/// AES block size; the envelope covers whole blocks only.
22pub const BLOCK: usize = 16;
23
24/// `data` is not a whole number of AES blocks.
25#[derive(Clone, Copy, Debug, PartialEq, Eq)]
26pub struct LengthError(pub usize);
27
28impl core::fmt::Display for LengthError {
29    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
30        write!(
31            f,
32            "enc: length {} is not a multiple of the {BLOCK}-byte AES block",
33            self.0
34        )
35    }
36}
37
38#[cfg(feature = "std")]
39impl std::error::Error for LengthError {}
40
41fn check(data: &[u8]) -> Result<(), LengthError> {
42    if data.len() % BLOCK != 0 {
43        return Err(LengthError(data.len()));
44    }
45    Ok(())
46}
47
48/// Encrypt `data` in place.
49pub fn encrypt(data: &mut [u8]) -> Result<(), LengthError> {
50    check(data)?;
51    let cipher = Aes128::new(GenericArray::from_slice(&KEY));
52    let (blocks, _) = InOutBuf::from(data).into_chunks();
53    cipher.encrypt_blocks_inout(blocks);
54    Ok(())
55}
56
57/// Decrypt `data` in place.
58pub fn decrypt(data: &mut [u8]) -> Result<(), LengthError> {
59    check(data)?;
60    let cipher = Aes128::new(GenericArray::from_slice(&KEY));
61    let (blocks, _) = InOutBuf::from(data).into_chunks();
62    cipher.decrypt_blocks_inout(blocks);
63    Ok(())
64}
65
66#[cfg(test)]
67mod tests {
68    use super::*;
69
70    #[test]
71    fn round_trips_and_rejects_partial_blocks() {
72        let mut data = [0u8; 32];
73        data[0] = 1;
74        let original = data;
75        encrypt(&mut data).unwrap();
76        assert_ne!(data, original);
77        // ECB: identical plaintext blocks give identical ciphertext blocks.
78        let mut zeros = [0u8; 32];
79        encrypt(&mut zeros).unwrap();
80        assert_eq!(zeros[..16], zeros[16..]);
81        decrypt(&mut data).unwrap();
82        assert_eq!(data, original);
83        assert_eq!(encrypt(&mut [0u8; 15]), Err(LengthError(15)));
84    }
85}