DIY Root of Trust using ARM Trusted Firmware on the 96Boards Hikey
This is a series of notes designed to be a walkthrough on how to configure the HiKey Kirin 620 to boot securely with ARM Trusted Firmware’s Trusted Board Boot. This does not use any proprietary settings or vendor-specific details about the SoC. Instead, the secure boot path relies on the SoC’s BOOT_SEL
configured to boot solely from the eMMC. With this configuration there should be no way to interrupt or bypass the root of trust via runtime changes.
Pay special attention to the should as this is not speaking from authority but rather from suspicion and research.
The Root of Trust (ROT) begins in the BL2
programmed to the eMMC’s boot0
partition. The bootrom must execute the HiKey’s l-loader.bin
and ARM-Trusted-Firmware’s (ATF) bl2.bin
written to this alternate boot partition. The eMMC’s extended CSD register 173 (BOOT_WP
) is written to permanent write-protect this content. This is a 1-time program operation that has the potential to brick the device.
As a quick preview, here are the functions and features:
BL2
(notBL1
because HiKey skipsBL1
, see the ATF notes) implementing a ROT.- eMMC
boot0
andboot1
partitions permanently hardware write-protected. - ROT implemented as a SHA256 of an RSA2048 public key you control, written to hardware write-protected region.
- Chain of Trust implemented with ATF’s development Trusted Board Boot (TBB) implementation.
- Configurable and verified Secure OS, and Non-Trusted World Firmware, essentially
BL31
,BL32
,SCP_BL2
, andBL3
. - Non-Trusted World implemented with U-Boot, for now the last thing verified.
Let’s start bottom up.
Dependencies
I recommend going through a few HiKey tutorials to become familiar with the flashing, recovery, and boot flows. This walkthrough will bootstrap many things, but if this is your entrypoint into HiKey development you may be lost by some references.
All of the following experimentation happens on a Ubuntu 16.04 host machine using an AARCH32 toolchain from apt and an AARCH64 toolchain from Linaro: https://releases.linaro.org/components/toolchain/binaries/7.2-2017.11/aarch64-linux-gnu/gcc-linaro-7.2.1-2017.11-i686_aarch64-linux-gnu.tar.xz
Build Linux
Use the recent build of Linux maintained by 96Boards for the HiKey SoC: https://github.com/96boards-hikey/linux and the branch working-android-hikey-linaro-4.4
.
I found the following changes needed to boot from U-Boot.
157,164c157
< CONFIG_BLK_DEV_INITRD=y
< CONFIG_INITRAMFS_SOURCE=""
< CONFIG_RD_GZIP=y
< CONFIG_RD_BZIP2=y
< CONFIG_RD_LZMA=y
< CONFIG_RD_XZ=y
< CONFIG_RD_LZO=y
< CONFIG_RD_LZ4=y
---
> # CONFIG_BLK_DEV_INITRD is not set
464,465c457,458
< CONFIG_CMDLINE="console=ttyAMA0"
< CONFIG_CMDLINE_FROM_BOOTLOADER=y
---
> CONFIG_CMDLINE="console=ttyAMA3,115200n8 root=/dev/mmcblk0p9 rw"
> # CONFIG_CMDLINE_FROM_BOOTLOADER is not set
467,470c460,461
< # CONFIG_CMDLINE_FORCE is not set
< CONFIG_EFI_STUB=y
< CONFIG_EFI=y
< CONFIG_DMI=y
---
> CONFIG_CMDLINE_FORCE=y
> # CONFIG_EFI is not set
1063c1054,1055
< # CONFIG_DEVTMPFS is not set
---
> CONFIG_DEVTMPFS=y
> CONFIG_DEVTMPFS_MOUNT=y
1163c1155,1156
< # CONFIG_TIFM_CORE is not set
---
> CONFIG_TIFM_CORE=y
> CONFIG_TIFM_7XX1=y
1198c1191,1193
< # CONFIG_CB710_CORE is not set
---
> CONFIG_CB710_CORE=y
> # CONFIG_CB710_DEBUG is not set
> CONFIG_CB710_DEBUG_ASSUMPTIONS=y
3479,3480c3474,3477
< # CONFIG_MMC_SDHCI_PCI is not set
< # CONFIG_MMC_SDHCI_ACPI is not set
---
> CONFIG_MMC_SDHCI_IO_ACCESSORS=y
> CONFIG_MMC_SDHCI_PCI=y
> CONFIG_MMC_RICOH_MMC=y
> CONFIG_MMC_SDHCI_ACPI=y
3482,3485c3479,3482
< # CONFIG_MMC_SDHCI_OF_ARASAN is not set
< # CONFIG_MMC_SDHCI_OF_AT91 is not set
< # CONFIG_MMC_SDHCI_F_SDH30 is not set
< # CONFIG_MMC_TIFM_SD is not set
---
> CONFIG_MMC_SDHCI_OF_ARASAN=y
> CONFIG_MMC_SDHCI_OF_AT91=y
> CONFIG_MMC_SDHCI_F_SDH30=y
> CONFIG_MMC_TIFM_SD=y
3487,3488c3484,3485
< # CONFIG_MMC_CB710 is not set
< # CONFIG_MMC_VIA_SDMMC is not set
---
> CONFIG_MMC_CB710=y
> CONFIG_MMC_VIA_SDMMC=y
3493,3498c3490,3495
< # CONFIG_MMC_DW_PCI is not set
< # CONFIG_MMC_VUB300 is not set
< # CONFIG_MMC_USHC is not set
< # CONFIG_MMC_USDHI6ROL0 is not set
< # CONFIG_MMC_TOSHIBA_PCI is not set
< # CONFIG_MMC_MTK is not set
---
> CONFIG_MMC_DW_PCI=y
> CONFIG_MMC_VUB300=y
> CONFIG_MMC_USHC=y
> CONFIG_MMC_USDHI6ROL0=y
> CONFIG_MMC_TOSHIBA_PCI=y
> CONFIG_MMC_MTK=y
3524d3520
< # CONFIG_LEDS_INTEL_SS4200 is not set
3631d3626
< CONFIG_RTC_DRV_EFI=y
3882,3883d3876
< CONFIG_DMIID=y
< # CONFIG_DMI_SYSFS is not set
3886,3896d3878
<
< #
< # EFI (Extensible Firmware Interface) Support
< #
< CONFIG_EFI_VARS=y
< CONFIG_EFI_ESRT=y
< CONFIG_EFI_VARS_PSTORE=y
< CONFIG_EFI_VARS_PSTORE_DEFAULT_DISABLE=y
< CONFIG_EFI_PARAMS_FROM_FDT=y
< CONFIG_EFI_RUNTIME_WRAPPERS=y
< CONFIG_EFI_ARMSTUB=y
4005d3986
< CONFIG_EFIVAR_FS=y
4557,4562d4537
< CONFIG_DECOMPRESS_GZIP=y
< CONFIG_DECOMPRESS_BZIP2=y
< CONFIG_DECOMPRESS_LZMA=y
< CONFIG_DECOMPRESS_XZ=y
< CONFIG_DECOMPRESS_LZO=y
< CONFIG_DECOMPRESS_LZ4=y
4585d4559
< CONFIG_UCS2_STRING=y
The goal of this configuration is to boot as simple as possible. No initial ramdisk is used and the arguments are set statically.
The commands used to build are as follows:
$ make ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu- hikey_defconfig
$ make ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu- menuconfig
$ make ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu- -j8 Image hisilicon/hi6220-hikey.dtb
For these tests I was using the image built for the HiKey. If it is helpful, the kernel can be replaced using the following flow, assuming you are working in the clone of Linux.
$ simg2img ./jessie.updated.img jessie.updated.fimg
$ sudo mount -o loop ./jessie.updated.fimg ./jessie
$ sudo cp ./arch/arm64/boot/dts/hisilicon/hi6220-hikey.dtb ../jessie/boot
$ sudo cp ./arch/arm64/boot/Image ../jessie/boot
$ sudo umount ./jessie
$ img2simg ./jessie.updated.fimg jessie.updated.simg
If you have a HiKey working, simply move the hi6220-hikey.dtb
and Image
to /boot
on the board’s filesystem.
Build U-Boot as the non-trusted world firmware
The use of U-Boot over UEFI is not required. I am more familiar with U-Boot and it can bootstrap Linux and a device tree easily. Later on it may be replaced with booting straight to Linux from ATF.
$ git clone https://github.com/trini/u-boot
$ cd u-boot
$ make O=build_hikey hikey_defconfig
I simplified the bootcommand
for easier debugging. This removes the default, flexible, U-Boot bootcommand
:
$ cat ./build_hikey/.config| grep BOOTCOMMAND
CONFIG_USE_BOOTCOMMAND=y
CONFIG_BOOTCOMMAND="ext2load mmc 0:9 0x00080000 boot/Image; ext2load mmc 0:9 0x02000000 boot/hi6220-hikey.dtb; booti 0x00080000 - 0x02000000"
The goal is to load only Linux and the device tree.
The following change is needed because Linux is uncompressed:
index e789f68..bba2837 100644
--- a/common/bootm.c
+++ b/common/bootm.c
@@ -31,7 +31,7 @@
#ifndef CONFIG_SYS_BOOTM_LEN
/* use 8MByte as default max gunzip size */
-#define CONFIG_SYS_BOOTM_LEN 0x800000
+#define CONFIG_SYS_BOOTM_LEN 0x8000000
#endif
#define IH_INITRD_ARCH IH_ARCH_DEFAULT
And to build U-Boot:
$ make O=build_hikey -j6
[...]
LD u-boot
OBJCOPY u-boot.srec
OBJCOPY u-boot-nodtb.bin
SYM u-boot.sym
DTC arch/arm/dts/hi6220-hikey.dtb
make[3]: 'arch/arm/dts/hi6220-hikey.dtb' is up to date.
SHIPPED dts/dt.dtb
FDTGREP dts/dt-spl.dtb
COPY u-boot.dtb
CAT u-boot-dtb.bin
COPY u-boot.bin
LD u-boot.elf
CFGCHK u-boot.cfg
make[1]: Leaving directory './build_hikey'
Build OP-TEE OS
OP-TEE has plenty of documentation. We will rush through this and focus on building a BL32
example only.
$ mkdir optee
$ cd optee
$ git clone https://github.com/OP-TEE/optee_os
$ git clone https://github.com/OP-TEE/build
$ cd build
$ make -f hikey.mk \
DEBUG=1 \
AARCH64_CROSS_COMPILE=$AARCH64_PATH/bin/aarch64-linux-gnu- \
AARCH32_CROSS_COMPILE=arm-linux-gnueabihf- \
optee-os
Build ATF without Trusted Board Boot
This is a stepping stone to booting securely. It will provide a BL1
to use in the recovery image as well as verify the ATF code can boot our non-trusted world firmware, U-Boot, and our Linux. We will build ATF again with TBB enabled soon.
$ git clone https://github.com/ARM-software/arm-trusted-firmware
$ cd arm-trusted-firmware
$ make \
PLAT=hikey DEBUG=1 \
SCP_BL2=../mcuimage.bin \
BL32=../optee/optee_os/out/arm/core/tee-header_v2.bin \
BL32_EXTRA1=../optee/optee_os/out/arm/core/tee-pager_v2.bin \
BL32_EXTRA2=../optee/optee_os/out/arm/core/tee-pageable_v2.bin \
SPD=opteed \
BL33=../u-boot/build_hikey/u-boot.bin \
all fip
[...]
Building hikey
mkdir -p "./build/hikey/debug/bl1"
mkdir -p "./build/hikey/debug/bl2"
mkdir -p "./build/hikey/debug/bl31"
make CPPFLAGS="-DVERSION='\"v1.5(debug):v1.5-285-g498161a\"'" --no-print-directory -C tools/fiptool
[...]
Built build/hikey/debug/fip.bin successfully
Building l-loader
and recovery mode
Recovery mode is booted by the HiKey when Boot Select
is enabled, pin 3-4 are connected on J15
. There is plenty of documentation for this: https://github.com/96boards/documentation/wiki/HiKeyUEFI but I would like to include the specifics of how I build a recovery mode image for completeness.
This will allow us to flash images during this walkthrough using fastboot
.
To accomplish this we will use another version of ARM-Trusted-Firmware maintained by 96Boards. This will build a bl1.bin
used in the l-loader
build. We will only use this once and it will not be part of our boot flow, just the recovery flow. The recovery image is loaded into device memory over the USB OTG so we can fastboot flash the eMMC. This code will not persist on the device.
$ export CROSS_COMPILE=$AARCH64_PATH/bin/aarch64-linux-gnu-
$ git clone https://github.com/96boards-hikey/atf-fastboot
$ cd atf-fastboot
$ make DEBUG=1
[...]
LD build/hikey/debug/bl1/bl1.elf
BIN build/hikey/debug/bl1.bin
Built build/hikey/debug/bl1.bin successfully
NB: We are not building for HiKey960, but we will use a checkout referencing the 960 board, sorry for this confusion.
Checkout the l-loader
repository, which has Makefiles for building the recovery image.
$ git clone https://github.com/96boards-hikey/l-loader -b testing/hikey960_v1.2
$ cd l-loader
$ ln -s ../arm-trusted-firmware/build/hikey/debug/bl1.bin bl1.bin
$ ln -s ../arm-trusted-firmware/build/hikey/debug/fip.bin fip.bin
$ ln -s ../atf-fastboot/build/hikey/debug/bl1.bin fastboot.bin
Confusing right?
Make the recovery image using:
$ make -f hikey.mk PTABLE_LST=linux-8g recovery.bin
Make the l-loader.bin
and bl2.bin
image using:
$ make -f hikey.mk PTABLE_LST=linux-8g l-loader.bin
Assembling the non-Secured boot flow
When CPU0 comes out of reset the bootrom will read the eMMC’s boot0
partition. This is memory mapped to address 0xF980:0000
and the first address executed is at offset 0x800
. This contains the l-loader
code that switches execution from aarch32 to aarch64 and jumps to offset 0x1000
. 0xF980:1000
is the entrypoint of ARM-Trusted-Firmware’s BL2
.
You can dive into the build logic in the ARM-Trusted-Firmware code to understand why BL1
is skipped as an optimization. In this case BL2
is executed in EL3
.
The ARM-Trusted-Boot flow takes over and BL2
is our loader, executing the proprietary mcuimage.bin
from HiKey, our OP-TEE secure OS as BL32
, and eventually our BL33
non-trusted firmware, U-Boot. U-Boot will auto-boot in 3 seconds unless interrupted on the command line.
NB: U-Boot is configured to load an environment from the uSD card. In this example no uSD is needed or used. The U-Boot default environment, compiled into the image, is used.
U-Boot loads our Linux and device tree.
The recovery.img
is used to program our content using fastboot. It uses the normal l-loader
bootstrap code, the normal ATF bl1.bin
, and the atf-fastboot
’s bl1.bin
as NS_BL1U
. Using this recovery mode is optional. If you have a working board then you can flash the eMMC partitions using dd
.
Here is an example:
$ sudo python ~/git/hikey/hisi-idt.py --img1=recovery.bin
$ sudo fastboot flash loader l-loader.bin
$ sudo fastboot flash fastboot fip.bin
If you have an already running HiKey you can use the following:
$ sudo dd if=fip.bin of=/dev/mmcblk0p4
$ echo 0 | sudo tee /sys/block/mmcblk0boot0/force_ro
$ sudo dd if=l-loader.bin of=/dev/mmcblk0boot0
Build ATF with Trusted Board Boot for HiKey
The first thing we should do is create an RSA2048 keypair. ATF is limited to 2048bit keys at the moment.
$ mkdir keys
$ openssl genrsa 2048 > ./keys/rot.key 2>/dev/null
Patch your ATF to build Trusted Board Boot for the HiKey using: https://patch-diff.githubusercontent.com/raw/ARM-software/arm-trusted-firmware/pull/1449.diff be careful as this is still undergoing code review and has not been heavily tested.
This will build a BL2
with the ROT key’s public key SHA256 hash. The key will be placed into RW storage within the fip.bin
later.
Checkout the mbedtls code, version 2.10.0:
$ git clone https://github.com/ARMmbed/mbedtls -b mbedtls-2.10.0
And finally glue it all together to create a bl1.bin
and fip.bin
:
$ cd arm-trusted-firmware
$ make \
PLAT=hikey \
DEBUG=1 \
BL33=../u-boot/build_hikey/u-boot.bin \
SCP_BL2=../mcuimage.bin \
BL32=../optee/optee_os/out/arm/core/tee-header_v2.bin \
BL32_EXTRA1=../optee/optee_os/out/arm/core/tee-pager_v2.bin \
BL32_EXTRA2=../optee/optee_os/out/arm/core/tee-pageable_v2.bin \
SPD=opteed \
TRUSTED_BOARD_BOOT=1 \
MBEDTLS_DIR=../../mbedtls \
GENERATE_COT=1 \
SAVE_KEYS=1 \
TRUSTED_WORLD_KEY=../keys/trusted_world.key \
NON_TRUSTED_WORLD_KEY=../keys/nt_worlded.key \
ROT_KEY=../keys/rot.key \
SCP_BL2_KEY=../keys/scp_bl2_content.key \
BL31_KEY=../keys/soc_content.key \
BL32_KEY=../keys/tos_content.key \
BL33_KEY=../keys/nt_fw_content.key \
all fip
The first time you run this command it should finish with some stdout:
NOTICE: Creating new key for 'Trusted World key'
NOTICE: Creating new key for 'Non Trusted World key'
NOTICE: Creating new key for 'SCP Firmware Content Certificate key'
NOTICE: Creating new key for 'SoC Firmware Content Certificate key'
NOTICE: Creating new key for 'Trusted OS Firmware Content Certificate key'
NOTICE: Creating new key for 'Non Trusted Firmware Content Certificate key'
Subsequent runs should be using your existing keys. Everything but the rot.key
can be updated later since only the hash of the rot.key
will be write-protected.
Note that the _KEY
variables are optional. Keys will be generated during the build automatically for you.
Basic Trusted Board Boot failure testing
As a very-quick smoke test for TBB I will edit a byte in U-Boot.
$ dd if=/dev/mmcblk0p4 of=fip.bin
$ grep -ab 2018.07-rc1-00132-g606fddd-dirty fip.bin
530221:U-Boot 2018.07-rc1-00132-g606fddd-dirty (Jun 18 2018 - 22:17:00 -0400)hikey_SM__DMI_ERROR : memory not allocated
I will switch the 2018 to be 3018.
$ dd of=/dev/mmcblk0p4 if=fip.bin
16384+0 records in
16384+0 records out
8388608 bytes (8.4 MB) copied, 1.52253 s, 5.5 MB/s
And sure enough, after a reset I see on the debug UART:
[...]
INFO: Loading image id=9 at address 0xf9858000
INFO: Image id=9 loaded: 0xf9858000 - 0xf98584da
INFO: Loading image id=13 at address 0xf9858000
INFO: Image id=13 loaded: 0xf9858000 - 0xf9858430
INFO: Loading image id=3 at address 0xf9858000
INFO: Image id=3 loaded: 0xf9858000 - 0xf9861058
INFO: BL2: Loading image id 5
INFO: Loading image id=11 at address 0x35000000
INFO: Image id=11 loaded: 0x35000000 - 0x350004ea
INFO: Loading image id=15 at address 0x35000000
INFO: Image id=15 loaded: 0x35000000 - 0x35000440
INFO: Loading image id=5 at address 0x35000000
INFO: Image id=5 loaded: 0x35000000 - 0x35061d9a
Platform exception reporting:
ESR_EL3: 0000000096000061
ELR_EL3: 00000000f9801574
Success! A more complete test involves generating a fake signature for U-Boot and writing a correct new SHA256 hash. But at least we know the auth-driver code is running.
Danger Zone! Permanent Write Protect eMMC boot0
Once you are happy with the BL2
build and DO NOT PLAN ON UPDATING THE ROT.KEY OR BL2 CODE we need to create our root of trust.
There is a feature of MMC similar to the hardware block write protection on SPI flash that allows you to write-protect the alternate boot partitions. Be very very careful as this is a 1-time operation and since this is a BGA eMMC, recovery is difficult. Remember we are only concerned with write-protecting the boot partitions. Yifan has a quick read on the dangers of this write-protection being in an non-configured tri-state. While you are here you might consider locking the GP and User sections non-write-protect.
The /dev/mmcblk0boot{0,1}
partitions hold the l-loader.bin
code, this is l-loader
and BL2
. Everything else is within the fip.bin
on the fourth partition. Only this l-loader.bin
code will become permanent read-only/write-protected.
This uses the eCSD[173]
register on the MMC, named EXT_CSD_BOOT_WP
. We will 1-time program this with a value of 0x04
or EXT_CSD_BOOT_WP_B_PERM_WP_EN
. Our Linux kernel allows programming via an ioctl
.
On the HiKey:
(hikey)$ git clone https://github.com/chiel99/mmc-utils
(hikey)$ cd mmc-utils;
Apply this diff to change the mmc
tool’s write-protection logic from Power-On (write-protect until next power cycle) to permanent write-protection. Permanent write-protection is not available by default because of the danger mentioned above.
diff --git a/mmc_cmds.c b/mmc_cmds.c
index d7215bb..a32d317 100644
--- a/mmc_cmds.c
+++ b/mmc_cmds.c
@@ -279,7 +279,7 @@ int do_writeprotect_boot_set(int nargs, char **argv)
}
value = ext_csd[EXT_CSD_BOOT_WP] |
- EXT_CSD_BOOT_WP_B_PWR_WP_EN;
+ EXT_CSD_BOOT_WP_B_PERM_WP_EN;
ret = write_extcsd_value(fd, EXT_CSD_BOOT_WP, value);
if (ret) {
fprintf(stderr, "Could not write 0x%02x to "
Then build and set the write-protection:
$ make
(DANGER)$ sudo ./mmc writeprotect boot set /dev/mmcblk0boot0
Now we can check the status:
$ sudo ./mmc writeprotect boot get /dev/mmcblk0boot0
Boot write protection status registers [BOOT_WP_STATUS]: 0x0a
Boot Area Write protection [BOOT_WP]: 0x04
Power ro locking: possible
Permanent ro locking: possible
ro lock status: locked permanently
$ cat /sys/block/mmcblk0boot{0,1}/ro_lock_until_next_power_on
2
2
Now the boot0
partition is the only entrypoint from the bootrom, unless you modify physical properties like the J15 jumpers. This partition’s content cannot be modified without an external programmer on the eMMC. In this configuration, from software perspective, the BL2 code is a ROM.
If you try to use fastboot
in recovery mode, flashing the l-loader
partition will not produce an error, but will not write the content, it silently fails.
The BL2 contains a permanent hash of the rot.key
’s public key content and forces signature verification of all additional content down to the non-trusted firmware code. U-Boot is the last part of code verified, in this form it will happily run any Linux and device tree configuration. It is possible to later extend this chain of trust to the Kernel using U-Boot’s verified boot implementation.
Again be careful and please challenge my assertions because I am not an authority and this DIY secured boot should not be used where high-security controls are needed. You should engage device manufactures and implement their ODM-preferred secured boot features. If you find any issues with the assumptions used here, please reach out!
Limitations
- There is no secure counter implemented, this is dangerous as there is no way to revoke old builds and the keys used to sign.
- The
BOOT_WP
permanent feature write-protect of eMMC is not fully investigated as a secure method for starting a Root of Trust. - The HiKey’s bootrom is not confirmed to only boot from the eMMC’s alternate boot partitions. There may be a software/eFUSE that can toggle this to boot another user-controlled location, such as a general purpose partition on the eMMC.
- The RSA key sizes used in ATF’s Trusted Board Boot only support RSA2048 (SHA256 is used for hashing). From what I can see the ECDSA curves available in the ATF build provide an equivalent level of security. Increasing the relative bits available in the build is absolutely a follow up.
- The
certtool
in ATF cannot use a smartcard/HSM/PKCS11 to sign content. - There is no runtime recovery option. If verification fails the board will hang.