This post intends to provide all the information you need to understand and use the HAB (High Assurance Boot) on your Ezurio (formerly 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.
Prerequisites
- Code Signing Tools (CST)
- Account required on NXP website
- https://www.nxp.com/webapp/Download?colCode=IMX_CST_TOOL_NEW
- Provides valuable documentation
- U-Boot source code
- See latest branch from our U-Boot repo
- https://github.com/boundarydevices/u-boot-imx6/tree/boundary-v2018.07
- Ezurio (formerly Boundary Devices) Nitrogen8 platform
- Any of the platforms from the Nitrogen8 family
- Patience & focus
- If you miss a step, this can brick your platform for good
- You've been WARNED!
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:
- CSF SPL that is used by Boot ROM to authenticate SPL + DDR FW
- 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: - csf_spl.txt: definition of the SPL signature which includes:
- IVT (SPL) +
u-boot-spl-ddr.bin
(see spl hab block)
- IVT (SPL) +
- 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.