mediatek_optical/
envelope.rs1use sha2::{Digest, Sha256};
7
8use crate::{image, layout, update::PrepareError, Chip};
9
10#[derive(Debug, Clone, Copy, PartialEq, Eq)]
12pub enum InputEncoding {
13 Plaintext,
15 Encrypted,
17}
18
19#[derive(Debug, Clone, PartialEq, Eq)]
27#[non_exhaustive]
28pub enum Error {
29 Image(PrepareError),
31 UnknownFraming,
33 Ambiguous,
35}
36impl core::fmt::Display for Error {
37 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
38 match self {
39 Self::Image(error) => write!(f, "{error}"),
40 Self::UnknownFraming => f.write_str("unrecognized MediaTek image framing"),
41 Self::Ambiguous => f.write_str("ambiguous MediaTek image framing"),
42 }
43 }
44}
45impl std::error::Error for Error {
46 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
47 match self {
48 Self::Image(source) => Some(source),
49 Self::UnknownFraming | Self::Ambiguous => None,
50 }
51 }
52}
53
54#[derive(Clone)]
56pub struct Envelope {
57 original: Vec<u8>,
58 canonical: Option<Vec<u8>>,
60 encoding: InputEncoding,
61 chip: Chip,
62 input_hash: [u8; 32],
63 image_hash: [u8; 32],
64}
65
66impl core::fmt::Debug for Envelope {
67 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
68 f.debug_struct("Envelope")
69 .field("length", &self.image().len())
70 .field("input_encoding", &self.encoding)
71 .field("chip", &self.chip)
72 .field("input_sha256", &self.input_hash)
73 .field("image_sha256", &self.image_hash)
74 .finish_non_exhaustive()
75 }
76}
77
78fn recognized(bytes: &[u8]) -> bool {
79 layout::BANNER
80 .of(bytes)
81 .and_then(Chip::from_banner)
82 .is_some()
83}
84
85fn validate(bytes: &[u8]) -> Result<Chip, PrepareError> {
86 if !image::cmac::verify(bytes) {
87 return Err(PrepareError::Integrity);
88 }
89 if image::is_drive_read(bytes) {
90 return Err(PrepareError::DriveRead);
91 }
92 image::detect_chip(bytes)
93 .map(|info| info.family)
94 .map_err(|_| PrepareError::UnknownChip)
95}
96
97impl Envelope {
98 pub fn load(input: &[u8]) -> Result<Self, Error> {
102 if input.len() != layout::IMAGE_SIZE {
103 return Err(Error::Image(PrepareError::Size(input.len())));
104 }
105 let plain = recognized(input).then(|| validate(input));
106 const _: () = assert!(layout::BANNER.start % crate::enc::BLOCK == 0);
109 const _: () = assert!(layout::BANNER.len % crate::enc::BLOCK == 0);
110 let mut banner = [0u8; layout::BANNER.len];
111 banner.copy_from_slice(
112 &input[layout::BANNER.start..layout::BANNER.start + layout::BANNER.len],
113 );
114 crate::enc::decrypt(&mut banner).expect("banner contains whole AES blocks");
115 let decoded = Chip::from_banner(&banner).is_some().then(|| {
116 let mut decoded = input.to_vec();
117 crate::enc::decrypt(&mut decoded).expect("complete image contains whole AES blocks");
118 decoded
119 });
120 let encrypted = decoded.as_deref().map(validate);
121 let (canonical, encoding, chip) = match (plain, encrypted) {
122 (Some(Ok(_)), Some(Ok(_))) => return Err(Error::Ambiguous),
123 (Some(Ok(chip)), _) => (None, InputEncoding::Plaintext, chip),
124 (_, Some(Ok(chip))) => (decoded, InputEncoding::Encrypted, chip),
125 (Some(Err(error)), _) | (_, Some(Err(error))) => return Err(Error::Image(error)),
126 (None, None) => return Err(Error::UnknownFraming),
127 };
128 let input_hash: [u8; 32] = Sha256::digest(input).into();
129 let image_hash = match &canonical {
130 None => input_hash,
131 Some(canonical) => Sha256::digest(canonical).into(),
132 };
133 Ok(Self {
134 original: input.to_vec(),
135 input_hash,
136 image_hash,
137 canonical,
138 encoding,
139 chip,
140 })
141 }
142
143 pub fn original(&self) -> &[u8] {
145 &self.original
146 }
147 pub fn image(&self) -> &[u8] {
149 self.canonical.as_deref().unwrap_or(&self.original)
150 }
151 pub fn input_encoding(&self) -> InputEncoding {
153 self.encoding
154 }
155 pub fn chip(&self) -> Chip {
157 self.chip
158 }
159 pub fn input_sha256(&self) -> &[u8; 32] {
161 &self.input_hash
162 }
163 pub fn image_sha256(&self) -> &[u8; 32] {
165 &self.image_hash
166 }
167}
168
169#[cfg(test)]
170mod tests {
171 use super::*;
172 use crate::fixtures::{self, BU3};
173
174 #[test]
175 fn both_input_formats_have_one_canonical_identity_and_preserve_provenance() {
176 let image = fixtures::image(&BU3, 7);
177 let plain = Envelope::load(&image).unwrap();
178 let mut wire = image.clone();
179 crate::enc::encrypt(&mut wire).unwrap();
180 let encrypted = Envelope::load(&wire).unwrap();
181 assert_eq!(plain.input_encoding(), InputEncoding::Plaintext);
182 assert_eq!(encrypted.input_encoding(), InputEncoding::Encrypted);
183 assert_eq!(plain.image(), encrypted.image());
184 assert_eq!(plain.image_sha256(), encrypted.image_sha256());
185 assert_ne!(plain.input_sha256(), encrypted.input_sha256());
186 assert_eq!(encrypted.original(), wire);
187 assert_eq!(plain.original(), image);
188 }
189
190 #[test]
191 fn rejects_capture_corruption_empty_integrity_and_unknown_framing() {
192 let image = fixtures::image(&BU3, 3);
193 assert_eq!(
194 Envelope::load(&fixtures::drive_read(&image)).unwrap_err(),
195 Error::Image(PrepareError::DriveRead)
196 );
197 let mut bad = image.clone();
198 bad[fixtures::SIGNED.0 as usize] ^= 1;
199 assert_eq!(
200 Envelope::load(&bad).unwrap_err(),
201 Error::Image(PrepareError::Integrity)
202 );
203 bad = image;
204 let table = image::cmac::TABLE_OFFSET;
205 bad[table..table + image::cmac::ENTRY_COUNT * image::cmac::ENTRY_SIZE].fill(0xff);
206 assert_eq!(
207 Envelope::load(&bad).unwrap_err(),
208 Error::Image(PrepareError::Integrity)
209 );
210 assert_eq!(
211 Envelope::load(&vec![0; layout::IMAGE_SIZE]).unwrap_err(),
212 Error::UnknownFraming
213 );
214 assert_eq!(
215 Envelope::load(&[0; 16]).unwrap_err(),
216 Error::Image(PrepareError::Size(16))
217 );
218 }
219}