3. TEE安全启动链:信任根、安全启动与回滚保护

安全启动链,说白了就是TEE的「出生证明」和「成长记录」。我刚开始接触TEE时,总觉得这东西离应用层很远。直到有一次,我在项目中遇到一台设备被刷入了篡改过的固件,整个安全域直接沦陷。嗯,从那以后,我再也不敢轻视启动链的任何一个环节了。

这一章,我们就把TEE安全启动链的四个核心模块拆开来看:信任根怎么建立、启动流程怎么走、镜像怎么签名验证、回滚怎么防。你想想看,如果连启动阶段都不安全,后面做的加密传输、证书配置,那都是空中楼阁。

3.1 信任根(RoT):一切安全的起点

信任根,英文叫Root of Trust,简称RoT。它是整个安全体系的「第一块砖」。这块砖必须是硬件级别的,不可篡改的。我个人习惯把RoT比作「保险箱的物理钥匙」——你软件层做得再花哨,钥匙本身被人复制了,一切白搭。

信任根的核心要求:

  • 不可变性:出厂后无法通过软件修改
  • 唯一性:每颗芯片的RoT都不同(或至少每批次不同)
  • 隔离性:普通软件无法直接读取RoT内容

常见的RoT实现方式有几种:

实现方式 载体 特点 我见过的坑
OTP熔丝 芯片内部一次性可编程存储 成本低,写入后不可改 熔丝烧录时电压不稳,导致部分位未烧透
eFuse 电熔丝阵列 可多次少量编程,密度高 某厂商eFuse读接口没加保护,被侧信道攻击
PUF 芯片物理不可克隆函数 每次上电生成唯一指纹,无存储 PUF在温度变化时误码率飙升,需要纠错
安全元件 独立安全芯片 隔离性最强,但成本高 通信接口没加密,中间人窃取密钥

我个人比较推荐eFuse + PUF的组合方案。eFuse存根密钥,PUF生成会话密钥。这样即使eFuse被物理探针读取,攻击者也拿不到完整的密钥材料。

避坑指南: 我曾经遇到一个项目,RoT密钥直接用明文存在Flash里。理由是「反正量产时再烧录,没人能读到」。结果样机流出后,不到一周就被提取了所有密钥。记住:RoT必须硬件隔离,不能依赖「没人能读到」这种假设。

3.2 安全启动流程:逐级验证的链条

安全启动,本质上是一个「链式信任」的过程。从RoT开始,每一级都验证下一级的签名,验证通过才执行。如果任何一级验证失败,启动立即终止。

为什么会这样设计?你想想看,如果让Boot ROM直接加载整个操作系统,那Boot ROM得有多大?而且一旦OS有漏洞,整个安全体系就崩了。所以,我们把它拆成多个阶段,每个阶段只做一件事,只信任上一级。

典型的TEE安全启动流程如下:

  1. 阶段0(Boot ROM):芯片上电,CPU从ROM中执行第一条指令。这段代码是掩膜在芯片里的,不可修改。它负责加载并验证下一级Bootloader的签名。
  2. 阶段1(Bootloader 1):验证通过后,Boot ROM将控制权交给Bootloader 1。Bootloader 1通常负责初始化DDR、时钟等基础硬件,然后验证Bootloader 2。
  3. 阶段2(Bootloader 2):Bootloader 2加载TEE内核镜像,验证其签名。验证通过后,将TEE内核加载到安全内存中。
  4. 阶段3(TEE内核):TEE内核启动,初始化安全世界的中断、内存管理、加密服务等。然后加载并验证TEE应用(Trusted Applications)。
  5. 阶段4(REE侧):TEE启动完成后,REE(普通世界)才开始启动。REE的启动过程同样需要验证,但它的信任链可以追溯到TEE。

下面这张图展示了完整的信任链传递过程:

TEE安全启动信任链 TEE安全启动信任链 阶段0:Boot ROM 验证签名 阶段1:Bootloader 1 验证签名 阶段2:Bootloader 2 验证签名 阶段3:TEE内核 验证签名 阶段4:TEE应用 任何一级验证失败 → 启动终止 → 进入安全错误处理状态

注意: 安全启动不是「越快越好」。我见过有些厂商为了缩短启动时间,跳过了某些阶段的验证。结果呢?攻击者只需要篡改Bootloader 1,就能绕过后续所有验证。每个阶段都必须验证,这是底线。

3.3 镜像签名与验证:数字签名的实战

镜像签名,说白了就是给固件打个「防伪标签」。这个标签用私钥生成,公钥验证。私钥只有厂商持有,公钥可以公开。这样,任何人都能验证固件是否来自厂商,是否被篡改过。

签名流程通常如下:

  1. 计算哈希:对镜像文件计算SHA-256(或更安全的SHA-384/512)哈希值。
  2. 签名:用厂商私钥对哈希值进行RSA或ECDSA签名。
  3. 打包:将签名、公钥证书(或公钥哈希)、镜像文件打包成一个固件包。

验证流程则是反过来:

  1. 提取签名和公钥:从固件包中提取签名和公钥信息。
  2. 验证公钥:用RoT中存储的根公钥验证当前公钥的合法性(如果是证书链,则逐级验证)。
  3. 计算哈希:对镜像文件计算哈希值。
  4. 验签:用公钥验证签名是否匹配。

下面是一个简化的签名验证代码示例(伪代码):

// 签名过程(在安全的生产环境中执行)
function sign_image(image_data, private_key):
    hash = SHA256(image_data)
    signature = RSA_sign(private_key, hash)
    return package(image_data, signature, public_key_cert)

// 验证过程(在设备启动时执行)
function verify_image(package_data, root_public_key):
    image_data, signature, cert = unpack(package_data)
    
    // 验证证书链
    if not verify_cert_chain(cert, root_public_key):
        return FAIL
    
    // 提取公钥
    image_public_key = extract_public_key(cert)
    
    // 计算哈希并验签
    hash = SHA256(image_data)
    if not RSA_verify(image_public_key, signature, hash):
        return FAIL
    
    return SUCCESS

经验之谈: 我曾经在项目中遇到一个「签名验证通过但镜像被篡改」的诡异问题。查了两天才发现,是签名算法使用了PKCS#1 v1.5填充,攻击者利用填充漏洞伪造了签名。从那以后,我坚持使用PSS填充模式。记住:算法选型很重要,别用已经被攻破的旧标准。

3.4 回滚保护机制:防降级攻击

回滚攻击,也叫降级攻击。攻击者把设备上的固件版本降级到有漏洞的旧版本,然后利用旧版本的漏洞攻入系统。你想想看,如果安全启动只验证签名不验证版本,那攻击者完全可以拿一个有签名的旧版固件刷进去。

回滚保护的常见实现方式:

方案 原理 优点 缺点
版本计数器 在RoT中维护一个单调递增的计数器 实现简单,硬件开销小 计数器耗尽后无法升级
版本号+签名 版本号作为签名内容的一部分 灵活,可回退到指定版本 需要维护版本号列表
安全存储+版本表 在安全存储中记录已安装的版本列表 支持多版本管理 安全存储空间有限

我个人比较推荐「版本计数器 + 版本号签名」的组合方案。计数器防止无限降级,版本号签名确保每个版本都是厂商授权的。具体做法是:

  • 在eFuse中维护一个4位的版本计数器(支持16次升级)
  • 每次升级时,新固件的版本号必须大于等于当前计数器的值
  • 版本号作为签名内容的一部分,防止篡改
  • 计数器只能递增,不能递减

血的教训: 我曾经参与过一个项目,回滚保护只检查了版本号,没检查版本号的签名。结果攻击者把版本号改成「9999」,然后刷入一个旧版固件。系统一看版本号比当前大,直接放行。嗯,那周的加班餐全是泡面味。

3.5 本章小结

安全启动链是TEE安全的基石。信任根提供不可篡改的起点,逐级验证确保每一级都可信,签名验证防止镜像被篡改,回滚保护防止降级攻击。这四个环节缺一不可。

在实际项目中,我建议你从「最坏情况」出发来设计启动链:假设攻击者已经物理接触设备,假设所有外部存储都不可信,假设网络通信被完全监听。只有在这种假设下还能保证安全的方案,才是真正可靠的方案。

核心要点回顾:

  • 信任根必须硬件实现,不可软件修改
  • 安全启动是链式验证,每一级都不能跳过
  • 签名算法选择PSS填充,避免PKCS#1 v1.5
  • 回滚保护必须结合版本计数和签名验证
  • 所有验证失败必须终止启动,不能降级处理