3、信任链的建立:从RoT到Bootloader再到OS的完整信任传递过程

好,咱们接着聊。上一章我们把可信根(RoT)是什么讲清楚了——它就是那个不可篡改的信任起点。但光有一个根没用,你得让这个根能“生长”出信任来,对吧?

说白了,安全启动的核心就是一场接力赛。RoT是第一棒,Bootloader是第二棒,OS是第三棒。每一棒都要验证下一棒的身份和完整性。只要有一棒出了问题,整个系统就不可信了。

我当年做第一个安全启动项目时,犯过一个低级错误——只验证了Bootloader的签名,没验证它要加载的配置分区。结果呢?攻击者篡改了配置,系统虽然启动了,但跑在了攻击者设定的参数上。嗯,从那以后,我养成了一个习惯:信任链上的每一个环节,哪怕是一个字节,都不能放过。

3.1 信任链的总体架构

先看一张图,把整个流程串起来。这张图我画了很多遍,每次给团队新人讲安全启动,我都会先让他们看这个。

信任链建立流程 信任链建立流程 阶段1:RoT Boot ROM / eFuse 阶段2:Bootloader SPL / U-Boot 阶段3:OS Linux Kernel App 每一阶段在移交控制权前,必须验证下一阶段的签名和完整性 详细验证流程 RoT 验证 Bootloader 公钥哈希在eFuse中 Bootloader 验证 OS 验证内核签名 OS 验证应用 模块/驱动签名 关键要点: 1. 信任链不能中断——任何跳过验证的环节都是安全漏洞 2. 每一级只信任上一级——没有跨级信任 3. 公钥存储必须硬件保护——eFuse或OTP区域 4. 验证失败必须停止启动——不能降级运行

你看,整个链条是单向的、不可逆的。RoT验证Bootloader,Bootloader验证OS,OS再验证应用模块。每一级都只信任上一级,没有跨级信任。为什么?因为跨级信任会破坏链式结构,给攻击者留下跳板。

3.2 第一棒:RoT如何验证Bootloader

这是整个信任链最关键的一步。RoT通常是芯片内部的Boot ROM,它出厂就固化在硅片上,物理上无法修改。我习惯把它叫做“硅基信任”。

RoT验证Bootloader的过程,我拆成四个步骤来讲:

  1. 加载Bootloader镜像:从Flash中读取Bootloader到SRAM
  2. 验证签名:用eFuse中的公钥哈希验证Bootloader的数字签名
  3. 验证完整性:计算镜像的哈希值,与签名中的哈希比对
  4. 移交控制权:验证通过后,跳转到Bootloader入口

核心原则:RoT只做验证,不做执行。它验证通过后就把控制权交出去,自己不再参与后续流程。这样可以减少RoT的代码量,降低出bug的概率。

这里有个细节很多人会忽略——公钥的存储。我见过一个项目,把公钥直接放在Flash里,RoT从Flash读取公钥去验证Bootloader。你想想看,攻击者把Flash里的公钥换成自己的,那整个信任链就崩塌了。正确的做法是把公钥哈希烧进eFuse,RoT用这个哈希去验证公钥的合法性。

3.3 第二棒:Bootloader验证OS

Bootloader拿到控制权后,它的任务就是加载并验证操作系统。这里分两种情况:单阶段Bootloader和多阶段Bootloader。

类型 特点 验证方式 典型场景
单阶段 一个Bootloader完成所有工作 直接验证OS签名 简单嵌入式系统
多阶段 SPL→U-Boot→OS 逐级验证 复杂SoC系统

多阶段Bootloader的信任传递是这样的:

  • RoT验证SPL(第一阶段Bootloader)
  • SPL验证U-Boot(第二阶段Bootloader)
  • U-Boot验证OS内核

为什么要搞这么复杂?因为RoT能访问的资源有限——通常只有SRAM,没有DDR。SPL负责初始化DDR,然后加载更大的U-Boot。U-Boot再加载OS。每一级都在扩展能力,同时也在传递信任。

我的经验:多阶段Bootloader的验证链容易出问题的地方是SPL到U-Boot的过渡。SPL验证U-Boot时,要确保验证的是完整的U-Boot镜像,包括它的配置和DTB。我曾经遇到一个案例,SPL只验证了U-Boot的代码段,没验证DTB,结果攻击者篡改DTB后系统照样启动了。

3.4 第三棒:OS验证应用模块

OS内核启动后,信任链并没有结束。内核还要验证它加载的模块、驱动、文件系统。这一步很多人会忽略,觉得内核都起来了,后面应该安全了吧?其实不然。

我参与过一个车载信息娱乐系统的项目。攻击者无法篡改内核,但通过修改一个内核模块的.so文件,成功注入了恶意代码。为什么?因为内核加载模块时没有验证签名。嗯,从那以后,我坚持所有内核模块都必须签名验证。

OS层面的验证通常包括:

  • 内核模块签名验证:modprobe时检查模块签名
  • 文件系统完整性:dm-verity或IMA机制
  • 关键配置验证:/etc下的敏感文件
  • 动态加载库验证:dlopen时的签名检查

注意:OS层面的验证不能依赖用户态程序,因为用户态可能已经被篡改。验证逻辑必须在内核态完成,或者使用硬件辅助验证(如TEE)。

3.5 信任链的常见断裂点

做了这么多年安全启动,我总结了几处最容易出问题的地方:

断裂点 问题描述 后果 修复方案
跳过RoT验证 通过JTAG/SWD绕过Boot ROM 直接加载恶意Bootloader 禁用调试接口,或设置访问权限
公钥被替换 Flash中的公钥被篡改 攻击者用自己的密钥签名 公钥哈希锁在eFuse中
验证不完整 只验证了部分镜像 未验证部分被篡改 验证整个镜像,包括元数据
降级攻击 加载旧版本的有漏洞镜像 利用已知漏洞 版本号检查,禁止回滚
验证失败后降级 签名错误但系统继续启动 信任链形同虚设 验证失败必须停止启动

我曾经在一个客户现场排查问题,发现他们的设备明明有安全启动,但还是被攻破了。查了半天,原因是Bootloader验证OS失败后,没有停止启动,而是打印了一条警告就继续了。你想想看,这跟没验证有什么区别?

3.6 信任链的代码实现示例

光说不练假把式。我写一个简化的信任链验证流程,用伪代码展示核心逻辑:

// RoT阶段:验证Bootloader
void boot_rom_main() {
    // 1. 从eFuse读取公钥哈希
    uint8_t expected_hash[32];
    efuse_read(EFUSE_PUBKEY_HASH, expected_hash, 32);
    
    // 2. 从Flash读取Bootloader镜像和签名
    uint8_t* bl_image = flash_read(BOOTLOADER_ADDR);
    uint8_t* bl_signature = flash_read(BOOTLOADER_SIG_ADDR);
    uint8_t* bl_pubkey = flash_read(BOOTLOADER_PUBKEY_ADDR);
    
    // 3. 验证公钥合法性
    uint8_t pubkey_hash[32];
    sha256(bl_pubkey, pubkey_hash);
    if (memcmp(pubkey_hash, expected_hash, 32) != 0) {
        halt_system("Public key mismatch!");
    }
    
    // 4. 验证Bootloader签名
    if (!rsa_verify(bl_image, bl_signature, bl_pubkey)) {
        halt_system("Bootloader signature invalid!");
    }
    
    // 5. 验证通过,跳转到Bootloader
    jump_to(bl_image);
}

// Bootloader阶段:验证OS内核
void bootloader_main() {
    // 1. 从Flash读取内核镜像和签名
    uint8_t* kernel = flash_read(KERNEL_ADDR);
    uint8_t* kernel_sig = flash_read(KERNEL_SIG_ADDR);
    
    // 2. 使用内置公钥验证内核签名
    if (!rsa_verify(kernel, kernel_sig, g_bootloader_pubkey)) {
        halt_system("Kernel signature invalid!");
    }
    
    // 3. 验证内核完整性(防止部分篡改)
    uint8_t kernel_hash[32];
    sha256(kernel, kernel_hash);
    if (!verify_hash_with_signature(kernel_hash, kernel_sig)) {
        halt_system("Kernel integrity check failed!");
    }
    
    // 4. 验证通过,启动内核
    boot_kernel(kernel);
}

这段代码看起来简单,但实际项目中要考虑的细节很多。比如:

  • 签名算法用什么?RSA还是ECDSA?我建议用ECDSA,密钥更短,验证更快
  • 哈希算法用什么?SHA256是底线,别用MD5或SHA1
  • 公钥怎么存?eFuse空间有限,通常只存哈希,不存完整公钥
  • 验证失败怎么处理?必须halt,不能有任何降级路径

一句话总结:信任链的建立,本质上就是“验证→移交→再验证→再移交”的循环。每一环都不可跳过,每一环都不可降级。只有这样,才能从RoT这个信任原点,构建出整个系统的可信运行环境。