1use std::borrow::Cow;
6use std::collections::HashSet;
7use std::convert::TryInto;
8use std::fs::File;
9use std::io::{Read, Write};
10use std::mem::{align_of, offset_of, size_of};
11use std::path::{Path, PathBuf};
12
13use anyhow::{Result, bail, ensure};
14use sphincsplus::{SphincsPlus, SpxDomain, SpxPublicKey};
15use thiserror::Error;
16use zerocopy::FromBytes;
17
18use crate::crypto::ecdsa::{EcdsaPublicKey, EcdsaRawPublicKey, EcdsaRawSignature};
19use crate::crypto::sha256::Sha256Digest;
20use crate::image::manifest::{
21 CHIP_MANIFEST_VERSION_MAJOR2, CHIP_ROM_EXT_IDENTIFIER, CHIP_ROM_EXT_SIZE_MAX,
22 MANIFEST_EXT_ID_SPX_KEY, MANIFEST_EXT_ID_SPX_SIGNATURE, Manifest, ManifestKind,
23 SigverifySpxSignature,
24};
25use crate::image::manifest_def::{ManifestSigverifyBuffer, ManifestSpec};
26use crate::image::manifest_ext::{ManifestExtEntry, ManifestExtEntrySpec};
27use crate::util::file::{FromReader, ToWriter};
28use crate::util::parse_int::ParseInt;
29
30#[derive(Debug, Error)]
31pub enum ImageError {
32 #[error("Incomplete read: expected to read {0} bytes but read {1} bytes")]
33 IncompleteRead(usize, usize),
34 #[error("Failed to parse image manifest.")]
35 Parse,
36 #[error("Extension data overflows flash image.")]
37 ExtensionOverflow,
38 #[error("Extension table index is out of bounds.")]
39 BadExtensionTableIndex,
40 #[error("Extension ID 0x{0:x} not in manifest table.")]
41 NoExtensionTableEntry(u32),
42 #[error("Extension 0x{0:x} is not aligned to word boundary.")]
43 BadExtensionAlignment(u32),
44 #[error("Invalid placement of signed extension 0x{0:x}.")]
45 MisplacedSignedExtension(u32),
46 #[error("Invalid manifest major version: {0}. ECDSA support requires major version {1}.")]
47 InvalidManifestVersionforEcdsa(u16, u16),
48}
49
50pub enum MainSignatureParams {
51 Ecdsa(EcdsaRawPublicKey, EcdsaRawSignature),
52}
53
54pub struct SpxSignatureParams {
55 key: SpxPublicKey,
56 signature: [u8; 7856],
57}
58
59pub struct SigverifyParams {
62 pub main_sig_params: MainSignatureParams,
63 pub spx_sig_params: Option<SpxSignatureParams>,
64 pub spx_hash_reversal_bug: bool,
65}
66
67impl SigverifyParams {
68 pub fn new(
69 main_sig_params: MainSignatureParams,
70 spx_sig_params: Option<SpxSignatureParams>,
71 ) -> Self {
72 SigverifyParams {
73 main_sig_params,
74 spx_sig_params,
75 spx_hash_reversal_bug: false,
76 }
77 }
78 pub fn with_hash_reversal_bug(mut self, bug: bool) -> Self {
79 self.spx_hash_reversal_bug = bug;
80 self
81 }
82
83 pub fn verify(&self, digest: &Sha256Digest) -> Result<()> {
85 match &self.main_sig_params {
86 MainSignatureParams::Ecdsa(key, sig) => {
87 let ecdsa_key: EcdsaPublicKey = key.try_into()?;
88 ecdsa_key.verify(digest, sig)?;
89 }
90 }
91 Ok(())
92 }
93
94 pub fn spx_verify(&self, b: &[u8], domain: SpxDomain) -> Result<()> {
96 if let Some(spx) = &self.spx_sig_params {
97 let msg = match domain {
98 SpxDomain::PreHashedSha256 => Cow::from(if self.spx_hash_reversal_bug {
99 Sha256Digest::hash(b).to_vec_rev()
100 } else {
101 Sha256Digest::hash(b).to_vec()
102 }),
103 _ => Cow::from(b),
104 };
105 spx.key.verify(domain, &spx.signature, &msg)?;
106 } else {
107 bail!("No SPX signature found");
108 }
109
110 Ok(())
111 }
112}
113
114#[repr(C)]
116#[derive(Debug)]
117pub struct ImageData {
118 pub bytes: [u8; Image::MAX_SIZE],
119 _align: [Manifest; 0],
120}
121
122impl Default for ImageData {
123 fn default() -> Self {
124 ImageData {
125 bytes: [0xFF; Image::MAX_SIZE],
126 _align: [],
127 }
128 }
129}
130
131#[derive(Debug, Default)]
132pub struct Image {
133 data: Box<ImageData>,
141 pub size: usize,
142}
143
144#[derive(Debug)]
145pub struct SubImage<'a> {
146 pub kind: ManifestKind,
147 pub offset: usize,
148 pub manifest: &'a Manifest,
149 pub data: &'a [u8],
150}
151
152#[derive(Debug)]
153pub enum ImageChunk {
154 Concat(PathBuf),
155 Offset(PathBuf, usize),
156}
157
158#[derive(Debug, Default)]
159pub struct ImageAssembler {
160 pub size: usize,
161 pub mirrored: bool,
162 pub chunks: Vec<ImageChunk>,
163}
164
165impl FromReader for Image {
166 fn from_reader(mut r: impl Read) -> Result<Self> {
168 let mut image = Image::default();
169 image.size = r.read(&mut image.data.bytes)?;
170 Ok(image)
171 }
172}
173
174impl ToWriter for Image {
175 fn to_writer(&self, w: &mut impl Write) -> Result<()> {
177 w.write_all(&self.data.bytes[..self.size])?;
178 Ok(())
179 }
180}
181
182impl Image {
183 pub const MAX_SIZE: usize = 1024 * 1024;
184
185 pub fn manifest_sanity_check(&self) -> Result<()> {
189 let manifest = self.borrow_manifest()?;
190 let len = self.data.bytes.len() as u32;
191
192 ensure!(manifest.signed_region_end <= len);
193 ensure!(manifest.length <= len);
194 ensure!(manifest.code_start < len);
195 ensure!(manifest.code_end < len);
196 ensure!(manifest.entry_point < len);
197 ensure!(manifest.extensions.entries.iter().all(|x| x.offset < len));
198
199 if (manifest.identifier == CHIP_ROM_EXT_IDENTIFIER)
202 && (manifest.length > CHIP_ROM_EXT_SIZE_MAX)
203 {
204 log::warn!("ROM_EXT is larger than 64k. Link offsets may need recalculating.");
205 }
206
207 Ok(())
208 }
209
210 fn get_spx_signature(&self) -> Result<Option<SpxSignatureParams>> {
212 let ext_tab = self.borrow_manifest()?.extensions.entries;
213 let key_o = ext_tab
214 .iter()
215 .find(|e| e.identifier == MANIFEST_EXT_ID_SPX_KEY);
216 let sig_o = ext_tab
217 .iter()
218 .find(|e| e.identifier == MANIFEST_EXT_ID_SPX_SIGNATURE);
219
220 match (key_o, sig_o) {
221 (Some(key_e), Some(sig_e)) => {
222 const KEY_SIZE: usize = 32; const SIG_SIZE: usize = std::mem::size_of::<SigverifySpxSignature>();
224
225 let mut key_bytes = [0u8; KEY_SIZE];
226 let mut signature = [0u8; SIG_SIZE];
227 let k_ofs = (key_e.offset + 8) as usize;
228 let s_ofs = (sig_e.offset + 8) as usize;
229
230 key_bytes.copy_from_slice(&self.data.bytes[k_ofs..k_ofs + KEY_SIZE]);
231 signature.copy_from_slice(&self.data.bytes[s_ofs..s_ofs + SIG_SIZE]);
232
233 let key = SpxPublicKey::from_bytes(SphincsPlus::Sha2128sSimple, &key_bytes)?;
234
235 Ok(Some(SpxSignatureParams { key, signature }))
236 }
237 (_, _) => Ok(None),
238 }
239 }
240
241 pub fn get_sigverify_params_from_manifest(&self) -> Result<SigverifyParams> {
242 let manifest = self.borrow_manifest()?;
243 let manifest_def: ManifestSpec = manifest.try_into()?;
244
245 let spx_sig_params = self.get_spx_signature()?;
246
247 let pub_key = manifest_def
248 .pub_key()
249 .ok_or(ImageError::Parse)?
250 .to_le_bytes();
251
252 let signature = manifest_def
253 .signature()
254 .ok_or(ImageError::Parse)?
255 .to_le_bytes();
256
257 let ecdsa_pub_key = EcdsaRawPublicKey::read(&mut std::io::Cursor::new(pub_key))?;
258 let ecdsa_sig = EcdsaRawSignature::read(&mut std::io::Cursor::new(signature))?;
259
260 Ok(SigverifyParams::new(
261 MainSignatureParams::Ecdsa(ecdsa_pub_key, ecdsa_sig),
262 spx_sig_params,
263 ))
264 }
265
266 pub fn overwrite_manifest(&mut self, other: ManifestSpec) -> Result<()> {
268 let manifest = self.borrow_manifest_mut()?;
269 let mut manifest_def: ManifestSpec = (&*manifest).try_into()?;
270 manifest_def.overwrite_fields(other);
271 *manifest = manifest_def.try_into()?;
272 Ok(())
273 }
274
275 pub fn add_signed_manifest_extensions(&mut self, spec: &[ManifestExtEntrySpec]) -> Result<()> {
281 spec.iter()
282 .filter(|entry_spec| entry_spec.is_signed())
283 .try_for_each(|entry_spec| {
284 self.add_manifest_extension(ManifestExtEntry::from_spec(entry_spec)?)
285 })
286 }
287
288 pub fn add_unsigned_manifest_extensions(
293 &mut self,
294 spec: &[ManifestExtEntrySpec],
295 ) -> Result<()> {
296 spec.iter()
297 .filter(|entry_spec| !entry_spec.is_signed())
298 .try_for_each(|entry_spec| {
299 self.add_manifest_extension(ManifestExtEntry::from_spec(entry_spec)?)
300 })
301 }
302
303 pub fn add_manifest_extension(&mut self, entry: ManifestExtEntry) -> Result<()> {
305 let manifest = self.borrow_manifest()?;
306
307 let mut ext_table = manifest.extensions.entries;
309
310 let entry_id = entry.header().identifier;
311
312 let ext_table_entry = ext_table
314 .iter_mut()
315 .find(|e| e.identifier == entry_id)
316 .ok_or(ImageError::NoExtensionTableEntry(entry_id))?;
317
318 let offset = if ext_table_entry.offset != 0 {
321 ext_table_entry.offset
322 } else {
323 ensure!(
324 self.size.is_multiple_of(align_of::<u32>()),
325 ImageError::BadExtensionAlignment(entry_id)
326 );
327 self.size.try_into()?
328 };
329 ext_table_entry.offset = offset;
330
331 let ext_bytes = entry.to_vec();
333 let end_index = offset
334 .checked_add(ext_bytes.len().try_into()?)
335 .ok_or(ImageError::ExtensionOverflow)?;
336 let extension_slice = self
337 .data
338 .bytes
339 .get_mut(offset as usize..end_index as usize)
340 .ok_or(ImageError::ExtensionOverflow)?;
341 extension_slice.copy_from_slice(ext_bytes.as_slice());
342 self.size = std::cmp::max(end_index as usize, self.size);
343
344 let manifest = self.borrow_manifest_mut()?;
345 manifest.extensions.entries = ext_table;
346
347 Ok(())
348 }
349
350 pub fn allocate_manifest_extension(&mut self, id: u32, len: usize) -> Result<()> {
356 let offset = self.size as u32;
357 self.borrow_manifest_mut()?
358 .extensions
359 .entries
360 .iter_mut()
361 .find(|e| e.identifier == id)
362 .ok_or(ImageError::NoExtensionTableEntry(id))?
363 .offset = offset;
364
365 self.size = self
366 .size
367 .checked_add(len)
368 .ok_or(ImageError::ExtensionOverflow)?;
369
370 Ok(())
371 }
372
373 pub fn drop_null_extensions(&mut self) -> Result<()> {
378 let manifest = self.borrow_manifest_mut()?;
379
380 manifest.extensions.entries.iter_mut().for_each(|e| {
381 if e.offset == 0 {
382 e.identifier = 0;
383 }
384 });
385
386 Ok(())
387 }
388
389 pub fn update_ecdsa_signature(&mut self, signature: EcdsaRawSignature) -> Result<()> {
391 let manifest = self.borrow_manifest_mut()?;
392
393 if manifest.manifest_version.major != CHIP_MANIFEST_VERSION_MAJOR2 {
408 log::error!(
409 "Invalid manifest version for ECDSA: {:?}",
410 manifest.manifest_version
411 );
412 }
413
414 let mut manifest_def: ManifestSpec = (&*manifest).try_into()?;
415
416 let signature_bytes = signature
417 .r
418 .iter()
419 .chain(signature.s.iter())
420 .copied()
421 .collect::<Vec<u8>>();
422
423 manifest_def.update_signature(ManifestSigverifyBuffer::from_le_bytes(signature_bytes)?);
424 *manifest = manifest_def.try_into()?;
425 Ok(())
426 }
427
428 pub fn update_ecdsa_public_key(&mut self, ecdsa_public_key: EcdsaRawPublicKey) -> Result<()> {
430 let manifest = self.borrow_manifest_mut()?;
431 let mut manifest_def: ManifestSpec = (&*manifest).try_into()?;
432
433 let key_bytes = ecdsa_public_key
434 .x
435 .iter()
436 .chain(ecdsa_public_key.y.iter())
437 .copied()
438 .collect::<Vec<u8>>();
439
440 manifest_def.update_pub_key(ManifestSigverifyBuffer::from_le_bytes(key_bytes)?);
441 *manifest = manifest_def.try_into()?;
442 Ok(())
443 }
444
445 pub fn subimages(&self) -> Result<Vec<SubImage<'_>>> {
446 let mut result = Vec::new();
447 let mut offset = 0;
448 while offset < self.size {
449 let m = &self.data.bytes[offset..offset + size_of::<Manifest>()];
450 let manifest = Manifest::ref_from_bytes(m).map_err(|_| ImageError::Parse)?;
451 let kind = ManifestKind(manifest.identifier);
452 let mut size = 1;
453 if kind.is_known_value() {
454 size = manifest.length as usize;
455 result.push(SubImage {
456 kind,
457 offset,
458 manifest,
459 data: &self.data.bytes[offset..offset + size],
460 });
461 }
462 offset += (size + 65535) & !65535;
464 }
465 Ok(result)
466 }
467
468 pub fn bytes(&self) -> &[u8] {
469 &self.data.bytes[..self.size]
470 }
471
472 pub fn borrow_manifest(&self) -> Result<&Manifest> {
473 let manifest_slice = &self.data.bytes[0..size_of::<Manifest>()];
474 let manifest = Manifest::ref_from_bytes(manifest_slice).map_err(|_| ImageError::Parse)?;
475 Ok(manifest)
476 }
477
478 pub fn borrow_manifest_mut(&mut self) -> Result<&mut Manifest> {
479 let manifest_slice = &mut self.data.bytes[0..size_of::<Manifest>()];
480 let manifest = Manifest::mut_from_bytes(manifest_slice).map_err(|_| ImageError::Parse)?;
481 Ok(manifest)
482 }
483
484 pub fn update_length(&mut self) -> Result<usize> {
486 self.borrow_manifest_mut()?.length = self.size as u32;
487 Ok(self.size)
488 }
489
490 pub fn update_signed_region(&mut self, signed_ids: &HashSet<u32>) -> Result<()> {
495 let image_size = self.size as u32;
496 let mut first_unsigned_ext = 0u32;
497 let manifest = self.borrow_manifest_mut()?;
498
499 let mut ext_table = manifest.extensions.entries;
500 ext_table.sort_by(|a, b| a.offset.cmp(&b.offset));
501 for e in ext_table {
502 if e.offset == 0 {
504 continue;
505 }
506 if signed_ids.contains(&e.identifier) {
507 ensure!(
510 first_unsigned_ext == 0,
511 ImageError::MisplacedSignedExtension(e.identifier)
512 );
513 } else if first_unsigned_ext == 0 {
514 first_unsigned_ext = e.offset;
515 }
516 }
517
518 manifest.signed_region_end = if first_unsigned_ext == 0 {
519 image_size
520 } else {
521 first_unsigned_ext
522 };
523
524 Ok(())
525 }
526
527 pub fn map_signed_region<F, R>(&self, f: F) -> Result<R>
529 where
530 F: FnOnce(&[u8]) -> R,
531 {
532 Ok(f(&self.data.bytes[offset_of!(Manifest, usage_constraints)
533 ..self.borrow_manifest()?.signed_region_end as usize]))
534 }
535
536 pub fn compute_digest(&self) -> Result<Sha256Digest> {
538 self.map_signed_region(|v| Sha256Digest::hash(v))
539 }
540}
541
542impl SubImage<'_> {
543 pub fn map_signed_region<F, R>(&self, f: F) -> Result<R>
545 where
546 F: FnOnce(&[u8]) -> R,
547 {
548 Ok(f(&self.data[offset_of!(Manifest, usage_constraints)
549 ..self.manifest.signed_region_end as usize]))
550 }
551
552 pub fn compute_digest(&self) -> Result<Sha256Digest> {
554 self.map_signed_region(|v| Sha256Digest::hash(v))
555 }
556}
557
558impl ImageAssembler {
559 pub fn with_params(size: usize, mirrored: bool) -> Self {
561 ImageAssembler {
562 size,
563 mirrored,
564 ..Default::default()
565 }
566 }
567
568 pub fn new() -> Self {
570 Self::with_params(0x100000, true)
571 }
572
573 pub fn parse(&mut self, chunks: &[impl AsRef<str>]) -> Result<()> {
577 for chunk in chunks {
578 if let Some((file, offset)) = chunk.as_ref().split_once('@') {
579 self.chunks.push(ImageChunk::Offset(
580 PathBuf::from(file),
581 usize::from_str(offset)?,
582 ));
583 } else {
584 self.chunks
585 .push(ImageChunk::Concat(PathBuf::from(chunk.as_ref())));
586 }
587 }
588 Ok(())
589 }
590
591 fn read(path: &Path, buf: &mut [u8]) -> Result<usize> {
593 let mut file = File::open(path)?;
594 let len = file.metadata()?.len() as usize;
595 ensure!(len <= buf.len(), ImageError::IncompleteRead(len, buf.len()));
596 if buf[..len].iter().any(|&c| c != 0xff) {
597 bail!("{path:?} Overlapped during image assembly");
598 }
599 let n = file.read(buf)?;
600 ensure!(len == n, ImageError::IncompleteRead(len, n));
601 Ok(n)
602 }
603
604 pub fn assemble(&self) -> Result<Vec<u8>> {
606 let size = if self.mirrored {
607 self.size / 2
608 } else {
609 self.size
610 };
611 let mut image = vec![0xff; size];
612 let mut pos = 0;
613 for chunk in &self.chunks {
614 match chunk {
615 ImageChunk::Concat(path) => {
616 let n = Self::read(path, &mut image[pos..])?;
617 pos += n;
618 }
619 ImageChunk::Offset(path, offset) => {
620 let n = Self::read(path, &mut image[*offset..])?;
621 pos = offset + n;
622 }
623 }
624 }
625 if self.mirrored {
626 image.extend_from_within(..size);
627 }
628 Ok(image)
629 }
630}
631
632#[cfg(test)]
633mod tests {
634 use super::*;
635 use crate::util::testdata;
636
637 #[test]
638 fn test_assemble_concat() -> Result<()> {
639 let mut image = ImageAssembler::with_params(16, false);
641 image.parse(&[
642 testdata("image/hello.txt").to_str().unwrap(),
643 testdata("image/world.txt").to_str().unwrap(),
644 ])?;
645 let data = image.assemble()?;
646 assert_eq!(data, b"HelloWorld\xff\xff\xff\xff\xff\xff");
647 Ok(())
648 }
649
650 #[test]
651 fn test_assemble_offset() -> Result<()> {
652 let mut image = ImageAssembler::with_params(16, false);
654 image.parse(&[
655 testdata("image/hello.txt@0").to_str().unwrap(),
656 testdata("image/world.txt@0x8").to_str().unwrap(),
657 ])?;
658 let data = image.assemble()?;
659 assert_eq!(data, b"Hello\xff\xff\xffWorld\xff\xff\xff");
660 Ok(())
661 }
662
663 #[test]
664 fn test_assemble_mirrored() -> Result<()> {
665 let mut image = ImageAssembler::with_params(20, true);
667 image.parse(&[
668 testdata("image/hello.txt").to_str().unwrap(),
669 testdata("image/world.txt").to_str().unwrap(),
670 ])?;
671 let data = image.assemble()?;
672 assert_eq!(data, b"HelloWorldHelloWorld");
673 Ok(())
674 }
675
676 #[test]
677 fn test_assemble_mirrored_offset_error() -> Result<()> {
678 let mut image = ImageAssembler::with_params(16, true);
680 image.parse(&[
681 testdata("image/hello.txt@0").to_str().unwrap(),
682 testdata("image/world.txt@0x5").to_str().unwrap(),
683 ])?;
684 let err = image.assemble().unwrap_err();
685 assert_eq!(
686 err.to_string(),
687 "Incomplete read: expected to read 5 bytes but read 3 bytes"
688 );
689 Ok(())
690 }
691
692 #[test]
693 fn test_load_image() {
694 let image = Image::read_from_file(&testdata("image/test_image.bin")).unwrap();
696 image
697 .write_to_file(&testdata("image/test_image_out.bin"))
698 .unwrap();
699
700 let (mut orig_bytes, mut res_bytes) = (Vec::<u8>::new(), Vec::<u8>::new());
702 File::open(testdata("image/test_image.bin"))
703 .unwrap()
704 .read_to_end(&mut orig_bytes)
705 .unwrap();
706 File::open(testdata("image/test_image_out.bin"))
707 .unwrap()
708 .read_to_end(&mut res_bytes)
709 .unwrap();
710 assert_eq!(orig_bytes, res_bytes);
711 }
712}