Skip to main content

mcuboot_meta/
lib.rs

1/// This is a library for parsing and verifying mcuboot images as per the documentation at <https://docs.mcuboot.com/design.html#image-format>
2///
3mod error;
4mod image_header;
5mod image_tlv;
6mod mcuboot_constants;
7
8use std::{fs::File, io::Cursor, path::Path};
9
10use log::trace;
11
12use crate::{
13    image_header::ImageHeader,
14    image_tlv::{
15        ImageTlvAreaHeader, ImageTlvAreaType, ImageTlvEntry, ImageTlvEntryType, TakeTlvEntry,
16    },
17};
18
19pub use crate::error::Error;
20
21#[derive(Debug)]
22/// Struct containing all the relevant metadata of a mcuboot image
23pub struct ImageMetadata {
24    pub header: ImageHeader,
25    // pub protected_tlvs: Option<Vec<ImageTlvEntry>>, currently not implemented
26    pub sha256_hash: Vec<u8>,
27    pub signature_key_hash: Vec<u8>,
28    pub signature: Vec<u8>,
29}
30
31/// Parses the provided mcuboot image and returns the corresponding `ImageMetadata`.
32/// Usage:
33///
34/// ```
35/// let path = std::path::Path::new("test/test_image.signed.bin");
36/// let image_metadata = mcuboot_meta::parse_image(&path).expect("Failed to parse image");
37/// println!("Image header:\n{:#?}", image_metadata.header);
38/// println!("Image sha256_hash: {:#?}", image_metadata.sha256_hash);
39/// println!("Image signature: {:#?}", image_metadata.signature);
40/// println!("Image signature_key_hash: {:#?}", image_metadata.signature_key_hash);
41/// ```
42///
43pub fn parse_image(path: impl AsRef<Path>) -> Result<ImageMetadata, Error> {
44    use std::io::Read;
45
46    let mut file = File::open(path)?;
47    let mut data = Vec::new();
48    file.read_to_end(&mut data)?;
49    let mut cursor = Cursor::new(data.as_slice());
50
51    let header: ImageHeader = (&mut cursor).try_into()?;
52    trace!("Image header: {:#?}", header);
53
54    // Move to the end of the image, pointing to the TLV area,
55    cursor.set_position(header.header_size as u64 + header.image_size as u64);
56
57    let tlv_header = ImageTlvAreaHeader::try_from(&mut cursor)?;
58
59    let tlv_header = match tlv_header.area_type() {
60        ImageTlvAreaType::Protected => {
61            trace!(
62                "Found TLV protected area of size: {}bytes",
63                tlv_header.area_size()
64            );
65
66            // For now we simply skip the TLV area, since we are not interested in its content.
67            cursor.set_position(cursor.position() + tlv_header.area_payload_size()? as u64);
68            ImageTlvAreaHeader::try_from(&mut cursor)?
69        }
70        ImageTlvAreaType::Unprotected => tlv_header,
71        ImageTlvAreaType::Invalid(magic) => return Err(Error::InvalidTlvMagic(magic)),
72    };
73
74    // Here we expect a non-protected TLV area
75    let area_type = tlv_header.area_type();
76    if let ImageTlvAreaType::Invalid(_) | ImageTlvAreaType::Protected = area_type {
77        return Err(Error::NonProtectedTlvAreaNotFound(area_type));
78    }
79
80    trace!(
81        "Reading TLV entries from TLV area ({}bytes)",
82        tlv_header.area_size()
83    );
84
85    let mut unprotected_tlvs = read_tlv_entries(&mut cursor, &tlv_header)?;
86
87    // In this area, the order of the TLV is guaranteed to be:
88    // 1) SHA256
89    // 2) KeyHash
90    // 3) Signature
91    // See https://docs.mcuboot.com/design.html#image-format
92    let signature = unprotected_tlvs.take(ImageTlvEntryType::EcdsaSig)?.value;
93    let signature_key_hash = unprotected_tlvs.take(ImageTlvEntryType::KeyHash)?.value;
94    let sha256_hash = unprotected_tlvs.take(ImageTlvEntryType::Sha256)?.value;
95
96    Ok(ImageMetadata {
97        header,
98        sha256_hash,
99        signature_key_hash,
100        signature,
101    })
102}
103
104fn read_tlv_entries(
105    cursor: &mut Cursor<&[u8]>,
106    tlv_header: &ImageTlvAreaHeader,
107) -> Result<Vec<ImageTlvEntry>, Error> {
108    let mut tlvs = Vec::new();
109
110    let end_of_area = cursor.position() + tlv_header.area_payload_size()? as u64;
111    while cursor.position() < end_of_area {
112        let tlv_entry = ImageTlvEntry::try_from(&mut *cursor)?;
113        trace!(
114            "Read TLV entry: {}, {:?}, value: {:x?}",
115            tlv_entry.len, tlv_entry.type_, tlv_entry.value
116        );
117        tlvs.push(tlv_entry);
118    }
119    Ok(tlvs)
120}
121
122fn read_u8(c: &mut Cursor<&[u8]>) -> Result<u8, Error> {
123    use std::io::Read;
124
125    let mut buf = [0u8; 1];
126    c.read_exact(&mut buf).map_err(Error::ImageParsing)?;
127    Ok(buf[0])
128}
129
130fn read_u16(c: &mut Cursor<&[u8]>) -> Result<u16, Error> {
131    use std::io::Read;
132
133    let mut buf = [0u8; 2];
134    c.read_exact(&mut buf).map_err(Error::ImageParsing)?;
135    Ok(u16::from_le_bytes(buf))
136}
137
138fn read_u32(c: &mut Cursor<&[u8]>) -> Result<u32, Error> {
139    use std::io::Read;
140
141    let mut buf = [0u8; 4];
142    c.read_exact(&mut buf).map_err(Error::ImageParsing)?;
143    Ok(u32::from_le_bytes(buf))
144}
145
146#[cfg(test)]
147mod tests {
148    use super::*;
149
150    #[test]
151    fn parsing_valid_image_works() {
152        let path = Path::new("test/test_image.signed.bin");
153        let image = parse_image(&path).expect("Failed to parse valid image");
154
155        println!("Image:\n{:#x?}", image);
156
157        assert_eq!(image.header.protect_tlv_size, 0);
158        assert_eq!(image.header.image_size, 852540);
159        assert_eq!(
160            image.header.flags,
161            mcuboot_constants::ImageHeaderFlags::empty()
162        );
163        assert_eq!(image.header.version.major, 1);
164        assert_eq!(image.header.version.minor, 4);
165        assert_eq!(image.header.version.revision, 2);
166        assert_eq!(image.header.version.build_num, 0);
167        assert_eq!(
168            image.sha256_hash,
169            vec![
170                0x80, 0xf3, 0xc5, 0xfb, 0x50, 0xa0, 0x16, 0xc1, 0xf6, 0xe4, 0x57, 0x49, 0x96, 0x47,
171                0x2e, 0xb3, 0xf7, 0xb6, 0x14, 0xee, 0xc2, 0xd6, 0xa5, 0xd0, 0x96, 0xbc, 0x7, 0xb6,
172                0x9a, 0x2d, 0x81, 0x21
173            ]
174        );
175        assert_eq!(
176            image.signature_key_hash,
177            vec![
178                0xe3, 0x4, 0x66, 0xf6, 0xb8, 0x47, 0xc, 0x1f, 0x29, 0x7, 0xb, 0x17, 0xf1, 0xe2,
179                0xd3, 0xe9, 0x4d, 0x44, 0x5e, 0x3f, 0x60, 0x80, 0x87, 0xfd, 0xc7, 0x11, 0xe4, 0x38,
180                0x2b, 0xb5, 0x38, 0xb6
181            ]
182        );
183        assert_eq!(
184            image.signature,
185            vec![
186                0x30, 0x44, 0x2, 0x20, 0x23, 0x14, 0xd5, 0xd3, 0x86, 0xeb, 0x61, 0x1d, 0xd6, 0xf5,
187                0xa9, 0xa8, 0x2, 0xcf, 0x7e, 0x26, 0xcc, 0x95, 0x57, 0x99, 0x43, 0xf5, 0xd6, 0xa5,
188                0xd0, 0x30, 0xe6, 0x22, 0x73, 0x26, 0x56, 0x92, 0x2, 0x20, 0xa, 0x30, 0xf7, 0x54,
189                0xb2, 0x1c, 0x22, 0x23, 0xe1, 0x75, 0xfa, 0x43, 0x49, 0x3b, 0xc1, 0x87, 0x41, 0x32,
190                0xab, 0xa4, 0xc3, 0xc4, 0xba, 0x75, 0xd, 0xc4, 0xa4, 0x18, 0xc4, 0x9e, 0xea, 0x83
191            ]
192        );
193    }
194
195    #[test]
196    fn parsing_valid_encrypted_image_works() {
197        let path = Path::new("test/test_image.signed.encrypted.ota.bin");
198        let image = parse_image(&path).expect("Failed to parse valid image");
199
200        println!("Image Header:\n{:#x?}", image);
201
202        assert_eq!(image.header.protect_tlv_size, 0);
203        assert_eq!(image.header.image_size, 852544);
204        assert_eq!(
205            image.header.flags,
206            mcuboot_constants::ImageHeaderFlags::IMAGE_F_ENCRYPTED_AES128
207        );
208        assert_eq!(image.header.version.major, 1);
209        assert_eq!(image.header.version.minor, 4);
210        assert_eq!(image.header.version.revision, 2);
211        assert_eq!(image.header.version.build_num, 0);
212        assert_eq!(
213            image.sha256_hash,
214            vec![
215                0x3, 0xb3, 0xbb, 0x6a, 0xfb, 0x5b, 0xce, 0xb2, 0xef, 0x45, 0x20, 0x69, 0x25, 0x38,
216                0xbe, 0xd7, 0xed, 0x1b, 0xb4, 0x9a, 0xfd, 0xd1, 0xab, 0x56, 0x91, 0x1d, 0x13, 0x5f,
217                0x4, 0xa3, 0x41, 0x7a
218            ]
219        );
220        assert_eq!(
221            image.signature_key_hash,
222            vec![
223                0xe3, 0x4, 0x66, 0xf6, 0xb8, 0x47, 0xc, 0x1f, 0x29, 0x7, 0xb, 0x17, 0xf1, 0xe2,
224                0xd3, 0xe9, 0x4d, 0x44, 0x5e, 0x3f, 0x60, 0x80, 0x87, 0xfd, 0xc7, 0x11, 0xe4, 0x38,
225                0x2b, 0xb5, 0x38, 0xb6
226            ]
227        );
228        assert_eq!(
229            image.signature,
230            vec![
231                0x30, 0x45, 0x2, 0x21, 0x0, 0xc2, 0x68, 0xa8, 0x4a, 0x21, 0x3b, 0xc1, 0x97, 0xc7,
232                0x5c, 0x4e, 0x3c, 0xf0, 0x8f, 0x58, 0xe2, 0x79, 0xf1, 0xb1, 0xb4, 0x94, 0x5a, 0x9e,
233                0x8f, 0xb0, 0x1b, 0x21, 0x58, 0x95, 0xaa, 0xe5, 0xdb, 0x2, 0x20, 0x17, 0x83, 0xb8,
234                0x10, 0x7c, 0xb4, 0x49, 0x25, 0xbb, 0x2f, 0x44, 0x2d, 0x6c, 0x7b, 0x29, 0x8f, 0x15,
235                0x3d, 0x6a, 0x7, 0x33, 0xfe, 0x9b, 0x3e, 0xb7, 0x91, 0xe8, 0xa0, 0x19, 0x98, 0x81,
236                0x16
237            ]
238        );
239    }
240}