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:

  1. Create a signed image: The first step is to create a signed image which can be verified during boot.
  2. 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.
  3. 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)
 

Leave a Reply