mediatek_optical/image/
cmac.rs1use aes::Aes128;
30use cmac::{Cmac, Mac};
31
32pub const CMAC_KEY: [u8; 16] = [
34 0xBD, 0x20, 0x94, 0x08, 0xE3, 0x5E, 0x52, 0x6A, 0x36, 0x23, 0x52, 0x34, 0x43, 0x4F, 0xE8, 0xAB,
35];
36
37pub const TABLE_OFFSET: usize = crate::layout::CMAC_TABLE.start;
39pub const ENTRY_COUNT: usize = 16;
41pub const ENTRY_SIZE: usize = 28;
43pub const ENABLED: u32 = 0x0000_0001;
45
46#[derive(Debug, Clone, Copy, PartialEq, Eq)]
48pub struct CmacEntry {
49 pub index: usize,
51 pub entry_off: usize,
53 pub enabled: u32,
55 pub start: u32,
57 pub end: u32,
59 pub stored: [u8; 16],
61}
62
63impl CmacEntry {
64 pub fn is_active(&self) -> bool {
66 self.enabled == ENABLED
67 }
68
69 pub fn covers_table(&self) -> bool {
71 (self.start as usize) <= TABLE_OFFSET && (self.end as usize) >= TABLE_OFFSET
72 }
73}
74
75fn read_u32_le(buf: &[u8], off: usize) -> u32 {
76 u32::from_le_bytes([buf[off], buf[off + 1], buf[off + 2], buf[off + 3]])
77}
78
79pub fn parse_table(image: &[u8]) -> Result<Vec<CmacEntry>, CmacError> {
81 let table_end = TABLE_OFFSET + ENTRY_COUNT * ENTRY_SIZE;
82 if image.len() < table_end {
83 return Err(CmacError::ImageTooSmall {
84 need: table_end,
85 got: image.len(),
86 });
87 }
88 let mut out = Vec::with_capacity(ENTRY_COUNT);
89 for index in 0..ENTRY_COUNT {
90 let entry_off = TABLE_OFFSET + index * ENTRY_SIZE;
91 let enabled = read_u32_le(image, entry_off);
92 let start = read_u32_le(image, entry_off + 4);
93 let end = read_u32_le(image, entry_off + 8);
94 let mut stored = [0u8; 16];
95 stored.copy_from_slice(&image[entry_off + 12..entry_off + 28]);
96 out.push(CmacEntry {
97 index,
98 entry_off,
99 enabled,
100 start,
101 end,
102 stored,
103 });
104 }
105 Ok(out)
106}
107
108fn reverse_blocks(data: &mut [u8]) {
110 for chunk in data.chunks_mut(16) {
111 chunk.reverse();
112 }
113}
114
115pub fn compute_stored_digest(image: &[u8], start: u32, end: u32) -> Result<[u8; 16], CmacError> {
119 let start = start as usize;
120 let end = end as usize;
121 if end < start || end >= image.len() {
122 return Err(CmacError::BadRange { start, end });
123 }
124 let mut data = image[start..=end].to_vec();
125 reverse_blocks(&mut data);
126
127 let mut mac = <Cmac<Aes128> as Mac>::new_from_slice(&CMAC_KEY)
128 .expect("AES-128 key length is always valid for CMAC");
129 mac.update(&data);
130 let tag = mac.finalize().into_bytes();
131
132 let mut digest = [0u8; 16];
133 digest.copy_from_slice(&tag);
134 digest.reverse(); Ok(digest)
136}
137
138#[derive(Debug, Clone, Copy)]
140pub struct EntryVerdict {
141 pub entry: CmacEntry,
143 pub computed: [u8; 16],
145 pub matches: bool,
147}
148
149pub fn verify_detailed(image: &[u8]) -> Result<Vec<EntryVerdict>, CmacError> {
151 let table = parse_table(image)?;
152 let mut verdicts = Vec::new();
153 for entry in table {
154 if !entry.is_active() {
155 continue;
156 }
157 let computed = compute_stored_digest(image, entry.start, entry.end)?;
158 verdicts.push(EntryVerdict {
159 matches: computed == entry.stored,
160 entry,
161 computed,
162 });
163 }
164 Ok(verdicts)
165}
166
167pub fn verify(image: &[u8]) -> bool {
169 match verify_detailed(image) {
170 Ok(v) => !v.is_empty() && v.iter().all(|e| e.matches),
171 Err(_) => false,
172 }
173}
174
175pub fn resign(image: &[u8]) -> Result<Vec<u8>, CmacError> {
180 let mut img = image.to_vec();
181 let table = parse_table(&img)?;
182
183 for entry in table.iter().filter(|e| e.is_active() && !e.covers_table()) {
185 let digest = compute_stored_digest(&img, entry.start, entry.end)?;
186 img[entry.entry_off + 12..entry.entry_off + 28].copy_from_slice(&digest);
187 }
188 for entry in table.iter().filter(|e| e.is_active() && e.covers_table()) {
190 let digest = compute_stored_digest(&img, entry.start, entry.end)?;
191 img[entry.entry_off + 12..entry.entry_off + 28].copy_from_slice(&digest);
192 }
193 Ok(img)
194}
195
196#[derive(Debug, Clone, Copy, PartialEq, Eq)]
198#[non_exhaustive]
199pub enum CmacError {
200 ImageTooSmall {
202 need: usize,
204 got: usize,
206 },
207 BadRange {
209 start: usize,
211 end: usize,
213 },
214}
215
216impl std::fmt::Display for CmacError {
217 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
218 match self {
219 CmacError::ImageTooSmall { need, got } => {
220 write!(f, "image too small: need {need} bytes, got {got}")
221 }
222 CmacError::BadRange { start, end } => {
223 write!(f, "bad CMAC range: start=0x{start:x} end=0x{end:x}")
224 }
225 }
226 }
227}
228
229impl std::error::Error for CmacError {}