3. TEE安全启动链:信任根、BootROM与启动校验

安全启动链,说白了就是给TEE装上一把「从娘胎里就带出来的锁」。我做了这么多年安全固件,最深的体会就是:如果第一级信任就塌了,后面全是白搭。这一章咱们就聊聊,信任根怎么种下去,BootROM怎么把信任一级级传下去,以及怎么防住回滚攻击这种老套路。

3.1 信任根(RoT)—— 一切信任的起点

信任根是什么?它是一段不可篡改的代码或硬件逻辑,通常固化在芯片内部。我习惯把它比作「芯片的出生证明」—— 你没法伪造,也没法修改。

核心原则:信任根必须是物理不可变的。一旦芯片出厂,RoT的内容就不能再被改写。这是整个安全启动链的基石。

在实际项目中,RoT通常由以下几部分组成:

  • BootROM代码:固化在芯片ROM中的第一段启动代码,芯片上电后CPU第一条指令就从这里取。
  • 一次性可编程存储(OTP/eFuse):用于存储根密钥的哈希、芯片唯一ID、安全配置位等。烧进去就改不了。
  • 硬件安全模块:比如硬件哈希引擎、PKA(公钥加速器)、TRNG(真随机数发生器)。这些模块在BootROM阶段就能被调用。

我记得有一次做芯片选型评估,某厂商的RoT居然放在Flash里,还号称「安全启动」。我当时就笑了 —— Flash是可以被物理篡改的,这哪叫信任根?真正的信任根,必须物理上不可写。

3.2 BootROM —— 第一棒跑好,后面才稳

BootROM是芯片上电后执行的第一段代码。它的任务很简单:验证下一级镜像(通常是SPL或BL1)的签名,如果验证通过,就把控制权交出去。

BootROM的典型流程是这样的:

  1. 硬件初始化:设置堆栈、时钟、内存控制器等基础外设。这一步要尽量精简,因为ROM空间有限。
  2. 读取根公钥:从OTP中读出预置的根公钥哈希,或者直接读出公钥本身(取决于芯片设计)。
  3. 加载下一级镜像:从Flash、eMMC或UART等启动介质中读取BL1镜像到SRAM。
  4. 验证签名:用根公钥验证BL1镜像的数字签名。验证通过才跳转执行。
  5. 失败处理:验证失败则进入错误状态,比如死循环、点亮错误指示灯、或者触发硬件安全中断。

我个人的经验:BootROM的代码量通常控制在4KB到32KB之间。空间非常紧张,所以能用硬件加速的就别用软件实现。比如RSA验证,用硬件PKA比软件快两个数量级,而且更安全。

3.3 启动校验流程 —— 链式信任传递

安全启动不是一步到位的,而是一级一级「接力」完成的。每一级都负责验证下一级的完整性和真实性。这就是所谓的链式信任。

我画了一张图,帮你理解这个链条:

TEE安全启动链流程图 TEE安全启动链:链式信任传递 BootROM (RoT) BL1 (SPL) BL2 (TEE Bootloader) TEE OS TA (可信应用) 验证方式 • 每级验证下一级的签名 • 公钥由上一级提供 • 根公钥固化在OTP中 安全属性 • 完整性:防篡改 • 真实性:防伪造 • 不可抵赖性:防否认

你看,每一级都只信任上一级。BootROM信任根公钥,所以它验证BL1;BL1信任BootROM传递下来的公钥,所以它验证BL2;以此类推。这个链条一旦断了,整个TEE就不可信了。

注意:链式信任有一个致命弱点 —— 如果某一级的验证逻辑本身有漏洞,攻击者就能跳过该级验证,直接加载恶意镜像。所以每一级的验证代码都必须经过严格的形式化验证和审计。

3.4 防止回滚攻击 —— 让旧版本永远无法复活

回滚攻击是什么?说白了就是攻击者把系统降级到一个有漏洞的旧版本,然后利用已知漏洞攻破TEE。你想想看,如果新版本修了漏洞,但攻击者能让你「穿越」回旧版本,那修复还有什么意义?

我曾在某个IoT项目里遇到过这种攻击。设备OTA升级后,攻击者通过物理手段把Flash里的镜像换成了旧版本,然后利用旧版本的一个缓冲区溢出漏洞拿到了TEE的控制权。嗯,从那以后,我对回滚防护就特别敏感。

防止回滚攻击,业界主要有三种做法:

方案 原理 优点 缺点
版本号检查 在镜像头中嵌入版本号,启动时比较当前版本与存储的「最小允许版本」 实现简单,开销小 版本号可被伪造(如果签名不覆盖版本号字段)
单调计数器 使用硬件单调计数器,每次升级递增,启动时验证计数器值 硬件保证不可回退 计数器有寿命限制(通常10万次左右)
eFuse熔断 每次升级熔断一组eFuse,启动时检查eFuse状态 物理不可逆,安全性最高 eFuse数量有限,熔断后无法恢复

我个人最推荐的是单调计数器 + 版本号签名的组合方案。具体做法是:

  1. 镜像头中包含版本号字段,并且版本号参与签名计算。这样攻击者无法篡改版本号而不破坏签名。
  2. 在OTP或安全存储中维护一个「最小允许版本号」。
  3. 每次启动时,BootROM读取镜像版本号,与最小允许版本号比较。如果镜像版本号更小,直接拒绝启动。
  4. 每次安全升级成功后,更新最小允许版本号为当前镜像版本号。

关键点:版本号必须被签名覆盖。我见过一些方案,版本号放在镜像头里但没被签名,结果攻击者把高版本镜像的签名贴到低版本镜像上,轻松绕过了版本检查。这种低级错误,千万别犯。

3.5 实战中的避坑指南

做安全启动链,有几个坑我踩过,分享给你:

  • BootROM不能依赖外部RAM:上电时外部RAM还没初始化,BootROM必须使用内部SRAM。我见过一个方案把BootROM栈放在DDR里,结果DDR初始化失败,芯片直接变砖。
  • 公钥存储要防物理读取:OTP中的公钥虽然不能改,但可以被读出来。如果攻击者能读出根公钥,他就能伪造签名?不对,公钥是公开的,签名需要私钥。但攻击者读出公钥后可以替换Flash中的公钥?嗯,这里要注意:公钥必须被硬件绑定到BootROM的验证逻辑中,不能从外部存储读取。
  • 回滚防护要覆盖所有镜像:不只是TEE OS,BL1、BL2、TA、甚至固件配置数据都要做回滚防护。攻击者往往从最薄弱的环节入手。
  • 测试要覆盖失败路径:我习惯在测试中故意破坏签名、篡改版本号、烧写错误公钥,确保BootROM能正确拒绝启动。这些测试用例比正常启动测试更重要。

一个小技巧:在开发阶段,可以在OTP中预留一个「调试模式」位。置位后BootROM跳过签名验证,方便调试。但量产前一定要熔断这个位,否则安全启动形同虚设。