2 minute read

I’ve been enjoying working with the MCUboot bootloader, which is natively supported on the Zephyr RTOS. For the maximum security, I like to used signed + encrypted firmware upgrade images. This ensures that:

  • Only images signed by the person or company owning the private signing key can be flashed your embedded target
  • Images can be publicly distributed, without exposing the plain firmware binary, as they are encrypted.

MCUboot supports different types of keys and ciphers: e.g. rsa-2048, rsa-3072, ecdsa-p256, ecdsa-p224 or ed25519. Read about signing images with imgtool here: https://docs.mcuboot.com/imgtool.html.
And see https://docs.mcuboot.com/encrypted_images.html for more information about encrypted images.

imgtool.py allows you to easily generate those keys, create C-headers from existing keys, etc… One thing I felt that was lacking, was an easy way to “look into” a binary MCUboot image, and see which type of signing and encryption it was using. This basically comes down to parsing the TLV structs embedded in the firmware images generated by Zephyr / MCUboot.

Because of this (and because I wanted to write some code in Rust again), I decided to create mcuboot-img-parser, which does exactly that. E.g.

$ ./target/release/mcuboot-img-parser build/zephyr/zephyr.signed.bin
mcuboot-tlv-parser v0.0.1
-------------------------
[*] Opening image file build/zephyr/zephyr.signed.bin, total file size:  17202
[*] Offset: 00000000, 0x00000000: ImageHeader { magic: 2532554813, load_addr: 0, hdr_size: 512, prot_tlv_size: 0, img_size: 16480, flags: 0, ver_major: 0, ver_minor: 0, ver_rev: 0, ver_build: 0, pad1: 0 }
[*] Offset: 00000512, 0x00000200: ImageHeader padding 480 bytes: [0, 0, 0, 0, 0, 0, 0, 0] ...
[*] Offset: 00000512, 0x00000200: BinaryImage 16480 bytes: [68, 7, 0, 20, 75, d7, 0, 0] ...
[*] Offset: 00016992, 0x00004260: ImageTLVInfo { magic: 26887, tlv_tot: 210 }
[*] Offset: 00016996, 0x00004264: TLV_SHA256, 32 bytes: [c, 4d, 34, 42, 55, 96, 5, 0] ...
[*] Offset: 00017032, 0x00004288: TLV_PUBKEY, 91 bytes: [30, 59, 30, 13, 6, 7, 2a, 86] ...
[*] Offset: 00017127, 0x000042e7: TLV_ECDSA256, 71 bytes: [30, 45, 2, 21, 0, 98, b6, 89] ...

You can see the TLV_ECDSA256 tag, which indicates that this image is using an ECDSA256 pubkey to verify the signature of the image. Additionally, you can see that the pubkey is embedded in this image (which is not the default), because of the presence of the TLV_PUBKEY tag. This can be achieved by using the --public-key-format full parameter when calling imgtool.

The mcuboot-img-parser output for an image using signing AND encrytion, would look like this:

$ ./target/release/mcuboot-img-parser build/zephyr/zephyr.signed.encrypted.bin
mcuboot-tlv-parser v0.0.1
-------------------------
[*] Opening image file build/zephyr/zephyr.signed.encrypted.bin, total file size:  150599
[*] Offset: 00000000, 0x00000000: ImageHeader { magic: 2532554813, load_addr: 0, hdr_size: 512, prot_tlv_size: 0, img_size: 149760, flags: 4, ver_major: 1, ver_minor: 2, ver_rev: 0, ver_build: 0, pad1: 0 }
                                : TLV Flag: ENCRYPTED_AES128
[*] Offset: 00000512, 0x00000200: ImageHeader padding 480 bytes: [0, 0, 0, 0, 0, 0, 0, 0] ...
[*] Offset: 00000512, 0x00000200: BinaryImage 149760 bytes: [94, 94, 84, cc, 3, a2, d7, 28] ...
[*] Offset: 00150272, 0x00024b00: ImageTLVInfo { magic: 26887, tlv_tot: 327 }
[*] Offset: 00150276, 0x00024b04: TLV_SHA256, 32 bytes: [3e, d2, 63, 38, 14, 58, 45, 27] ...
[*] Offset: 00150312, 0x00024b28: TLV_PUBKEY, 91 bytes: [30, 59, 30, 13, 6, 7, 2a, 86] ...
[*] Offset: 00150407, 0x00024b87: TLV_ECDSA256, 71 bytes: [30, 45, 2, 21, 0, 82, 75, b0] ...
[*] Offset: 00150482, 0x00024bd2: TLV_ENC_EC256, 113 bytes: [4, 97, f5, 14, 92, 8d, 43, 39] ...

You can see the TLV Flag ENCRYPTED_AES128 in the ImageHeader and the additional TLV_ENC_EC256 TLV in this this image.

You can find the code here: https://github.com/maximevince/mcuboot-img-parser