High Assurance Boot (HAB) - i.MX8M edition

Published on May 22, 2020

This post intends to provide all the information you need to understand and use the HAB (High Assurance Boot) on your Boundary Devices Nitrogen8 platform. The goal is also to provide an update to our HAB for Dummies blog post so that new platforms are covered. Indeed, the i.MX 8M family (including i.MX 8MQ, i.MX 8M Mini, i.MX 8M Nano and i.MX 8M Plus) have a different boot process than older i.MX6/7 platforms. So although some of the details from the previous blog post still apply, a new version is required to explain/show the differences. For simplicity and clarity, this guide and examples have been made on i.MX8M Mini based Nitrogen8M_Mini but the procedure applies to the entire Nitrogen8 family. Please contact us if you need more details for other platforms.


HAB architecture

Before getting started, let's explain a few acronyms related to this subject
  • CSF: Command Sequence File
  • CST: Code-Signing Tool
  • DCD: Device Configuration Data
  • DEK: Data Encryption Key
  • HAB: High Assurance Boot
  • IVT: Image Vector Table
  • SRK: Super Root Key
The HAB library is a sub-component of the boot ROM on i.MX processors. It is responsible for verifying the digital signatures included as part of the product software and ensures that, when the processor is configured as a secure device, no unauthenticated code is allowed to run. In short, enabling HAB/secure boot on your platform prevents hackers to alter the boot process, making sure only the software you have previously signed/approved can be ran. The ROM and HAB cannot be changed so they can be considered as trusted software components. First, i.MX Boot ROM reads the eFuses to determine the security configuration of the SoC and the type of the boot device. The ROM then loads the SPL image into the OCRAM memory along with the DDR training binaries. Next it verifies the authenticity of the SPL code both in flash and memory against the signature embedded in the binary (CSF). That is the first stage verification. If it fails, execution is not allowed to leave the ROM for securely configured SoCs, also called "closed" devices. However, with an "open" device, you will be able to see HAB events which will tell you if the image would pass the authentication process. If it succeeds (or if the device is open), the Boot ROM executes the HDMI/DP Firmware first which in turn jumps to SPL. For other i.MX8M processors, there's no such firmware so it goes straight to SPL. SPL initializes the clocks, PMIC, (LP)DDR4 and UART. Then it is able to load and verify the rest of the components using the HAB APIs to extend the root of trust A FIT image (see here for details) is used to package all those components which are:
  • U-Boot binary + device tree blob (dtb)
  • ARM Trusted Firmware (ATF)
  • OP-TEE binary (TrustZone, optional, not covered in this blog post)
Finally, if that second stage verification succeeds, ATF is executed which will in turn execute U-Boot. The root of trust can be extended again at U-Boot level to authenticate Kernel and M4 images. In order to understand the signed image generation, it is best to have a look at the final flash.bin image layout. You can see there are 2 CSF binaries required, one per verification stage:
  1. CSF SPL that is used by Boot ROM to authenticate SPL + DDR FW
  2. CSF FIT that is used by SPL (through HAB APIs) to authenticate FIT components

How to enable/use it?

The below procedure will describe an example on how it has been done on one Nitrogen8M_Mini, make sure to modify the serial/password/keys values by your own!

1- Creation of the keys

First you need to unpack the Code Siging Tools package from NXP: ~$ tar xzf cst-3.3.0.tar.gz ~$ cd release/keys Then a couple of files need to be created, the first one being name 'serial' with an 8-digit content. OpenSSL uses the contents of this file for the certificate serial numbers. ~/release/keys$ vi serial 42424242 Create a file called 'key_pass.txt' that contains your pass phrase that will protect the HAB code signing private keys. The format of this file is the pass phrase repeated on the first and second lines of the file. ~/release/keys$ vi key_pass.txt Boundary123! Boundary123! You can now create the signature keys. ~/release/keys$ ./hab4_pki_tree.sh ... Do you want to use an existing CA key (y/n)?: n Do you want to use Elliptic Curve Cryptography (y/n)?: n Enter key length in bits for PKI tree: 4096 Enter PKI tree duration (years): 20 How many Super Root Keys should be generated? 4 Do you want the SRK certificates to have the CA flag set? (y/n)?: y ... Create the fuse table and binary to be flashed later. ~/release/keys$ cd ../crts/ ~/release/crts$ ../linux64/bin/srktool -h 4 -t SRK_1_2_3_4_table.bin -e SRK_1_2_3_4_fuse.bin -d sha256 -c ./SRK1_sha256_4096_65537_v3_ca_crt.pem,./SRK2_sha256_4096_65537_v3_ca_crt.pem,./SRK3_sha256_4096_65537_v3_ca_crt.pem,./SRK4_sha256_4096_65537_v3_ca_crt.pem -f 1

2- Flashing the keys

The fuse table generated in the previous section is what needs to be flashed to the device. ~/release/crts$ hexdump -e '/4 "0x"' -e '/4 "%X""\n"' < SRK_1_2_3_4_fuse.bin 0x3402271 0x67166C19 0x64679AE8 0x25056CEE 0x676D664 0xE46DD5CA 0x3A561C27 0x9E6742BA The above command gives you what needs to be flashed in the proper order. The commands below are made for i.MX 8MQ, i.MX 8M Mini, i.MX 8M Nano and i.MX 8M Plus only. Make sure to use your own values here, otherwise your board will never be able to authenticate the boot image. => fuse prog -y 6 0 0x3402271 => fuse prog -y 6 1 0x67166C19 => fuse prog -y 6 2 0x64679AE8 => fuse prog -y 6 3 0x25056CEE => fuse prog -y 7 0 0x676D664 => fuse prog -y 7 1 0xE46DD5CA => fuse prog -y 7 2 0x3A561C27 => fuse prog -y 7 3 0x9E6742BA If you flashed the above by mistake, without reading the note above, know that we share the set of keys used for this blog post:

3- Build U-Boot/SPL with security features

Build U-Boot with the HAB configuration enabled:
  • CONFIG_SECURE_BOOT for U-Boot <= 2018.07
  • CONFIG_IMX_HAB for U-Boot >= 2020.10
Here is an example for Nitrogen8M_Mini: ~$ git clone https://github.com/boundarydevices/u-boot-imx6 -b boundary-v2018.07 ~$ wget https://www.nxp.com/lgfiles/NMG/MAD/YOCTO/firmware-imx-8.5.bin ~$ chmod +x firmware-imx-8.5.bin ~$ ./firmware-imx-8.5.bin ~$ cp firmware-imx-8.5/firmware/hdmi/cadence/signed_*.bin u-boot-imx6/ ~$ cp firmware-imx-8.5/firmware/ddr/synopsys/lpddr4*.bin u-boot-imx6/ ~$ cd u-boot-imx6 ~/u-boot-imx6$ export ARCH=arm64 ~/u-boot-imx6$ export CROSS_COMPILE=aarch64-linux-gnu- ~/u-boot-imx6$ make nitrogen8mm_2g_defconfig ~/u-boot-imx6$ make menuconfig (enable the HAB configuration here) ~/u-boot-imx6$ make flash.bin V=1 ... ========= OFFSET dump ========= Loader IMAGE: header_image_off 0x0 image_off 0x40 csf_off 0x2ae00 spl hab block: 0x7e0fc0 0x0 0x2ae00 Second Loader IMAGE: sld_header_off 0x57c00 sld_csf_off 0x58c20 sld hab block: 0x401fcdc0 0x57c00 0x1020 Note that the "hab block" lines are very important when creating the .csf files:
  • spl hab block: includes IVT (SPL) + u-boot-spl-ddr.bin
  • sld hab block: includes FDT (FIT header) + IVT (FIT)
Also the "csf_off" are useful to know where to copy the signatures. ~/u-boot-imx6$ ./print_fit_hab.sh 0x40200000 0x5AC00 0xA0AF0 0x402A0AF0 0xFB6F0 0x72E0 0x920000 0x1029D0 0x9170 The above output gives you the addresses/offsets of (in that order):
  • u-boot-nodtb.bin
  • u-boot.dtb
  • bl31.bin (ARM Trusted Firmware)

4- Sign your flash.bin bootloader

Go to the CST tools again: ~$ cd ~/release/linux64/bin/ ~/release/linux64/bin$ cp ~/u-boot-imx6/flash.bin . At this point you need to create two .csf file using the information from previous section:
  1. csf_spl.txt: definition of the SPL signature which includes:
    • IVT (SPL) + u-boot-spl-ddr.bin (see spl hab block)
  2. csf_fit.txt: definition of the FIT signature that includes:
    • FDT (FIT) + IVT (FIT) (see sld hab block)
    • u-boot-nodtb.bin (see print_fit_hab.sh output)
    • u-boot.dtb (see print_fit_hab.sh output)
    • bl31.bin (see print_fit_hab.sh output)
You can download the files we used for this blog post: ~/release/linux64/bin$ wget https://storage.googleapis.com/boundarydevices.com/csf_spl.txt ~/release/linux64/bin$ wget https://storage.googleapis.com/boundarydevices.com/csf_fit.txt ... edit the size in the "Blocks = " lines in both files... ~/release/linux64/bin$ ./cst -i csf_spl.txt -o csf_spl.bin CSF Processed successfully and signed data available in csf_spl.bin ~/release/linux64/bin$ ./cst -i csf_fit.txt -o csf_fit.bin CSF Processed successfully and signed data available in csf_fit.bin At this point, all the signatures are generated, you can now insert them into the (unsigned) flash.bin: ~/release/linux64/bin$ cp flash.bin signed_flash.bin ~/release/linux64/bin$ dd if=csf_spl.bin of=signed_flash.bin seek=$((0x2ae00)) bs=1 conv=notrunc ~/release/linux64/bin$ dd if=csf_fit.bin of=signed_flash.bin seek=$((0x58c20)) bs=1 conv=notrunc You can see that the seek addresses match the 'csf_off' info provided by U-Boot.

That's a lot of offsets/addresses to remember!

You might wonder if there isn't a way to make the whole process above simpler? Writing this blog post required a lot of concentration not to get any address wrong, so we created scripts that can do everything for you. This script requires you to specify the path of the CST tool as well as the keys necessary for signing, that's it. Here is the simplified version to build and sign U-Boot: ~/u-boot-imx6$ export CST_BIN=~/release/linux64/bin/cst ~/u-boot-imx6$ export SIGN_KEY=~/release/crts/CSF1_1_sha256_4096_65537_v3_usr_crt.pem ~/u-boot-imx6$ export IMG_KEY=~/release/crts/IMG1_1_sha256_4096_65537_v3_usr_crt.pem ~/u-boot-imx6$ export SRK_TABLE=~/release/crts/SRK_1_2_3_4_table.bin ~/u-boot-imx6$ make flash.bin V=1 ~/u-boot-imx6$ ./sign_hab_imx8m.sh CSF Processed successfully and signed data available in csf_spl.bin CSF Processed successfully and signed data available in csf_fit.bin 6472+0 records in 6472+0 records out 6472 bytes (6.5 kB, 6.3 KiB) copied, 0.00416391 s, 1.6 MB/s 6488+0 records in 6488+0 records out 6488 bytes (6.5 kB, 6.3 KiB) copied, 0.00414051 s, 1.6 MB/s signed_flash.bin is ready! You can copy this binary to the root of an SD card along with upgrade.scr. Don't forget that the name of the binary must match the platform name (see U-Boot blog post for the explanation). ~/release/linux64/bin$ cp signed_flash.bin /u-boot.nitrogen8mm_2g

5- Flash and test the device

Use our standard procedure to update U-Boot: => run upgradeu The device should reboot, then check the HAB status: => hab_status Secure boot disabled HAB Configuration: 0xf0, HAB State: 0x66 No HAB Events Found! The Secure boot disabled means that the device is open. But still the HAB engine will check the image and report errors (Events) if the signature isn't right.

6- Closing the device?

Once you are *absolutely* sure you've understood what has been done so far and that you are sure it works, you can "close" the device. Once again, this step is IRREVERSIBLE, better make sure there is no HAB Events in open configuration. Below is the procedure to "close" the device on i.MX 8MQ, i.MX 8M Mini, i.MX 8M Nano and i.MX 8M Plus. => fuse prog 1 3 0x02000000 => reset ... => hab_status Secure boot enabled HAB Configuration: 0xcc, HAB State: 0x99 No HAB Events Found!

Going further

What about USB recovery?

Although this step was pretty complicated on i.MX6/7 due to the DCD reset, it is much easier now. As the DCD pointer is always NULL now, you can simply turn on the USB recovery switch on your platform for it to appear as a NXP device: ~/u-boot-imx6$ lsusb | grep NXP Bus 001 Device 064: ID 1fc9:0134 NXP Semiconductors SP Blank M845S At this point you just need to run uuu tool and it will load your signed binary: ~/u-boot-imx6$ uuu -d signed_flash.bin If you're not familiar with uuu, we highly recommend reading our blog post on the topic:

What about authenticating the kernel?

This part hasn't changed since out previous blog post so you can have a look at it: Note that the tool paths changed a bit so you might need to adapt the procedure. Once again, to ease the process, we created a script that simply takes an Image file and signs it for you: ~/u-boot-imx6$ ./sign_hab_imx8m-Image.sh /path/to/Image Extracting size from Image header... Padding Image file... Generating IVT header... Generating CSF binary... CSF Processed successfully and signed data available in csf_image.bin Image-signed.bin is ready! Then you can use the U-Boot hab_auth_img function to check your kernel signature. Here is an example for our kernel (your offsets might differ): => dhcp ${loadaddr} ${tftpserverip}:Image-signed.bin => hab_auth_img ${loadaddr} ${filesize} 0x1d22000 Authenticate image from DDR location 0x40480000... Secure boot enabled HAB Configuration: 0xcc, HAB State: 0x99 No HAB Events Found!

Boot time impact

We have not run any test to know how much impact HAB has on boot time yet. Enabling the signature definitely increases the time it takes before U-Boot is loaded. Feel free to let us know if you have made some comparison.