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 let table_end = TABLE_OFFSET + ENTRY_COUNT * ENTRY_SIZE;
72 (self.start as usize) < table_end && (self.end as usize) >= TABLE_OFFSET
73 }
74}
75
76fn read_u32_le(buf: &[u8], off: usize) -> u32 {
77 u32::from_le_bytes([buf[off], buf[off + 1], buf[off + 2], buf[off + 3]])
78}
79
80pub fn parse_table(image: &[u8]) -> Result<Vec<CmacEntry>, CmacError> {
82 let table_end = TABLE_OFFSET + ENTRY_COUNT * ENTRY_SIZE;
83 if image.len() < table_end {
84 return Err(CmacError::ImageTooSmall {
85 need: table_end,
86 got: image.len(),
87 });
88 }
89 let mut out = Vec::with_capacity(ENTRY_COUNT);
90 for index in 0..ENTRY_COUNT {
91 let entry_off = TABLE_OFFSET + index * ENTRY_SIZE;
92 let enabled = read_u32_le(image, entry_off);
93 let start = read_u32_le(image, entry_off + 4);
94 let end = read_u32_le(image, entry_off + 8);
95 let mut stored = [0u8; 16];
96 stored.copy_from_slice(&image[entry_off + 12..entry_off + 28]);
97 out.push(CmacEntry {
98 index,
99 entry_off,
100 enabled,
101 start,
102 end,
103 stored,
104 });
105 }
106 Ok(out)
107}
108
109fn reverse_blocks(data: &mut [u8]) {
111 for chunk in data.chunks_mut(16) {
112 chunk.reverse();
113 }
114}
115
116pub fn compute_stored_digest(image: &[u8], start: u32, end: u32) -> Result<[u8; 16], CmacError> {
120 let start = start as usize;
121 let end = end as usize;
122 if end < start || end >= image.len() {
123 return Err(CmacError::BadRange { start, end });
124 }
125 let mut mac = <Cmac<Aes128> as Mac>::new_from_slice(&CMAC_KEY)
126 .expect("AES-128 key length is always valid for CMAC");
127 let mut buf = [0u8; 1024];
130 for piece in image[start..=end].chunks(buf.len()) {
131 let part = &mut buf[..piece.len()];
132 part.copy_from_slice(piece);
133 reverse_blocks(part);
134 mac.update(part);
135 }
136 let tag = mac.finalize().into_bytes();
137
138 let mut digest = [0u8; 16];
139 digest.copy_from_slice(&tag);
140 digest.reverse(); Ok(digest)
142}
143
144#[derive(Debug, Clone, Copy)]
146pub struct EntryVerdict {
147 pub entry: CmacEntry,
149 pub computed: [u8; 16],
151 pub matches: bool,
153}
154
155pub fn verify_detailed(image: &[u8]) -> Result<Vec<EntryVerdict>, CmacError> {
157 let table = parse_table(image)?;
158 let mut verdicts = Vec::new();
159 for entry in table {
160 if !entry.is_active() {
161 continue;
162 }
163 let computed = compute_stored_digest(image, entry.start, entry.end)?;
164 verdicts.push(EntryVerdict {
165 matches: computed == entry.stored,
166 entry,
167 computed,
168 });
169 }
170 Ok(verdicts)
171}
172
173pub fn verify(image: &[u8]) -> bool {
175 match verify_detailed(image) {
176 Ok(v) => !v.is_empty() && v.iter().all(|e| e.matches),
177 Err(_) => false,
178 }
179}
180
181pub fn resign(image: &[u8]) -> Result<Vec<u8>, CmacError> {
186 let mut img = image.to_vec();
187 let table = parse_table(&img)?;
188
189 for entry in table.iter().filter(|e| e.is_active() && !e.covers_table()) {
191 let digest = compute_stored_digest(&img, entry.start, entry.end)?;
192 img[entry.entry_off + 12..entry.entry_off + 28].copy_from_slice(&digest);
193 }
194 for entry in table.iter().filter(|e| e.is_active() && e.covers_table()) {
196 let digest = compute_stored_digest(&img, entry.start, entry.end)?;
197 img[entry.entry_off + 12..entry.entry_off + 28].copy_from_slice(&digest);
198 }
199 Ok(img)
200}
201
202#[derive(Debug, Clone, Copy, PartialEq, Eq)]
204#[non_exhaustive]
205pub enum CmacError {
206 ImageTooSmall {
208 need: usize,
210 got: usize,
212 },
213 BadRange {
215 start: usize,
217 end: usize,
219 },
220}
221
222impl std::fmt::Display for CmacError {
223 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
224 match self {
225 CmacError::ImageTooSmall { need, got } => {
226 write!(f, "image too small: need {need} bytes, got {got}")
227 }
228 CmacError::BadRange { start, end } => {
229 write!(f, "bad CMAC range: start=0x{start:x} end=0x{end:x}")
230 }
231 }
232 }
233}
234
235impl std::error::Error for CmacError {}