Hands-on iMX6 High Assurance Boot a.k.a. Secure boot
Introduction
These notes are based on Boundary Device’s blog on implementing HAB on an imx6 SoC.
The guide above is out of date, and missing information. I hope this helps to fill in some of those gaps.
I won’t cover the purpose of secure boot or the chain of trust. You can read up on more info here.
The steps described here alone will not prevent you system from running untrusted code. A number of other steps will need to be included, such as
- blowing fuses to prevent debug interfaces
- prevent the system from booting from arbitrary RAM location
- protecting OS filesystem
- Locked down uboot
- Disable any external scripts from loading. e.g. 6x_bootscript
- protect any dtbs
- Strip the uboot binary down to the basic feature required
- Basically protect any artifacts that are involved in the boot process and running of the OS
Disclaimer: These are just some of my notes and should not be used in any production systems without due caution and validation for accuracy.
This guide also doesn’t cover encrypting your firmware.
Board spec
Boundary devices Nitrogen6X dev board
https://boundarydevices.com/product/nitrogen6x/
SoC MCiMX6Q6AVT10AC Arm Cortex-A9 Quad core 1GB DDR3 Ram
Comes with Linux pre-installed on sdcard.
Terminal access
HDMI
Get a working terminal on boundary devices’ shipped sdcard with Linux.
We need to disable qt gui startup from systemd.
Mount sdcard from another system.
Delete /media/mkocbayi/c1a350a8-ddfb-4d54-8f9f-56b0cbfab55c/etc/systemd/system/multi-user.target.wants/qtlauncher.service
This will stop qt gui on startup.
Boot the board with the modified sdcard.
Might have to press ctl-alt-f5 to get login prompt
Login root, no password.
RS232
The board comes with an rs232 cable for tty access and is enabled in uboot and linux.
I used the Trendnet TU-S9 usb to rs232 cable to connect with no issues.
CPU serial number
Serial number can be read from imx6 On-Chip OTP Controller.
From imx6 console run
cat /sys/fsl_otp/HW_OCOTP_CFG0 0xee669fb8 cat /sys/fsl_otp/HW_OCOTP_CFG1 0x150e11d4 |
From build system, set certificate serial using cpu uid, set CA passphrase to protect code signing private key, and generate certs
$ cd keys $ echo “ee669fb8ee669fb8” > serial$ echo “SUPER_SECURE_SECRET” > key_pass.txt$ echo “SUPER_SECURE_SECRET” >> key_pass.txt$ ./hab4_pki_tree.sh |
Rest of steps can be followed using HAB tutorial.
Create fuse table and binary to be flashed to imx fuses.
~/projects/imx-sb/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 |
Display binary contents
$ hexdump -C SRK_1_2_3_4_fuse.bin 00000000 0f 4f 1f dd 9e 34 91 25 84 63 1d 3f 7d ea 65 eb |.O…4.%.c.?}.e.| 00000010 2e e4 f6 fc b9 7c 12 02 40 c5 ac 92 16 cf 42 ed |…..|..@…..B.| 00000020 |
Corrected endianness
$ hexdump -e ‘/4 “0x”‘ -e ‘/4 “%X””\n”‘ < SRK_1_2_3_4_fuse.bin 0xDD1F4F0F 0x2591349E 0x3F1D6384 0xEB65EA7D 0xFCF6E42E 0x2127CB9 0x92ACC540 0xED42CF16 |
Backup known working sdcard
partitions
$ sudo partclone.fat16 -c -d -s /dev/sdb1 -o sdb1.img … Partclone successfully cloned the device (/dev/sdb1) to the image (sdb1.img) Cloned successfully. |
and
$ sudo partclone.ext3 -c -d -s /dev/sdb2 -o sdb2.img … Syncing… OK! Partclone successfully cloned the device (/dev/sdb2) to the image (sdb2.img) Cloned successfully. |
partition table
$ sudo dd if=/dev/sdb of=backup-sdb.mbr count=1 bs=512 1+0 records in 1+0 records out 512 bytes copied, 0.00168312 s, 304 kB/s |
extended partition table
$ sudo sfdisk -d /dev/sdb > backup-sdb.sf |
Restore sdcard backup
First restore partition tables
$ sudo dd if=parti-backup/backup-sdb.mbr of=/dev/sdb 1+0 records in 1+0 records out 512 bytes copied, 0.00145495 s, 352 kB/s |
Now extended partitions
$ sudo sfdisk /dev/sdb < parti-backup/backup-sdb.sf Checking that no-one is using this disk right now … OK Disk /dev/sdb: 29.8 GiB, 32010928128 bytes, 62521344 sectors … Old situation: Device Boot Start End Sectors Size Id Type /dev/sdb1 * 8192 40959 32768 16M c W95 FAT32 (LBA) /dev/sdb2 40960 2711551 2670592 1.3G 83 Linux … |
Update OS with drive’s new partitions
$ sudo partprobe /dev/sdb |
Restore partitions
First partition
$ sudo partclone.fat16 -r -d -s parti-backup/sdb1.img -o /dev/sdb1 Partclone v0.3.11 http://partclone.org … Syncing… OK! Partclone successfully restored the image (parti-backup/sdb1.img) to the device (/dev/sdb1) Cloned successfully. |
second partition
$ sudo partclone.extfs -r -d -s parti-backup/sdb2.img -o /dev/sdb2 Partclone v0.3.11 http://partclone.org … Syncing… OK! Partclone successfully restored the image (parti-backup/sdb2.img) to the device (/dev/sdb2) Cloned successfully. |
Programming imx fuses
Sanity test
imx6 fuse prog command <bank> <word> <value>
As an example sanity check, we will read the mac address of our imx
imx6 eFuse base address = 0x400
On the i.MX6Q The MAC address is located at 0x620[31:0] (Lower MAC address) and 0x630[15:0] (Upper MAC address), the following example in U-Boot documentation can be used for calculating the bank and word for the Lower MAC address.
(0x620 – 0x400)/0x10 = 0x22 Hexadecimal = 34 Decimal
As the fuses are arranged in banks of 8 words:
34 / 8 = 4 and the remainder is 2, so in this case:
Bank = 4
Word = 2
Python code to calculate MAC_ADDR bank and word
def get_bank_word(fuse_address):
if fuse_address <= 0x0: # TODO:Change to actual lowest possible value
print('Must supply fuse address from imx reference manual')
return
fuse_base = 0x400
result = int((fuse_address - fuse_base)/0x10) # result should be in decimal
bank = int(result / 8)
word = int(result % 8)
print('Bank {} Word {}'.format(bank,word))
In [34]: get_bank_word(0x620)
Bank 4 Word 2
In [35]: get_bank_word(0x630)
Bank 4 Word 3
This fuse word is shadowed in the register OCOTP_MAC0
And reading the fuses from uboot we get
=> fuse read 4 2
Reading bank 4:
Word 0x00000002: b806928d
=> fuse read 4 3
Reading bank 4:
Word 0x00000003: 00000019
Booting into the Linux kernel confirms the ethernet mac address
root@b2qt-nitrogen6x:~# ifconfig eth0
eth0 Link encap:Ethernet HWaddr 00:19:B8:06:92:8D
BROADCAST MULTICAST MTU:1500 Metric:1
RX packets:0 errors:0 dropped:0 overruns:0 frame:0
TX packets:0 errors:0 dropped:0 overruns:0 carrier:0
collisions:0 txqueuelen:1000
RX bytes:0 (0.0 B) TX bytes:0 (0.0 B)
One Time Program of a SHA256 of Four RSA public keys
Using the knowledge from above, let’s plug-in the fuse address for burning the SRK (super root key) hash.
Page 351 of the imx6q reference manual says the fuse address starts at 0x580
In [35]: get_bank_word(0x580) Bank 3 Word 0 |
From board’s u-boot prompt
=> fuse prog -y 3 0 0xDD1F4F0F => fuse prog -y 3 1 0x2591349E => fuse prog -y 3 2 0x3F1D6384 => fuse prog -y 3 3 0xEB65EA7D => fuse prog -y 3 4 0xFCF6E42E => fuse prog -y 3 5 0x2127CB9 => fuse prog -y 3 6 0x92ACC540 => fuse prog -y 3 7 0xED42CF16 |
Each value is 32bits x 8 = 256bit hash
Build u-boot boundary-v2018.07
Note: I originally followed boundary device’s HAB blog which instructed to compile u-boot 2016, and then decided to compile a more recent version instead.
Download and compile
~$ git clone https://github.com/boundarydevices/u-boot-imx6 -b boundary-v2018.07 ~$ cd u-boot-imx6 ~/u-boot-imx6$ export ARCH=arm ~/u-boot-imx6$ export CROSS_COMPILE=arm-linux-gnueabihf- ~/u-boot-imx6$ make nitrogen6q_defconfig ~/u-boot-imx6$ make menuconfig |
Select ‘ARM architecture’ —> ‘Support i.MX HAB features’
And
Select ‘Device Drivers’ —> Hardware crypto devices —>
[*] Freescale Crypto Driver Support
[*] Big-endian access to Freescale Secure Boot
(4) Freescale Secure Boot compatibility
-*- Little-endian access to Freescale Secure Boot
And
Select ‘Library routines’ —> ‘Security support’ —>
[*] Support the AES algorithm
Compile
u-boot-imx6$ make -j4 V=1 u-boot.imx |
Should have a file called u-boot.imx
Now, if the output of your make command displayed your HAB blocks values then great. However, at the time of writing these notes, my version of mkimage did not display the hab values.
Luckily a kind author wrote a patch.
u-boot-imx6$ wget -O 940826.diff https://patchwork.ozlabs.org/patch/940826/raw/ u-boot-imx6$ git apply 940826.diff |
Compile again
u-boot-imx6$ make -j4 V=1 u-boot.imx u-boot-imx6$ ./tools/mkimage -l u-boot.imx Image Type: Freescale IMX Boot Image Image Ver: 2 (i.MX53/6/7 compatible) Mode: DCD Data Size: 585728 Bytes = 572.00 KiB = 0.56 MiB Load Address: 177ff420 Entry Point: 17800000 HAB Blocks: 0x177ff400 0x00000000 0x0008cc00 DCD Blocks: 0x00910000 0x0000002c 0x00000318 |
We will also need to create the uboot update script.
u-boot-imx6$ ./tools/mkimage -A arm -O linux -T script -C none -a 0 -e 0 -n “update script” -d board/boundary/bootscripts/upgrade.txt upgrade.scr Image Name: update script Created: Sun Aug 25 17:26:27 2019 Image Type: ARM Linux Script (uncompressed) Data Size: 5351 Bytes = 5.23 KiB = 0.01 MiB Load Address: 00000000 Entry Point: 00000000 Contents: Image 0: 5343 Bytes = 5.22 KiB = 0.01 MiB |
Copy your u-boot.imx file to where your code signing tools are.
u-boot-imx6$ cp u-boot.imx ../release/linux64/ |
Sign boot loader
Download and edit u-boot_sign.csf file.
Amended HAB block values to
Blocks = 0x177ff400 0x0 0x8cc00 “u-boot.imx”
Now create csf file
u-boot-imx6$ cd ../release/linux64/release/linux64$ ./bin/cst –o u-boot_csf.bin –i u-boot_sign.csf CSF Processed successfully and signed data available in u-boot_csf.bin |
concat csf binary with uboot
release/linux64$ cat u-boot.imx u-boot_csf.bin > u-boot_signed.imx |
Flash signed uboot to eeprom
Copy the signed boot loader binary to the root of an SD card along with the upgrade script.
u-boot-imx6$ cp upgrade.scr /media/user/Boot\ nitrog/release/linux64$ cp u-boot_signed.imx /media/user/Boot\ nitrog/u-boot.nitrogen6q |
Note: The board boots uboot from spi flash.
From the uboot prompt run
=> env default -a => savee => run upgradeu |
Reset the board
U-Boot 2018.07-35628-gc54f6b851b (Aug 25 2019 – 17:01:05 -0700) CPU: Freescale i.MX6Q rev1.2 at 792 MHz Reset cause: WDOG Board: nitrogen6x I2C: ready DRAM: 1 GiB MMC: FSL_SDHC: 0, FSL_SDHC: 1 Loading Environment from SPI Flash… SF: Detected sst25vf016b with page size 256 Bytes, erase size 4 KiB, total 2 MiB OK auto-detected panel 1280x720M@60 Display: hdmi:1280x720M@60 (1280×720) In: serial Out: serial Err: serial Net: Micrel ksz9021 at 6 FEC [PRIME], usb_ether Hit any key to stop autoboot: 0 |
And the uboot message shows the correct version.
Verify SRK hash is burnt into fuses
After upgrading uboot and restarting the board into uboot, read the fuses.
=> fuse read 3 0 1 Reading bank 3: Word 0x00000000: dd1f4f0f => fuse read 3 1 1 Reading bank 3: Word 0x00000001: 2591349e => fuse read 3 2 1 Reading bank 3: Word 0x00000002: 3f1d6384 => fuse read 3 3 1 Reading bank 3: Word 0x00000003: eb65ea7d => fuse read 3 4 1 Reading bank 3: Word 0x00000004: fcf6e42e => fuse read 3 5 1 Reading bank 3: Word 0x00000005: 02127cb9 => fuse read 3 6 1 Reading bank 3: Word 0x00000006: 92acc540 => fuse read 3 7 1 Reading bank 3: Word 0x00000007: ed42cf16 |
Check HAB for any events which might indicate failures.
=> hab_status Secure boot disabled HAB Configuration: 0xf0, HAB State: 0x66 No HAB Events Found! |
Enable secure boot
From uboot prompt run
=> fuse prog 0 6 0x2 |
These values are obtained from the imx6 reference manual page 348, 0x460[1]
SEC_CONFIG[1].
Restart system and enter uboot again to verify secure boot mode
=> hab_status Secure boot enabled HAB Configuration: 0xcc, HAB State: 0x99 No HAB Events Found! |
Sign kernel
Unsigned kernel
You can copy the zImage file from the original sdcard
release/linux64$ cp /media/user/Boot\ nitrog/zImage . |
Download the script that will help generate the signed image’s ivt
Calculate kernel image size in hex
release/linux64$ hexdump -C zImage | tail -n 1 006188e8 |
Pad kernel to nearest 4KB
def get_next_boundary(size, boundary): ”’Helper to calculate next memory value which is bounded by increments of sizes in bytes. Args: size: hex. Base starting value. e.g. 0x468628 boundary: integer. Interval steps in binary byte size . e.g. 4KB = 4096 bits Returns: bytes: hex string. Next highest interval to boundary size. e.g. get_next_boundary(0x468628,4096) ‘0x469000’ ”’ for x in range(size,size+boundary): if x % boundary == 0: return hex(x) In [44]: get_next_boundary(0x006188e8,4096)Out[44]: ‘0x619000’ |
Pad our kernel image
release/linux64$ objcopy -I binary -O binary –pad-to=0x619000 –gap-fill=0x00 zImage zImage-pad.bin |
Obtain uboot load address
10800000
Generate an ivt for the kernel image.
In [89]: gen_ivt(0x10800000,’/home/user/projects/imx-sb/release/linux64/zImage-pad.bin’) Creating ivt. /home/user/projects/imx-sb/release/linux64/zImage-pad.bin size:0x619000 New ivt.bin size is 32 |
Merge ivt binary with padded kernel
release/linux64$ cat zImage-pad.bin ivt.bin > zImage-pad-ivt.bin |
Download example csf file for zImage
and plug in specific values for blocks.
[Header] Version = 4.0 Hash Algorithm = sha256 Engine Configuration = 0 Certificate Format = X509 Signature Format = CMS [Install SRK] File = “../crts/SRK_1_2_3_4_table.bin” Source index = 0 [Install CSFK] File = “../crts/CSF1_1_sha256_4096_65537_v3_usr_crt.pem” [Authenticate CSF] [Install Key] Verification index = 0 Target index = 2 File = “../crts/IMG1_1_sha256_4096_65537_v3_usr_crt.pem” [Authenticate Data] Verification index = 2 # Description for blocks. # 0x10800000 = uboot loadaddr # 0x619000 = zImage size # 0x20 = ivt bin size # 0x619020 = zImage_size + ivt_size Blocks = 0x10800000 0x0 0x619020 “zImage-pad-ivt.bin” |
Generate signature
release/linux64$ ./bin/cst –o zImage_csf.bin –i zImage.csf CSF Processed successfully and signed data available in zImage_csf.bin |
Create signed binary by appending signature to kernel and ivt
cat zImage-pad-ivt.bin zImage_csf.bin > zImage_signed_10800000 |
Load signed image into ram via uboot and test
=> tftp 10800000 zImage_signed_10800000 Using FEC device TFTP from server 192.168.146.1; our IP address is 192.168.146.10 Filename ‘zImage_signed_10800000’. Load address: 0x10800000 Loading: ################################################################# … ################ 4.2 MiB/s done Bytes transferred = 6400368 (61a970 hex) => hab_auth_img 10800000 61a970 619000 Authenticate image from DDR location 0x10800000… Secure boot enabled HAB Configuration: 0xcc, HAB State: 0x99 No HAB Events Found! |
Values for hab_auth_img
=> help hab_auth_img hab_auth_img – authenticate image via HAB Usage: hab_auth_img addr length ivt_offset addr – image hex address length – image hex length ivt_offset – hex offset of IVT in the image |
$ hexdump -C /tftpboot/zImage_signed_10800000 |tail
0061a970
Boot
Need to modify u-boot script to load signed image when hab test passes.
I took Boundary device’s u-boot 6x_bootscript from my sdcard and stripped it down to what I needed to test the hab testing of the signed kernel image.
This is the result of my testing.
Once I was satisfied with the results, I then incorporated the hab test and signed kernel load commands to the main 6x_bootscript, and copied my signed kernel to the sdcard.
The final version of the 6x_bootscript is here.
As stated in the intro, the writeup isn’t a secure configuration and was created for testing imx6 hab features.
Test unsigned bootloader
Flash unsigned bootloader to verify that HAB feature works.
I wanted to test what the board would do if I flashed an unsigned bootloader.
I proceeded to reflash the EEPROM using the old unsigned uboot image and upgradeu. When the upgrade process completed and I power cycled the board, the system wouldn’t boot and no messages were displayed on the serial terminal.
Recover bricked board (imx_usb_loader)
Follow these instructions
imx_usb_loader install
https://boundarydevices.com/unbricking-nitrogen6x-sabre-lite-i-mx6-board/
imx_usb_loader using signed image
Follow the steps described here https://boundarydevices.com/high-assurance-boot-hab-dummies/#flashing under “What about imx_usb_loader?”
These were the steps I performed
Set my board’s jumpers to boot into otg mode 1 off, and 2 on. Attached to board’s usb otg port and power cycled.
Obtain compiled unsigned u-boot binary
release/linux64$ cp ../../u-boot-imx6/u-boot.imx . |
Get dcd length from u-boot binary
release/linux64$ dd if=u-boot.imx bs=1 skip=45 count=2 | od -t x2 -A none –endian=big 2+0 records in 2+0 records out 2 bytes copied, 8.1967e-05 s, 24.4 kB/s 0318 |
Update csf file.
e.g. u-boot_sign.csf
with
[Authenticate Data] Verification index = 2 Blocks = 0x00910000 0x2C 0x318 “u-boot.imx” |
Downloaded mod_4_mfgtool.sh tool
release/linux64$ wget \ https://storage.googleapis.com/boundarydevices.com/mod_4_mfgtool.sh |
Clear dcd address from u-boot binary
release/linux64$ bash ./mod_4_mfgtool.sh clear_dcd_addr u-boot.imx |
Create csf bin
release/linux64$ ./bin/cst –o u-boot_csf.bin –i u-boot_sign.csf CSF Processed successfully and signed data available in u-boot_csf.bin |
Set dcd in u-boot
release/linux64$ bash ./mod_4_mfgtool.sh set_dcd_addr u-boot.imx 4+0 records in 4+0 records out 4 bytes copied, 0.000401627 s, 10.0 kB/s |
Append both binaries
release/linux64$ cat u-boot.imx u-boot_csf.bin > u-boot_signed.imx |
Place board into spl mode and flash
imx_usb_loader$ sudo ./imx_usb ../release/linux64/u-boot_signed.imx |
You should now see u-boot prompt on the serial console.
I inserted my sdcard which contained my signed u-boot image (without the imx_usb_loader modification).
Ran
=> run upgradeu |
from uboot and waited for uboot to say completed and reset.
Turned off board power and set boot switches to 1, and 2 off.
Board started up as normal.
On my first attempt I failed. I went back and erased any old files and it worked on my second attempt.
References
I.MX6 High Assurance Boot (HAB) tutorial | https://boundarydevices.com/high-assurance-boot-hab-dummies/ |
Obtain serial number | https://freescale.jiveon.com/docs/DOC-94459 |
Unbricking | https://boundarydevices.com/unbricking-nitrogen6x-sabre-lite-i-mx6-board/ |
Reference manualPage 351 fuse map for srk hashPage 347 boot fuse mapPage 351 SRK_HASH fuse map description | https://www.nxp.com/doc/IMX6DQRM |
Secure boot HABv4Page 8 SRK fuse map programmingPage 381 Boot eFUSE descriptions | https://www.nxp.com/docs/en/application-note/AN4581.pdf |
Calculate efuse bank and word | https://imxdev.gitlab.io/tutorial/Burning_eFuses_on_i.MX/ |
uBoot tips | https://www.linuxjournal.com/content/handy-u-boot-trick |
Locking down bootloader | https://elinux.org/images/e/e0/Josserand-schulz-secure-boot.pdf |
Excellent writeup on HAB implementation and bootrom security | https://casualhacking.io/blog/2018/2/10/exploring-secured-boot-on-the-sabre-lite-imx6s-v13-sbc-and-nxp-habv4 |