安全启动

概述

安全启动旨在保护固件,防止攻击者恶意修改或替换固件。当芯片通电时,安全启动 ROM 会执行以检查固件签名的有效性。

如果签名有效,则认证会成功,这意味着固件是安全的,可以继续后续操作。否则,SoC 将清除栈并进入无限循环。

本章说明了 Linux 验证启动的使用方法。

ROM 启动机制

Linux 安全启动应从 ROM 代码开始,该代码使用保存在 OTP 中的信任根密钥(RoT)验证引导加载程序。引导加载程序应验证固件中的 KM0/KM4 固件和 cert.bin 。除 RoT 外,所有密钥都保存在 cert.bin (密钥证书)中。

../../_images/secure_boot_flow.svg

安全启动流程

链式验证

Linux 安全启动基于 ROM/KM0/KM4 安全启动。安全启动流程如下图所示:

../../_images/verified_boot_sw_arch.svg

备注

通常情况下,内核命令行将是 Kernel command line: console=ttyS0,1500000 earlycon psci=enable ubi.mtd=8 ubi.block=0,0 ubi.mtd=9 dm-mod.create="system,,0,ro, 0 55928 verity 1 /dev/ubiblock0_0 /dev/ubiblock0_0 4096 4096 6991 6991 sha512 <salt> <hash> 2 ignore_corruption ignore_zero_blocks" root=/dev/dm-0 rootfstype=squashfs

内核初始化时会显示命令行,其内容由 <sdk>/sources/boot/uboot/cmd/realtek_avb.c 提供。

函数 realtek_organize_cmdline() 将提取关键信息并组织一个新的命令行用于安全启动。

  • ubi.mtd=8 表示 mtd 中 rootfs 块的索引,它用于将 mtd8 设为字符设备 ubi0。

  • ubi.block=0,0 表示将字符设备 ubi0 设为块设备 ubiblock0,因为 dm verity 只能对块设备进行操作。

  • dm-mod.create 参数是由 dm-verity 驱动程序定义的。这些参数包括要验证的块设备、哈希块的大小、哈希块的数量、rootfs 哈希树的起始地址、验证所需的盐值和摘要等。摘要是指在 uboot 中已经被 VBmeta 验证过的 rootfs 的根哈希值。

  • ubi.block0_0 被附加到 dm-0 上。 root=/dev/dm-0 将 dm-0 挂载为根文件系统,这也意味着将 mtd8 区域的信息设为 rootfs。当用户访问 rootfs 中的某些块时,dm-verity 会对任何块及其相关块进行动态哈希计算。

有关 dm-verity 的更多细节,请参考 dm verify

本节介绍构造参数、操作原理、哈希树、磁盘格式以及一些示例。内核命令行是 rootfs 哈希树验证的唯一起点,哈希树的根哈希摘要将被插入 VBmeta,其有效性已通过之前的流程确认。

上述框架的代码可以分为三个部分: Flash 区域、 U-Boot 区域和 kernel 区域。下表仅列出每个部分的核心代码目录。

区域

路径

介绍

Flash

<sdk>/sources/yocto/meta-realtek/tools/verified_boot

制作安全相关固件的脚本

U-Boot

<uboot>/cmd/mtd.c

读取 NAND 闪存的操作步骤

<uboot>/cmd/realtek-avb.c

vbmeta/公钥/内核/设备树的验证

Kernel

<linux>/drivers/md/*

此目录由名为 dm-verity 的 GPI Linux 内核提供,用于验证 rootfs 的哈希树

更多关于安卓验证启动的信息,请参考 android avb

使用说明

下列步骤列出了如何使用 Linux 安全启动。

  1. 启用 uboot 安全启动配置

  2. 准备安全启动密钥

  3. 编译安全固件

  4. 启用安全启动

启用 uboot 安全启动配置

要启用 Linux 安全启动,需要确保已打开与 U-Boot 相关的安全启动配置。

位于 <sdk>/sources/boot/uboot/configs 中的 U-Boot 配置以及与安全启动相关的配置如下:

CONFIG_VERIFIED_BOOT=y
CONFIG_OTP_RTK_AMEBA=y

当前,我们已默认启用了这些配置。

准备安全启动密钥

要使用 Linux 验证启动,必须准备安全密钥。

默认的清单/密钥文件位于 <sdk>/sources/yocto/meta-realtek/tools/verified_boot/security_keys/test 。默认秘钥可在实验阶段直接使用,量产时必须生成自己的安全秘钥。

Linux SDK 提供了工具 make_key ,位于 <sdk>/sources/yocto/meta-realtek/tools/verified_boot 文件夹中,供用户在特定密钥路径下生成自己的安全密钥。

使用以下命令生成安全密钥,生成后的密钥将位于 <key path> 路径:

make_key <key path>

安全密钥如下表所示:

文件

描述

manifest.json5

用于记录所有加密启动相关的秘钥信息

vbmeta.priv.key

用于 Linux vbmeta 安全固件的私钥信息

vbmeta.pub.key

用于 Linux vbmeta 安全固件的公钥信息

soc_info.json

SOC 信息

编译安全固件

在制作安全固件之前,所有原始固件都必须已经生成结束。

Yocto SDK 使用 mksecure.sh 脚本编译安全固件,该脚本位于 <sdk>/sources/yocto/meta-realtek/tools/verified_boot 。该脚本的用法说明如下:

mksecure.sh
--key_dir=<secure key path>
--output_dir=<secure image output path>
--input_dir=<directory of the normal images>
--boot_image=<boot image path>
--dtb_image=<device tree blob image path>
--dtb_part_size=<tree blob image partition size>
--kernel_image=<kernel image path>
--kernel_part_size=<kernel partition size>
--recovery_dtb_image=<recovery device tree blob image path>
--recovery_dtb_part_size=<recovery device tree  blob partition size>
--recovery_kernel_image=<recovery kernel image path>
--recovery_kernel_part_size=<recovery kernel partition size>
--root_image=<rootfs image path>
--root_part_size=<rootfs partition size>
--km4_boot=<firmware boot image path>
--km4_app=<firmware app image path>
--imgtool_flashloader=<imgtool flashloader path>
--use_dtb_size =<enable the auto parse of image size>

其参数如下表所列:

参数

必选/可选?

描述

key_dir

必选

表示安全密钥的位置,默认在 <sdk>/sources/yocto/meta-realtek/tools/verified_boot/security_keys/test

output_dir

必选

表示安全固件的输出路径。

input_dir

可选

表示普通固件的目录。该目录中的固件将用作安全固件的来源。

  • 如果指定了 input_dir,则可以省略 boot_image、kernel_image、recovery_kernel_image、root_image、km4_boot、km4_app 和 imgtool_flashloader。mksecure.sh 将默认使用指定 input_dir 中的 boot.img、uImage、<va7, va8>.rootfs.squashfs、km4_boot_all.bin、km0_km4_app.bin、imgtool_flashloader.bin 和 uImage-initramfs-rtl8730elh-recovery.bin。

  • 如果某些固件是通过其他输入参数指定的,mksecure.sh 将优先使用指定的路径。

  • 如果未指定 input_dir,则必须分别提供所有固件路径。

dtb_image

必选

表示非安全设备树 blob 固件的路径。

recovery_dtb_image

当开启 recovery 时必选

表示恢复设备树 blob 固件的路径,如果需要刷入非安全恢复固件,则此参数不是必需的。

boot_image

当存在 input_dir 时可选

表示非安全启动固件的路径。

kernel_image

当存在 input_dir 时可选

表示非安全内核固件的路径。

recovery_kernel_image

当存在 input_dir 时可选

表示非安全恢复内核固件的路径,如果需要刷入非安全恢复固件,则此参数不是必需的。

root_image

当存在 input_dir 时可选

表示非安全 rootfs squashfs 固件的路径,并且只需要 squashfs 固件。

km4_boot

当存在 input_dir 时可选

表示非安全 firmware 启动固件的路径。

km4_app

当存在 input_dir 时可选

表示非安全 firmware 应用固件的路径。

imgtool_flashloader

当存在 input_dir 时可选

表示文件 imgtool_flashloader.bin 的路径。

use_dtb_size

可选, 另名为 -s

设置为 1 以启用自动设备树解析。mksecure.mk 将根据选定的 dtb_image 解析大小信息。

  • 如果启用了 -s,则可以省略 dtb_part_size、kernel_part_size、recovery_dtb_part_size、recovery_kernel_part_size 和 root_part_size 的大小信息。

  • 然而,请确保在 dts 中的布局描述中包含 Device Tree BlobKernel ImageRootfs Image 的关键名称, 字母的大小写并不重要。

dtb_part_size

只在 -s 未设置时有效

表示在 <dts_dir>/rtl8730e-spi-nand-256m.dtsi (rtl8730e-spi-nand-128m.dtsi) 中描述的设备树blob分区的分区大小。

kernel_part_size

只在 -s 未设置时有效

表示在 rtl8730e-spi-nand-256m.dtsi (rtl8730e-spi-nand-128m.dtsi) 中描述的内核分区的分区大小。

recovery_dtb_part_size

只在 -s 未设置时有效

表示在 rtl8730e-spi-nand-256m.dtsi (rtl8730e-spi-nand-128m.dtsi) 中描述的恢复设备树blob分区的大小,如果需要刷入非安全恢复映像,
则此参数不是必需的。

recovery_kernel_part_size

只在 -s 未设置时有效

表示在 rtl8730e-spi-nand-256m.dtsi (rtl8730e-spi-nand-128m.dtsi) 中描述的恢复内核分区的大小,如果需要刷入非安全恢复映像,
则此参数不是必需的。

root_part_size

表示在 rtl8730e-spi-nand-256m.dtsi (rtl8730e-spi-nand-128m.dtsi) 中描述的根文件系统(rootfs)分区的大小。

其中 <dts_dir> 表示设备树文件的目录。

  • 对于内核 5.4.x,该目录是 <sdk>/kernel/linux-5.4/arch/arm/boot/dts

  • 对于内核 6.6.x,该目录是 <sdk>/kernel/linux-6.6/arch/arm/boot/dts/realtek/ameba

启用安全启动

在刷写安全固件并启动系统后,通过编程 OTP 位来启用安全启动。

示例中的具体秘钥信息均为 Linux SDK 中自带的默认秘钥。

  1. 确认 准备安全启动密钥 中描述的秘钥信息已正确用于编译安全固件,并向 OTP 中写入该路径下的秘钥信息。

  2. 在 OTP 中编程安全启动根公钥哈希。

    根公钥哈希值存放在 准备安全启动密钥 中描述的 manifest.json5 文件中。找到文件中 image1sboot_public_key_hash 字段。

    image1: {
      img_id: 0,
      img_ver_major: 1,
      img_ver_minor: 1,
      huk_epoch: 1,
      rsip_iv: "0102030405060708",
      sboot_private_key: "A3508C1155602F2C7B5DAC524868A4667B63D2097482786838EFD1046E31404E",
      sboot_public_key: "63C64A234E15B11E7C4A9227F151C640637AEF0699774DCAA7D38BB565172896",
      sboot_public_key_hash: "B7A307DCE8C5967983D55A9E3EB0E0617C48775383753A6D26EA6E13B1187BD3",
    },
    

    使用以下命令编程 OTP PK1:

    efuse wraw 320 32 B7A307DCE8C5967983D55A9E3EB0E0617C48775383753A6D26EA6E13B1187BD3
    
  3. 在 OTP 中启用安全启动。

    1. 使用 efuse rmap 检查 0x2 和 0x3 中的值:

      $efuse rmap 0 16
      
      ../../_images/check_bit.png

      比如,我们可以看到地址 0x2 0x3 的值分别为 0x10 0xa0。

    2. 将 0x3[2] 设置为 1,启用安全启动使能位。以 0x10 0xa0 为例,我们会需要向地址 0x2 0x3 写入 0x10 0xa4。

      $efuse wmap 2 2 10a4
      

      再次使用 efuse rmap 检查安全启动位是否成功设置。

      ../../_images/enable_bit.png
  4. 复位开发板。

    当安全启动成功时,可以看到以下日志:

    • IMG1 SBOOT EN: secure boot is enabled

    • IMG1(OTA1) VALID, ret: 0: bootloader authentication pass

    • IMG2 VERIFY PASS: IMAGE2 authentication pass

    • AP BL1&FIP VERIFY PASS: AP uboot authentication pass

    • Public Key Hash Verified Success

    • Rollback Index: Version PASS!: rollback authentication pass

    • VbMeta Signature Verified Success!: vbmeta authentication pass

    • Kernel Image verified success!: linux kernel authentication pass

    • DTB/FDT Image verified success!: Linux DTB authentication pass

    • linux verified boot: success!