This blog post, written by István Telek, is the third post in a series of blog posts on transforming the Raspberry Pi into a security enhanced IoT platform. It describes how you can implement a verified boot process on the Raspberry pi.
Introduction
Securing the boot process is the first step in securing an embedded system. Booting untrusted images can circumvent existing security measures, therefore it is vital to ensure the integrity of these images. U-Boot introduced a feature called “verified boot” which can be used to verify images while still allowing them to be upgraded when needed. This feature needs an initial trusted image called a “root of trust” which is missing on the Raspberry Pi platform. This is a known limitation of the platform, but we are only using it to demonstrate the verified boot process. More information about verified boot can be found at [1].
The following steps are required to implement verified boot:
- Create a signed image: The first step is to create a signed image which can be verified during boot.
- Compile U-Boot with FIT image support: By default U-Boot doesn’t verify the images, so we have to configure it to support verified boot.
- Install the image: The next step is to install the signed image and boot from it.
1. Create a signed image
U-Boot’s image format is called FIT (Flat Image Tree) which is a structured container format that supports multiple images, device trees, etc. We can add signatures during or after creating a FIT.
The FIT image source file is an .its (image tree source) file, which describes the included images and the signature methods. We can use multiple configurations for booting and we can sign these configurations. In our example we are using OP-TEE with Linux to demonstrate loading multiple images during boot. More information about booting multiple images can be found at [2]. Our .its file is the following:
/dts-v1/;
/ {
description = "RPi FIT Image";
#address-cells = <2>;
images {
kernel-1 {
description = "default kernel";
data = /incbin/("Image");
type = "kernel";
arch = "arm64";
os = "linux";
compression = "none";
load = <0x00080000>;
entry = <0x00080000>;
hash@1 {
algo = "sha1";
};
};
tee-1 {
description = "atf";
data = /incbin/("optee.bin");
type = "standalone";
arch = "arm64";
compression = "none";
load = <0x08400000>;
entry = <0x08400000>;
hash@1 {
algo = "sha1";
};
};
fdt-1 {
description = "device tree";
data = /incbin/("bcm2710-rpi-3-b.dtb");
type = "flat_dt";
arch = "arm64";
compression = "none";
hash@1 {
algo = "sha1";
};
};
};
configurations {
default = "config-1";
config-1 {
description = "default configuration";
kernel = "kernel-1";
loadables = "tee-1";
fdt = "fdt-1";
signature-1 {
algo = "sha1,rsa2048";
key-name-hint = "dev";
sign-images = "fdt", "kernel", "loadables";
};
};
};
};
The steps to create a signed FIT image which includes all images and the Linux DTB are described below:
Copy the images to the desired location
$ mkdir ../fit && cd ../fit
$ ln -s ../optee/linux/arch/arm64/boot/dts/broadcom/bcm2710-rpi-3-b.dtb
$ ln -s ../optee/linux/arch/arm64/boot/Image
$ ln -s ../optee/arm-trusted-firmware/build/rpi3/debug/optee.bin
$ cp ../optee/linux/arch/arm64/boot/dts/broadcom/bcm2710-rpi-3-b.dtb bcm2710-rpi-3-b-pubkey.dtb
Generate a new key-pair
We have to generate a new RSA key pair which we can use to sign our configuration. We can use OpenSSL to create this key pair.
$ mkdir keys
$ openssl genrsa -F4 -out keys/dev.key 2048
$ openssl req -batch -new -x509 -key keys/dev.key -out keys/dev.crt
Create a signed FIT image
The next step is to create the image and sign our configuration with the generated private key. The image source is called im-magic.its
which is the source file shown above. The signed image is called image.fit
. We are storing the public key in U-Boot’s Control DTB so it can verify the image during boot.
$ ../optee/u-boot/tools/mkimage -f im-magic.its -K bcm2710-rpi-3-b-pubkey.dtb -k keys -r image.fit
2. Compile U-Boot with FIT image support
To use our signed image during boot replace optee/build/rpi3/firmware/uboot.env.txt
with the following lines:
boot_fit=bootm {fit_addr}
fit_addr=0x1F000000
load_fit=fatload mmc 0:1{fit_addr} image.fit
mmcboot_old=run load_kernel; run load_dtb; run load_firmware; run set_bootargs_tty set_bootargs_mmc set_common_args; run boot_it
mmcboot=run load_fit; run set_bootargs_tty set_bootargs_mmc set_common_args; run boot_fit
nfsboot_old=usb start; dhcp {kernel_addr_r}{tftpserverip}:Image; dhcp {fdt_addr_r}{tftpserverip}:{fdtfile}; dhcp{atf_load_addr} {tftpserverip}:{atf_file}; run set_bootargs_tty set_bootargs_nfs set_common_args; run boot_it
nfsboot=usb start; dhcp {fit_addr}{tftpserverip}:image.fit; run set_bootargs_tty set_bootargs_nfs set_common_args; run boot_fit
Note: The original boot command is available with mmcboot_old
. Changing nfsboot
is optional, it is only used for network boot. The FIT image is loaded to fit_addr
.
Next compile U-Boot and related tools by following the steps below:
Compile the U-Boot env file
We have to recompile our env file to reflect the changes made to uboot.env.txt
$ cd optee/build
$ make EXT_DTB=../../fit/bcm2710-rpi-3-b-pubkey.dtb u-boot-rpi-bin
Note: These variables can be configured during boot in U-Boot command line with setenv
, editenv
and saveenv
.
Configure U-Boot to support FIT images
Add the following lines to optee/u-boot/configs/rpi_3_defconfig
. This enables FIT, FIT RSA signatures, and it indicates that we store the public key in the U-Boot Control DTB.
CONFIG_OF_CONTROL=y
CONFIG_FIT=y
CONFIG_FIT_SIGNATURE=y
CONFIG_RSA=y
Replace the following definition in optee/u-boot/include/configs/rpi.h
. This enables larger kernel images in the FIT image. (The default is 8MiB which is increased to 16 MiB)
#define CONFIG_SYS_BOOTM_LEN (16 << 20)
Build U-Boot with FIT image support
Rebuild U-Boot with the new DTB. EXT_DTB is the new DTB which includes the public key.
$ cd optee/build
$ make u-boot-clean u-boot-rpi-bin-clean head-bin-clean
$ make EXT_DTB=../../fit/bcm2710-rpi-3-b-pubkey.dtb u-boot u-boot-rpi-bin
3. Install the image
To install the image, copy optee/out/uboot.env
, optee/u-boot/u-boot-rpi.bin
and fit/image.fit
to the SD card boot partition. Delete Image
and optee.bin
to save some space. If the image was verified successfully, a similar output should appear on the UART terminal during boot:
U-Boot 2016.03-gf1714d56f2 (Apr 12 2018 - 13:54:58 +0000)
DRAM: 944 MiB
RPI 3 Model B (0xa02082)
boot regs: 0x00000000 0x00000000 0x00000000 0x00000000
MMC: bcm2835_sdhci: 0
reading uboot.env
In: serial
Out: lcd
Err: lcd
Net: Net Initialization Skipped
No ethernet found.
starting USB...
USB0: Core Release: 2.80a
scanning bus 0 for devices... 3 USB Device(s) found
scanning usb for storage devices... 0 Storage Device(s) found
scanning usb for ethernet devices... 1 Ethernet Device(s) found
Hit any key to stop autoboot: 0
reading image.fit
10801892 bytes read in 927 ms (11.1 MiB/s)
## Loading kernel from FIT Image at 1f000000 ...
Using 'config-1' configuration
Verifying Hash Integrity ... sha1,rsa2048:dev+ OK
Trying 'kernel-1' kernel subimage
Description: default kernel
Type: Kernel Image
Compression: uncompressed
Data Start: 0x1f0000c0
Data Size: 10368000 Bytes = 9.9 MiB
Architecture: AArch64
OS: Linux
Load Address: 0x00080000
Entry Point: 0x00080000
Hash algo: sha1
Hash value: c50e407ece2782c54de9bd13bc848e52776af800
Verifying Hash Integrity ... sha1+ OK
## Loading fdt from FIT Image at 1f000000 ...
Using 'config-1' configuration
Trying 'fdt-1' fdt subimage
Description: device tree
Type: Flat Device Tree
Compression: uncompressed
Data Start: 0x1fa498f8
Data Size: 13376 Bytes = 13.1 KiB
Architecture: AArch64
Hash algo: sha1
Hash value: c9910b9685ec0c0dcabf8efeb10142652523f690
Verifying Hash Integrity ... sha1+ OK
Booting using the fdt blob at 0x1fa498f8
## Loading loadables from FIT Image at 1f000000 ...
Trying 'tee-1' loadables subimage
Description: atf
Type: Standalone Program
Compression: uncompressed
Data Start: 0x1f9e35a0
Data Size: 418432 Bytes = 408.6 KiB
Architecture: AArch64
Load Address: 0x08400000
Entry Point: 0x08400000
Hash algo: sha1
Hash value: 5fc54417f8d074f494c886473b5e32b240853696
Verifying Hash Integrity ... sha1+ OK
Loading loadables from 0x1f9e35a0 to 0x08400000
Loading Kernel Image ... OK
reserving fdt memory region: addr=0 size=1000
reserving fdt memory region: addr=8000000 size=2000000
Loading Device Tree to 000000003ab21000, end 000000003ab2743f ... OK
Starting kernel ...
## Transferring control to ARM TF (at address 8400000) (dtb at 3ab21000)...
The next post will explain OS hardening steps on the Raspberry Pi.
References
- 1 S. Glass, “Verified U-Boot,” LWN.net, Oct. 23, 2013. [Online]. Available: https://lwn.net/Articles/571031/ [Accessed: May 10, 2018].
- 2 J. Teki, “U-Boot – Multi image booting scenarios,” denx.de, [Online]. Available: https://www.denx.de/wiki/pub/U-Boot/Documentation/multi_image_booting_scenarios.pdf [Accessed: May 10, 2018].
Sources
- U-Boot FIT image documentation (https://github.com/u-boot/u-boot/tree/master/doc/uImage.FIT)