X5 安全启动概述

以下仅针对于烧了地瓜KEY的芯片,如果是烧的是客户自己的KEY,请参考X5 Customer root rsa key hash烧录及使用

../../../../_images/secure-boot-flow.png

启动流程如上图所示

阶段 完成 验证方式 密钥 开启方式
1 bootrom 验签和解密 BL2 RSA4096 + AES X5密钥 默认开启
2 BL2 验证 BL31、optee、DDR FW RSA X5密钥 默认开启
2 BL2 验证 BL2 CFG、Uboot RSA 客户密钥 默认不开启,可通过烧写 eFuse 开启
3 Uboot 验证 Kernel AVB 客户密钥 默认不开启,可通过烧写 eFuse 开启
4 Kernel 验证 Rootfs dm-verity 客户密钥 默认不开启,可通过烧写 eFuse 开启

Uboot & BL2 CFG 验证

以下仅针对于烧了地瓜KEY的芯片,如果是烧的是客户自己的KEY,请参考X5 Customer root rsa key hash烧录及使用。以下部分可以跳过

流程概述

PC 端签名流程

../../../../_images/sign-uboot.png

在PC端签名 Uboot 的流程如上图所示,主要包括以下两个步骤

  • 对 Uboot image 签名

  • 对 BL2 CFG 镜像签名

板端验证流程

../../../../_images/verify-uboot.png

在板端BL2验证的流程如上图所示

  • 从镜像中读取公钥,计算公钥 hash 并与 eFuse 中的公钥 hash 比较,确保公钥的合法性

  • 验证 BL2 CFG 镜像

  • 验证 Uboot image

密钥管理

Uboot 和 BL2 CFG 密钥的生成与烧录

Uboot 和 BL2 CFG 的 key 位于 device/horizon/x5/board_cfg/soc/bl2_cfg/bl2_rot_prikey.pem

注意 签名校验算法使用的是RSA4096,私钥使用标准的PEM PKCS#1格式。可以使用下列命令生成用户自己的私钥文件

openssl genrsa -out test_uboot_priv.pem 4096   #生成私钥

客户在使用时替换该文件即可,编译后公钥 hash 数据保存到文件 out/deploy/uboot/pubkey-hash.txt。 同时 BSP 会将公钥 hash 编译到 BL2 CFG 文件中。 并在首次启动后 BL2 将其烧录到 eFuse 中。

详细可参考eFuse烧录

Kernel & rootfs 验证

AVB 和 dm-verity 概述

Uboot 验证 Kernel,基于 AVB(Android Verified Boot)实现,结合 Linux Kernel 的 DM-verity 机制,保证根文件系统的完整性。

AVB 使用的核心数据结构是 vbmeta 结构体,保存在 vbmeta 镜像中。这个结构体中包含了多个描述符,每个描述符用于保存镜像文件的相关信息,如 boot 分区的哈希值,system 分区的 Hashtree 数据,传递给 Kernel 的 cmdline 等,并且对上述信息进行了签名。

../../../../_images/vbmeta.png

dm-verity 是 Device mapper 架构下的一种目标设备类型,通过它来保证设备或设备分区的完整性,典型架构如下图。dm-verity 类型的设置需要2个底层设备:

  • 一个是数据设备,顾名思义是用来存储数据,实际上就是要保证完整性的设备

  • 一个是哈希设备,用来存储哈希值,在校验数据设备完整性时需要。

图中映射设备和目标设备是一对一关系,对映射设备的读操作被映射成对目标设备的读操作,在目标设备中,dm-verity 又将读操作映射为数据设备(Data Device)的读操作。但是在读操作的结束处,dm-verity 加了一个额外的校验操作,对读到的数据计算一个 hash 值,用这个哈希值和存储在哈希设备(Hash Device)中的值做比较,如果不同,则本次读操作被标记为错误。

../../../../_images/dm-verity1.jpg

数据设备和哈希设备中每块大小均为4KB,使用 hash 算法 SHA256,即每块数据的哈希值为32B(256bit),则哈希设备中的每块(4KB)存储有4096/32=128个哈希值。所以在 layer0 中一个哈希设备的块对应数据设备的128个块。进一步,dm-verity 还要防备哈希设备中存储的哈希值被篡改的情况。所有要加上 layer1,在 layer1 中的每块数据对应 layer0 的128个块,layer1 中的数据就是对layer0 中的数据计算 hash 值,如果 layer1 中只有1块,那么就此停止,否则继续增加 layer,直到 layer n 只有一块。最后对 layer n 再计算 hash 值,称这个值为 roothash。而这个roothash 则通过签名认证的方式,防止被篡改,这样就确保了数据设备中的完整性。

../../../../_images/dm-verity2.jpg

AVB 和 dm-verity 验证流程

AVB 和 dm-verity 验证流程如下图

../../../../_images/avb-and-dmverity.png

  1. Uboot 读取 vbmeta 中的公钥,与 Uboot 源码中的公钥比对,确保公钥的合法性

  2. Uboot 使用公钥验证 vbmeta 的签名,确保 vbmeta 镜像的合法性

  3. Uboot 计算 boot 分区的哈希,与 vbmeta 中保存的哈希比较,确保 boot 分区的合法性

  4. Uboot 通过 cmdline 向 Kernel 传递 system 分区的 roothash

  5. Kernel 在读取 system 分区时,使用 dm-verity 机制验证 system 分区合法性

密钥管理

用户密钥的生成与烧录

X5 BSP 默认的私钥文件路径: build/tools/android_tools/avbtools/keys/shared.priv.pem ,签名校验算法使用的是 RSA2048,用户生成自己私钥文件后,替换原来私钥文件。

生成私钥可通过如下命令:

openssl genrsa -out test_kernel_priv.pem 2048  #生成私钥

生成私钥后,还需生成公钥,并放置在Uboot源码中。X5 BSP 提供工具key2array.py,用于生成 AVB 私钥对应的公钥数组,路径是 build/tools/android_tools/avbtools/key2array.py,使用方法是

cd build/tools/android_tools/avbtools/
python3 key2array.py -i keys/shared.priv.pem -o avb_root.c

参数说明:

  • -i: AVB私钥文件

  • -o: 对应生成的公钥数组文件

然后将 avb_root.c 中数据替换 Uboot 中 common/avb_verify.c中的 avb_root_pub[520] 数组即可。

软件版本防回滚

介绍

X5提供软件版本防回滚(antirollback)功能,当secure boot和antirollback功能都开启时,只能启动antirollback版本号高于/等于eFuse中储存的版本号的镜像,如果小于,则当前镜像会校验失败,启动失败。

X5 antirollback分为两个域,一个sec_antirollback域,版本号保存在eFuse的secure bank中,位宽64bit,专门用于BL2、DDR FW,BL31、optee等镜像的版本验证;另一个是nosec_antirollback域,版本号保存在eFuse的secure bank中,位宽64bit,用于BL2_CFG、Uboot、Vbmeta、Boot、System等镜像的版本验证。

../../../../_images/antirollback.png

对于sec_antirollback域内的镜像,antirollback版本保存在对应镜像的证书中,证书与镜像一一对应,启动时校验证书内的antirollback版本,如果证书内的版本小于eFuse中保存的sec_antirollback版本,则会启动失败。验证流程会分为两部分,一是bootrom加载BL2时的版本验证;二是BL2加载DDR FW、BL31、optee等镜像时的版本验证。

对于nosec_antirollback域内的镜像,BL2_CFG和Uboot的antirollback版本保存在证书中,Vbmeta/Boot/System等镜像antirollback版本保存在Vbmeta镜像中。根据AVB的特性,Vbmeta/Boot/System必须是同一次编译的产物,这个机制保证了Vbmeta/Boot/System等镜像可以共同使用Vbmeta镜像内的antirollback版本号。同样启动时,Uboot会读取Vbmeta分区和eFuse中的nosec_antirollback版本,如果Vbmeta内的版本小于eFuse中保存的版本,则会启动失败。验证流程会分为两部分,一是BL2加载BL2_CFG和Uboot时的版本验证;二是Uboot加载Vbmeta时的版本验证。

antirollback功能开启

如果开启secure boot功能,BL2、DDR FW,BL31、optee、BL2_CFG、Uboot等镜像的antirollback版本校验会自动开启。AVB框架中antirollback版本校验默认关闭,如需开启需要在Uboot中打开配置

CONFIG_X5_SUPPORT_CHECK_ROLLBACK=y

详细开启 antirollback 功能的方式可以参考 OTA 文档中的开启 antirollback 章节。

antirollback版本设置与更新

antirollback版本仅支持通过OTA升级的方式更新,具体请参考OTA章节

限定范围

  • sec和nosec的antirollback版本分别最多支持65个版本的迭代,版本0-64

  • sec_antirollback版本和nosec_antirollback版本独立管理,支持单独和同时更新。sec_antirollback版本功能仅适用于由客户key签名BL2等镜像的场景;由地瓜key签名BL2等镜像的场景只支持nosec_antirollback版本

  • antirollback版本只支持通过OTA升级的方式更新

  • 地瓜antirollback版本启动校验只负责到System,oem分区antirollback版本的启动校验由客户负责,地瓜提供从eFuse读取nosec_antorollback版本的接口(如果使用地瓜提供的oem dm-verity方案,则这一点忽略)

  • 同一个antirollback版本域中由于多个镜像共用一个antirollback版本,如果antirollback版本要更新,则对应域中的所有镜像必须要同时更新,否则下次启动时会antirollback校验失败

  • OTA升级时会做antirollback版本校验,如果镜像中的antirollback小于eFuse中的版本,则停止升级

  • 同一个域中的所有镜像的antirollback版本必须保持一致,如果不一致时,会报错退出,停止升级

开启 secure boot

通过上述章节,烧录密钥后,还需要以下步骤开启 secure boot :

  • 在 eFuse 中开启 secure boot

  • 在配置文件中设置 system 镜像的验证方式

配置 eFuse

以下仅针对于烧了地瓜KEY的芯片,如果是烧的是客户自己的KEY,请参考X5 Customer root rsa key hash烧录及使用。以下部分可以跳过

eFuse 的配置信息位于 BL2 CFG 文件中,文件路径为 device/horizon/x5/board_cfg/soc/bl2_cfg/bl2_cfg.json

其内容如下所示:

{
    "bl2_cfg": {
      "feature": {
        ...
      },
      "efuse_cfg": {
        "bypass": 0,
        "secure_boot": "true",
        "debug_disable": "true",
        "burn_user_rot_key": "false",
        "status_gpio" : {
            "gpio_sub": "hsio",
            "gpio_group": 1,
            "gpio_num": 1
        },
        "delay_before_efuse": 0,
        "delay_after_efuse": -1
      },
        ...
}

参数说明

  • bypass: 0 表示 disable bypass eFuse,将会烧写 eFuse

  • secure_boot:开启 secure boot,将会烧写 non-secure bank10 bit0

  • debug_disable: 禁止 debug port,将会烧写 non-secure bank10 bit1

  • power_gpio: eFuse 电源 IO 控制,默认使用 aon gpio0_7为 eFuse power GPIO,默认为低

  • status_gpio: eFuse 烧录指示IO,以上示例设置 hsio gpio1_1 为 eFuse status GPIO,默认为低

  • delay_before_efuse:eFuse 上电后到开始烧写的延迟时间(单位 ms),0 表示默认延迟 1s

  • delay_after_efuse: 烧写完成之后代码停止

有关 eFuse 开启 secure boot 与烧录的详细说明,请参考eFuse烧录

查看是否开启secure boot

可以通过sysfs接口查看当前芯片是否开启的secure boot,请参考查看boardinfo

  • 开启secure boot之后字段”sec_boot”对应是”enable”

  • 未开启secure boot字段”sec_boot”对应是”disable”

配置 system 镜像

根据项目修改配置文件,配置 system 镜像编译选项,开启 dm-verity, 以 device/horizon/x5/board_x5_evb_debug_config.mk 为例

# system 配置
# 指定根文件系统类型和预编译的文件系统路径
export HR_SYSTEM_TYPE="buildroot"
export HR_SYSTEM_DIR=${HR_TOP_DIR}/system/buildroot/prebuilt
# 根文件系统的分区名,需要和分区表配置对应
export HR_SYSTEM_PART_NAME="system"
# system verify method: dm-verity, crypt
export HR_SYSTEM_VERIFY="dm-verity"

注意

  • 如果不需要 secure boot,可在配置文件中将变量 HR_SYSTEM_VERIFY 去掉

  • 如果储存介质为 flash,比如 NAND 或 NOR,不支持开启 dm-verity,需在配置文件中将 HR_SYSTEM_VERIFY 去掉

oem分区开启dm-verity

分区表中添加oem分区

../../../../_images/oem-dm-part.png

  • 字段 dm_verity 必须配置为true

  • 当前仅支持对ext4分区实现dm-verity

添加挂载点

../../../../_images/oem-dm-mountpoint.png

oem分区将会挂载到/oem下

配置自动mount

../../../../_images/oem-dm-automount.png

  • hb-veritytab语法与fstab语法一致

  • /dem/mapper下的设备名默认为分区名

  • dm-verity分区必须以ro方式挂载

打包oem镜像

以下是打包oem镜像的sample

#!/bin/bash

set -e

################### setting utils_funcs ###################
SCRIPT_DIR="$( cd "$( dirname "$(readlink -f "${BASH_SOURCE[0]}")" )" && pwd )"
source "$SCRIPT_DIR/utils_funcs.sh"

HR_TOP_DIR=$(realpath "${SCRIPT_DIR}"/../)
export HR_TOP_DIR
export HR_LOCAL_DIR=${SCRIPT_DIR}

# check board config
check_board_config "${@:1}"

# 编译出来的镜像保存位置
mkdir -p "${HR_TARGET_BUILD_DIR}" "${HR_TARGET_PRODUCT_DIR}" "${HR_TARGET_DEPLOY_DIR}"

export OEM_DEPLOY_DIR=${HR_TARGET_DEPLOY_DIR}/oem
export PATH=${HR_BUILD_TOOL_PATH}:$PATH

function build_oem_pack()
{
    fs_type=$(get_part_attr oem fs_type)

    "${HR_PARTITION_TOOL_PATH}"/mk_avb_fs.sh oem "${OEM_DEPLOY_DIR}"
    "${HR_PARTITION_TOOL_PATH}"/pack_avb_img.sh fs oem "${fs_type}"
}

function build_all()
{
    # sample, prepare oem content
    mkdir -p "${OEM_DEPLOY_DIR}"
    echo "This test oem dm-verity" > "${OEM_DEPLOY_DIR}/profile"

    build_oem_pack
}

if [ $# -eq 0 ] || [ "$1" = "all" ]; then
    build_all
else
    echo "Invalid parameter"
fi

其中主要的实现是

function build_oem_pack()
{
    fs_type=$(get_part_attr oem fs_type)

    "${HR_PARTITION_TOOL_PATH}"/mk_avb_fs.sh oem "${OEM_DEPLOY_DIR}"
    "${HR_PARTITION_TOOL_PATH}"/pack_avb_img.sh fs oem "${fs_type}"
}
  • mk_avb_fs.sh脚本负责把源目录中的内容打包生成一个ext4镜像。第一个参数是”oem”,表示分区名;第二个参数是oem的源目录

  • pack_avb_img.sh负责生成hashtree,并将根hash等校验信息放到system的源目录中。第一个参数是”fs”,表示当前分区类型是文件系统;第二个参数是”oem”,表示分区名;第三个参数是文件系统类型。

因为oem的根hash放到了system镜像中,所以要先打包oem分区,然后再打包system分区,oem镜像的制作脚本的运行要在system镜像的制作之前

../../../../_images/oem-dm-build.png

oem分区升级注意

由以上的oem分区的dm-verity实现可知,oem分区的根hash将会放到system分区中,因此必须开启secure boot和system分区的dm-verity,才能保证oem分区信任链完整。

../../../../_images/oem-dm-flow.png

为了安全性考虑,在boot/system/oem等分区的校验信息中都了salt,每次编译产生的校验信息都不一致,因此对于开启secure boot之后的镜像升级,必须保证vbmeta/boot/system/oem等镜像是在同一次编译中产生,且必须同时更新vbmeta/boot/system/oem等镜像,不能单独编译或者烧录其中的某一个镜像,否则就有可能校验失败。

验证 secure boot

上述步骤完成之后,重新编译镜像,烧写到板端重启后,将在BL2中完成对 key hash 的烧写,启动后在 BL2 中将会有如下 log

../../../../_images/log-bl2-cfg.jpg

../../../../_images/log-bl2-uboot.jpg

在Uboot中将会有如下log

## Android Verified Boot 2.0 version 1.1.0
Verification passed successfully