
# X5 安全启动概述

**以下仅针对于烧了地瓜 KEY 的芯片，如果是烧的是客户自己的 KEY，请参考[X5 Customer root rsa key hash 烧录及使用](../efuse/cus_key_hash_update.html)**

![](./_static/_images/secure_boot/secure-boot-flow.png)

启动流程如上图所示

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

# <span id="Uboot_BL2_CFG_verify"/> U-Boot & BL2 CFG 验证

**以下仅针对于烧了地瓜 KEY 的芯片，如果是烧的是客户自己的 KEY，请参考[X5 Customer root rsa key hash 烧录及使用](../efuse/cus_key_hash_update.html)。以下部分可以跳过**

## 流程概述

### PC 端签名流程

![](./_static/_images/secure_boot/sign-uboot.png)

在 PC 端签名 U-Boot 的流程如上图所示，主要包括以下两个步骤

- 对 U-Boot image 签名
- 对 BL2 CFG 镜像签名

### 板端验证流程

![](./_static/_images/secure_boot/verify-uboot.png)

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

- 从镜像中读取公钥，计算公钥 hash 并与 eFuse 中的公钥 hash 比较，确保公钥的合法性
- 验证 BL2 CFG 镜像
- 验证 U-Boot image

## <span id="Uboot_Key_management"/> U-Boot 下密钥管理

### U-Boot 和 BL2 CFG 密钥的生成与烧录

U-Boot 和 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 烧录](../efuse/efuse_update.html)


# <span id="boot-system-verify"/> Kernel & rootfs 验证

## AVB 和 dm-verity 概述

U-Boot 验证 Kernel，基于 AVB（Android Verified Boot）实现，结合 Linux Kernel 的 DM-verity 机制，保证根文件系统的完整性。

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

![](./_static/_images/secure_boot/vbmeta.png)

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

- 一个是数据设备，顾名思义是用来存储数据，实际上就是要保证完整性的设备
- 一个是哈希设备，用来存储哈希值，在校验数据设备完整性时需要。

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

![](./_static/_images/secure_boot/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 则通过签名认证的方式，防止被篡改，这样就确保了数据设备中的完整性。

![](./_static/_images/secure_boot/dm-verity2.jpg)

## AVB 和 dm-verity 验证流程

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

![](./_static/_images/secure_boot/avb-and-dmverity.png)

1. U-Boot 读取 vbmeta 中的公钥，与 U-Boot 源码中的公钥比对，确保公钥的合法性
2. U-Boot 使用公钥验证 vbmeta 的签名，确保 vbmeta 镜像的合法性
3. U-Boot 计算 boot 分区的哈希，与 vbmeta 中保存的哈希比较，确保 boot 分区的合法性
4. U-Boot 通过 cmdline 向 Kernel 传递 system 分区的 roothash
5. Kernel 在读取 system 分区时，使用 dm-verity 机制验证 system 分区合法性

## Kernel 下密钥管理

### 用户密钥的生成与烧录

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

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

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

生成私钥后，还需生成公钥，并放置在 U-Boot 源码中。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` 中数据替换 U-Boot 中 `common/avb_verify.c`中的 `avb_root_pub[520]` 数组即可。

<span id="antirollback-main"/> </span>

# 软件版本防回滚

## 介绍

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、U-Boot、Vbmeta、Boot、System 等镜像的版本验证。

![antirollback](./_static/_images/secure_boot/antirollback.png)

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

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

## antirollback 功能开启

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

```shell
CONFIG_X5_SUPPORT_CHECK_ROLLBACK=y
```

详细开启 antirollback 功能的方式可以参考 OTA 文档中的[开启 antirollback](../../ota/1-ota.html#span-id-enable-antirollback-antirollback) 章节。

## antirollback 版本设置与更新

antirollback 版本仅支持通过 OTA 升级的方式更新，具体请参考[OTA 章节](../../ota/1-ota.html)

## 限定范围

- 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/cus_key_hash_update.html)。以下部分可以跳过**

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 烧录](../efuse/efuse_update.html)

## 查看是否开启 secure boot

可以通过 sysfs 接口查看当前芯片是否开启的 secure boot，请参考[查看 boardinfo](../../../driver_develop_guide/44-boardinfo.html)

- 开启 secure boot 之后字段"sec_boot"对应是"enable"
- 未开启 secure boot 字段"sec_boot"对应是"disable"

## 配置 system 镜像

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

```shell
# 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 分区

![oem-dm-part](./_static/_images/secure_boot/oem-dm-part.png)

- 字段 `dm_verity` 必须配置为 true
- 当前仅支持对 ext4分区实现 dm-verity

### 添加挂载点

![oem-dm-mountpoint](./_static/_images/secure_boot/oem-dm-mountpoint.png)

oem 分区将会挂载到/oem 下

### 配置自动 mount

![oem-dm-automount](./_static/_images/secure_boot/oem-dm-automount.png)

- hb-veritytab 语法与 fstab 语法一致
- /dem/mapper 下的设备名默认为分区名
- dm-verity 分区必须以 ro 方式挂载

### 打包 oem 镜像

以下是打包 oem 镜像的 sample

```bash
#!/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
```

其中主要的实现是

```bash
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 镜像的制作之前

![](./_static/_images/secure_boot/oem-dm-build.png)

### oem 分区升级注意

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

![](./_static/_images/secure_boot/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

![](./_static/_images/secure_boot/log-bl2-cfg.jpg)

![](./_static/_images/secure_boot/log-bl2-uboot.jpg)

在 U-Boot 中将会有如下 log

```
## Android Verified Boot 2.0 version 1.1.0
Verification passed successfully
```
