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::{BlockDecrypt, BlockEncrypt, KeyInit};
13use aes::Aes128;
14
15/// The public AES-128 transport key.
16pub const KEY: [u8; 16] = [
17    0x5e, 0x9e, 0x4f, 0x00, 0x94, 0xef, 0x20, 0xab, 0x52, 0xe3, 0x5e, 0x73, 0x6a, 0xcb, 0x23, 0x24,
18];
19
20/// AES block size; the envelope covers whole blocks only.
21pub const BLOCK: usize = 16;
22
23/// `data` is not a whole number of AES blocks.
24#[derive(Clone, Copy, Debug, PartialEq, Eq)]
25pub struct LengthError(pub usize);
26
27impl core::fmt::Display for LengthError {
28    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
29        write!(
30            f,
31            "enc: length {} is not a multiple of the {BLOCK}-byte AES block",
32            self.0
33        )
34    }
35}
36
37#[cfg(feature = "std")]
38impl std::error::Error for LengthError {}
39
40fn check(data: &[u8]) -> Result<(), LengthError> {
41    if data.len() % BLOCK != 0 {
42        return Err(LengthError(data.len()));
43    }
44    Ok(())
45}
46
47/// Encrypt `data` in place.
48pub fn encrypt(data: &mut [u8]) -> Result<(), LengthError> {
49    check(data)?;
50    let cipher = Aes128::new(GenericArray::from_slice(&KEY));
51    for block in data.chunks_mut(BLOCK) {
52        cipher.encrypt_block(GenericArray::from_mut_slice(block));
53    }
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    for block in data.chunks_mut(BLOCK) {
62        cipher.decrypt_block(GenericArray::from_mut_slice(block));
63    }
64    Ok(())
65}
66
67#[cfg(test)]
68mod tests {
69    use super::*;
70
71    #[test]
72    fn round_trips_and_rejects_partial_blocks() {
73        let mut data = [0u8; 32];
74        data[0] = 1;
75        let original = data;
76        encrypt(&mut data).unwrap();
77        assert_ne!(data, original);
78        // ECB: identical plaintext blocks give identical ciphertext blocks.
79        let mut zeros = [0u8; 32];
80        encrypt(&mut zeros).unwrap();
81        assert_eq!(zeros[..16], zeros[16..]);
82        decrypt(&mut data).unwrap();
83        assert_eq!(data, original);
84        assert_eq!(encrypt(&mut [0u8; 15]), Err(LengthError(15)));
85    }
86}