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)]
21pub enum Error {
22 Image(PrepareError),
24 UnknownFraming,
26 Ambiguous,
28}
29impl core::fmt::Display for Error {
30 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
31 match self {
32 Self::Image(error) => write!(f, "{error}"),
33 Self::UnknownFraming => f.write_str("unrecognized MediaTek image framing"),
34 Self::Ambiguous => f.write_str("ambiguous MediaTek image framing"),
35 }
36 }
37}
38impl std::error::Error for Error {
39 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
40 match self {
41 Self::Image(source) => Some(source),
42 _ => None,
43 }
44 }
45}
46
47#[derive(Clone)]
49pub struct Envelope {
50 original: Vec<u8>,
51 canonical: Vec<u8>,
52 encoding: InputEncoding,
53 chip: Chip,
54 input_hash: [u8; 32],
55 image_hash: [u8; 32],
56}
57
58impl core::fmt::Debug for Envelope {
59 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
60 f.debug_struct("Envelope")
61 .field("length", &self.canonical.len())
62 .field("input_encoding", &self.encoding)
63 .field("chip", &self.chip)
64 .field("input_sha256", &self.input_hash)
65 .field("image_sha256", &self.image_hash)
66 .finish_non_exhaustive()
67 }
68}
69
70fn recognized(bytes: &[u8]) -> bool {
71 layout::BANNER
72 .of(bytes)
73 .and_then(Chip::from_banner)
74 .is_some()
75}
76
77fn validate(bytes: &[u8]) -> Result<Chip, PrepareError> {
78 if !image::cmac::verify(bytes) {
79 return Err(PrepareError::Integrity);
80 }
81 if image::is_drive_read(bytes) {
82 return Err(PrepareError::DriveRead);
83 }
84 image::detect_chip(bytes)
85 .map(|info| info.family)
86 .map_err(|_| PrepareError::UnknownChip)
87}
88
89impl Envelope {
90 pub fn load(input: &[u8]) -> Result<Self, Error> {
94 if input.len() != layout::IMAGE_SIZE {
95 return Err(Error::Image(PrepareError::Size(input.len())));
96 }
97 let plain = recognized(input).then(|| validate(input));
98 let mut decoded = input.to_vec();
99 crate::enc::decrypt(&mut decoded).expect("complete image contains whole AES blocks");
100 let encrypted = recognized(&decoded).then(|| validate(&decoded));
101 let (canonical, encoding, chip) = match (plain, encrypted) {
102 (Some(Ok(_)), Some(Ok(_))) => return Err(Error::Ambiguous),
103 (Some(Ok(chip)), _) => (input.to_vec(), InputEncoding::Plaintext, chip),
104 (_, Some(Ok(chip))) => (decoded, InputEncoding::Encrypted, chip),
105 (Some(Err(error)), _) | (_, Some(Err(error))) => return Err(Error::Image(error)),
106 (None, None) => return Err(Error::UnknownFraming),
107 };
108 Ok(Self {
109 original: input.to_vec(),
110 input_hash: Sha256::digest(input).into(),
111 image_hash: Sha256::digest(&canonical).into(),
112 canonical,
113 encoding,
114 chip,
115 })
116 }
117
118 pub fn original(&self) -> &[u8] {
120 &self.original
121 }
122 pub fn image(&self) -> &[u8] {
124 &self.canonical
125 }
126 pub fn input_encoding(&self) -> InputEncoding {
128 self.encoding
129 }
130 pub fn chip(&self) -> Chip {
132 self.chip
133 }
134 pub fn input_sha256(&self) -> &[u8; 32] {
136 &self.input_hash
137 }
138 pub fn image_sha256(&self) -> &[u8; 32] {
140 &self.image_hash
141 }
142}
143
144#[cfg(test)]
145mod tests {
146 use super::*;
147 use crate::fixtures::{self, BU3};
148
149 #[test]
150 fn both_input_formats_have_one_canonical_identity_and_preserve_provenance() {
151 let image = fixtures::image(&BU3, 7);
152 let plain = Envelope::load(&image).unwrap();
153 let mut wire = image.clone();
154 crate::enc::encrypt(&mut wire).unwrap();
155 let encrypted = Envelope::load(&wire).unwrap();
156 assert_eq!(plain.input_encoding(), InputEncoding::Plaintext);
157 assert_eq!(encrypted.input_encoding(), InputEncoding::Encrypted);
158 assert_eq!(plain.image(), encrypted.image());
159 assert_eq!(plain.image_sha256(), encrypted.image_sha256());
160 assert_ne!(plain.input_sha256(), encrypted.input_sha256());
161 assert_eq!(encrypted.original(), wire);
162 assert_eq!(plain.original(), image);
163 }
164
165 #[test]
166 fn rejects_capture_corruption_empty_integrity_and_unknown_framing() {
167 let image = fixtures::image(&BU3, 3);
168 assert_eq!(
169 Envelope::load(&fixtures::drive_read(&image)).unwrap_err(),
170 Error::Image(PrepareError::DriveRead)
171 );
172 let mut bad = image.clone();
173 bad[fixtures::SIGNED.0 as usize] ^= 1;
174 assert_eq!(
175 Envelope::load(&bad).unwrap_err(),
176 Error::Image(PrepareError::Integrity)
177 );
178 bad = image;
179 let table = image::cmac::TABLE_OFFSET;
180 bad[table..table + image::cmac::ENTRY_COUNT * image::cmac::ENTRY_SIZE].fill(0xff);
181 assert_eq!(
182 Envelope::load(&bad).unwrap_err(),
183 Error::Image(PrepareError::Integrity)
184 );
185 assert_eq!(
186 Envelope::load(&vec![0; layout::IMAGE_SIZE]).unwrap_err(),
187 Error::UnknownFraming
188 );
189 assert_eq!(
190 Envelope::load(&[0; 16]).unwrap_err(),
191 Error::Image(PrepareError::Size(16))
192 );
193 }
194}