Changes between Initial Version and Version 1 of secure_boot


Ignore:
Timestamp:
04/07/2021 06:04:06 AM (4 years ago)
Author:
Tim Harvey
Comment:

initial page

Legend:

Unmodified
Added
Removed
Modified
  • secure_boot

    v1 v1  
     1= Secure Boot
     2Secure Boot refers to hardware and software that does not allow an attacker to obtain sensitive data or boot altered firmware. This can be accomplished on modern embedded System on Chip devices by creating a Chain of Trust.
     3
     4== Chain of Trust
     5A Chain of Trust is established by validating each component of software to ensure that only trusted software can be used.
     6
     7For a typical embedded Linux board the chain of trust may look like:
     8 - Boot firmware (ie SPL) validated by embedded SoC BOOT ROM against signature hashes fused into one-time-programmable memory
     9 - U-Boot validated by the Boot firmware (ie SPL)
     10 - Kernel / FDT / initramfs images validated by U-Boot
     11 - Filesystem encryption (if needed) unlocked by the kernel+initramfs
     12
     13== Secure the Boot firmware
     14Securing your product boot involves using SoC specific methods to verify the initial boot code that is fetched from the storage device and executed.
     15
     16The initial boot code and how the various SoC manages secure boot varies per processor:
     17 - Venice: IMX8M HABv4: initial boot code is U-Boot SPL
     18 - Newport: CN803x Trusted Boot; initial boot code is Marvell BDK
     19 - Ventana: IMX6 HABv4; initial boot code is U-Boot SPL
     20
     21
     22== Secure the U-Boot Environment
     23For a secure U-Boot environment you want to disable the ability to stop autoboot and get to a U-Boot console. Additionally you do not want to use env variables that can be used by an attacker to affect the boot sequence.
     24
     25To do this you need to understand where U-Boot env comes from:
     26- static default env embedded in uboot itself is from include/env_default.h default_environment and is built up depending on a lot of various configs. You can see this by doing a 'make u-boot-initial-env' which will produce a 'u-boot-initial-env' file
     27- programmatic env vars set at runtime such as eth*addr, fdtcontroladdr, stdin, stdout, stderr (and more depending on your config)
     28- FLASH storage backed env such as MMC
     29
     30First and foremost you need to disable the ability to stop U-Boot's autoboot for security. This is done by setting CONFIG_BOOTDELAY=-2 which will disallow stopping from user input as well as disable any delay. Additionally your bootcmd should either be in an endless loop or end with a 'reset' so that any failure of the bootcmd does not leave you in an insecure U-Boot console environment. You probably want to do this step last however after debugging and development cycles as you may need to break into your bootloader during design and development.
     31
     32Secondly you need to ensure there are no env vars that can be set outside of U-Boot that can alter the boot path. There are two common cases you may fall into:
     331. Your Linux environment never needs to access or alter U-Boot env (ie via fw_setenv/fw_printenv). In this case you can:
     34 * define the following in your U-Boot config:
     35{{{#!bash
     36CONFIG_ENV_IS_NOWHERE=y
     37# CONFIG_ENV_IS_IN_EEPROM is not set
     38# CONFIG_ENV_IS_IN_FAT is not set
     39# CONFIG_ENV_IS_IN_EXT4 is not set
     40# CONFIG_ENV_IS_IN_FLASH is not set
     41# CONFIG_ENV_IS_IN_MMC is not set
     42# CONFIG_ENV_IS_IN_NAND is not set
     43# CONFIG_ENV_IS_IN_NVRAM is not set
     44# CONFIG_ENV_IS_IN_ONENAND is not set
     45# CONFIG_ENV_IS_IN_REMOTE is not set
     46CONFIG_USE_BOOTCOMMAND=y
     47CONFIG_BOOTCOMMAND="<your simple boot-command which uses 'root' variable>; reset"
     48# CONFIG_LEGACY_IMAGE_FORMAT is not set
     49CONFIG_FIT_SIGNATURE=y
     50}}}
     51  - Note the 'reset' at the end of the bootcmd. This is to ensure that a failure to boot does not drop you into an insecure U-boot console
     522. Your Linux environment needs to alter a variable (ie SWUpdate may need to alter a variable indicating which partition your rootfs is on if you are using a A/B ping-pong update method). In this case to allow U-boot to import the variable 'root' treated as a decimal you can define:
     53 * define the following in your U-Boot config:
     54{{{#!bash
     55CONFIG_ENV_IS_NOWHERE=y
     56CONFIG_ENV_IS_MMC=y
     57CONFIG_ENV_WRITEABLE_LIST=y
     58CONFIG_USE_BOOTCOMMAND=y
     59CONFIG_BOOTCOMMAND="<your simple boot-command which uses 'root' variable>; reset"
     60# CONFIG_LEGACY_IMAGE_FORMAT is not set
     61CONFIG_FIT_SIGNATURE=y
     62}}}
     63  - Note the 'reset' at the end of the bootcmd. This is to ensure that a failure to boot does not drop you into an insecure U-boot console
     64 * patch the appropriate board config file in include/configs (ie include/configs/imx8mm_venice.h for Venice, include/configs/octeontx_common.h for Newport, or include/configs/gw_ventana.h for Ventana) to set:
     65{{{#!bash
     66CONFIG_ENV_FLAGS_LIST_DEFAULT="root:dw"
     67CONFIG_ENV_FLAGS_LIST_STATIC="root:dw"
     68}}}
     69  - ENV_FLAGS_LIST_DEFAULT is the default environment .flags variable that sets up permissions and validations
     70  - ENV_FLAGS_LIST_STATIC is used when CONFIG_ENV_WRITEABLE_LIST=y and is an unchangeable list of vars with permissions and validations
     71  - make sure your writable variables do not appear in the static default env as that will always override what gets imported from a FLASH based env
     72 * use U-Boot 'mkenvimage' on your development host to create a binary FLASH env that sets necessary defaults for any writeable vars you declare
     73
     74
     75== Securing the Kernel, FDT, ramdisk via FIT images
     76The simplest way to secure the Kernel, the FDT, and the (optional) ramdisk image used to boot a Linux based OS is to use a U-Boot FIT image to contain signed versions of these.
     77
     78A FIT (Flattened Image Tree) file is a container designed to hold one or more binary objects. It also can contain meta-data about each object such as addresses, hash values, and signing key information which can be used to validate the binary objects.
     79
     80For more info on FIT images see:
     81 * http://www.denx.de/wiki/pub/U-Boot/Documentation/multi_image_booting_scenarios.pdf
     82 * https://elixir.bootlin.com/u-boot/latest/source/doc/uImage.FIT
     83
     84FIT images are created using the U-Boot {{{mkimage}}} application and use a template file (.its extension typically) which describes the image. The output is a binary image (.itb extension typically) which can be booted in U-Boot with the {{{bootm}}} command. The syntax of the its template is well documented in U-Boot source:
     85 * [https://elixir.bootlin.com/u-boot/latest/source/doc/uImage.FIT/source_file_format.txt its file format]
     86 * [https://elixir.bootlin.com/u-boot/latest/source/doc/uImage.FIT/signature.txt signed images]
     87 * [https://elixir.bootlin.com/u-boot/latest/source/doc/uImage.FIT/verified-boot.txt verified boot]
     88
     89There are two parts to using signed FIT images:
     90 * the FDT used by U-Boot itself needs to have a signature node that describes the public key details needed to verify the FIT image components
     91 * the FIT image needs to contain signatures covering the images inside as well as the configuration
     92
     93Both steps above are achieved by using the {{{mkimage}}} application from U-Boot which is built for your host OS in the tools directory. Note that it is important to use the mkimage app built for your U-Boot to ensure it has FIT signatures enabled and is compatible. In other words, **do not use a mkimage binary that came from a package in your development host distro**.
     94
     95Procedure for using a FIT image:
     96 1. Create a key. It is recommended to use a sha256,rsa2048 key:
     97{{{#!bash
     98openssl genpkey -algorithm RSA -out fit.key -pkeyopt rsa_keygen_bits:2048 -pkeyopt rsa_keygen_pubexp:65537
     99}}}
     100  * see [https://elixir.bootlin.com/u-boot/latest/source/doc/uImage.FIT/verified-boot.txt verified-boot.txt] for more info on key options
     101
     102 2. add a signature node to an existing compiled FDT (myboard.dtb in this example) with a key named $KEY_NAME in the $KEY_DIR directory:
     103{{{#!bash
     104cat << EOF > dummy.its
     105/dts-v1/;
     106
     107/ {
     108        description = "Dummy file for signing board .dtb files";
     109        #address-cells = <1>;
     110
     111        /* dummy image just to keep mkimage tool happy */
     112        images {
     113                kernel@1 {
     114                        description = "dummy kernel image";
     115                        data = /incbin/("dummy.bin");
     116                        type = "kernel";
     117                        arch = "arm64";
     118                        os = "linux";
     119                        compression = "none";
     120                        load = <0x0>;
     121                        entry = <0x0>;
     122                        hash@1 {
     123                                algo = "sha256";
     124                        };
     125                };
     126        };
     127        configurations {
     128                default = "config@1";
     129                config@1 {
     130                        description = "Linux configuration";
     131                        kernel = "kernel@1";
     132                        signature@1 {
     133                                algo = "sha256,rsa2048";
     134                                key-name-hint = "dummy";
     135                        };
     136                };
     137        };
     138};
     139EOF
     140touch dummy.bin
     141# replace dummy placeholders for key-name
     142sed -i "s;key-name-hint = \"dummy\";key-name-hint = \"$KEY_NAME\";g" dummy.its
     143# create cert for key
     144openssl req -batch -new -x509 -key $KEY_DIR/$KEY_NAME.key -out $KEY_DIR/$KEY_NAME.crt
     145# add signature node to myboard.dtb
     146mkimage -r -k $KEY_DIR -K myboard.dtb -f dummy.its dummy.itb
     147}}}
     148  * Note that the kernel image defined above and the created dummy.itb is just a dummy config to keep mkimage happy - the important thing above is the signature node in the configurations
     149  * Note that if you are using U-Boot SPL with a FIT image (such as Venice) you will need to perform a 'make flash.bin' again following the update of any dtb's to re-create the FIT image.
     150
     151 3. create a signed FIT image containing $KERNEL, $FDT, $INITRAMFS with a key named $KEY_NAME in the $KEY_DIR directory:
     152{{{#!bash
     153cat << EOF > fit.its
     154/dts-v1/;
     155
     156/ {
     157        description = "Image for Linux Kernel";
     158        #address-cells = <1>;
     159
     160        images {
     161                kernel {
     162                        description = "Linux Kernel";
     163                        data = /incbin/("kernel");
     164                        type = "kernel";
     165                        arch = "arm64";
     166                        os = "linux";
     167                        compression = "gzip";
     168                        load = <0x20080000>;
     169                        entry = <0x20080000>;
     170                        hash@1 {
     171                                algo = "sha256";
     172                        };
     173                };
     174                ramdisk {
     175                        description = "ramdisk";
     176                        data = /incbin/("ramdisk");
     177                        type = "ramdisk";
     178                        arch = "arm64";
     179                        os = "linux";
     180                        compression = "none";
     181                        hash@1 {
     182                                algo = "sha256";
     183                        };
     184                };
     185                fdt {
     186                        description = "fdt";
     187                        data = /incbin/("fdt");
     188                        type = "flat_dt";
     189                        arch = "arm64";
     190                        compression = "none";
     191                        hash@1 {
     192                                algo = "sha256";
     193                        };
     194                };
     195        };
     196        configurations {
     197                default = "config@1";
     198                config@1 {
     199                        description = "Linux configuration";
     200                        kernel = "kernel";
     201                        ramdisk = "ramdisk";
     202                        fdt = "fdt";
     203                        signature@1 {
     204                                algo = "sha256,rsa2048";
     205                                key-name-hint = "key";
     206                        };
     207                };
     208        };
     209};
     210EOF
     211# replace dummy placeholders for kernel, ramdisk, fdt binaries
     212sed -i "s;data = /incbin/(\"kernel\");data = /incbin/(\"$KERNEL\");g" fit.its
     213sed -i "s;data = /incbin/(\"ramdisk\");data = /incbin/(\"$RAMDISK\");g" fit.its
     214sed -i "s;data = /incbin/(\"fdt\");data = /incbin/(\"$FDT\");g" fit.its
     215# replace dummy placeholders for key-name
     216sed -i "s;key-name-hint = \"key\";key-name-hint = \"$KEY_NAME\";g" fit.its
     217mkimage -k $KEY_DIR -f fit.its fit.itb
     218}}}
     219  * If not using a ramdisk or fdt you can remove those sections above including the references in config and sign-images
     220  * Note the kernel compression is set to gzip so you will want to make sure your $KERNEL points to a compressed kernel image (Image.gz)
     221  * Note there is no compression type for a ramdisk as the kernel supports self-decompression of ramdisks. Use whatever compression type you want as long as you have enabled that compression type in your kernel.
     222  * Note that you need a valid load/entry address above which varies depending on your processor (0x40080000 for Venice, 0x20080000 for Newport, 0x10008000 for Ventana)
     223
     224 4. boot a FIT image using the U-boot {{{bootm}}} command:
     225  * from network
     226{{{#!bash
     227tftp $loadaddr fit.itb && bootm $loadaddr
     228}}}
     229  * from mmc device 0, partition 1, /boot/fit.itb:
     230{{{#!bash
     231load mmc 0:1 $loadaddr /boot/fit.itb && bootm $loadaddr
     232}}}