尧图建网站 尧图建网站 YAOTU WEB BUILD 免费咨询
ARTICLE DETAIL

资讯详情

深耕网站建设与建站编程的一线实战洞察。

IoT固件安全基石:安全启动链、信任根选型与防降级实战

IoT固件安全基石:安全启动链、信任根选型与防降级实战 记得写这个系列之前我一直在琢磨一个问题前面几篇从威胁建模、通信加密、身份认证、云平台对接讲到数据隐私把IoT安全在网络侧的防线捋得比较清楚了。但每次分享完总有做硬件的朋友追着问你说的这些防护前提是不是设备上跑的还是我们自己的代码如果设备出厂后被拆开换了固件开机跑的压根不是我们写的程序网络侧做再多不是白搭吗这个问题问到根子上了。Part 6我就集中解决这“最后一公里”——固件与信任根。这篇会覆盖三层内容第一安全启动链是怎么一环扣一环验证的为什么它不等于“开机验一次签”第二信任根选型时OTP熔丝、独立安全芯片、TEE这些方案的取舍逻辑第三我在实际项目中搭建安全启动链、处理OTA防降级的完整路径以及调试阶段踩过的高频坑。刚接触IoT安全的工程师或者正在给产品做安全方案选型的技术负责人这篇应该都能找到对你有用的东西。1. 前五篇聊完了网络与协议这一篇该碰硬骨头了固件与信任根1.1 网络侧防线与设备本地安全的边界系列前面几篇分别写过IoT设备在网络上怎么设计身份认证、怎么把通信加密落地、怎么规划安全的云端接入流程。这些工作共同构成一道外围防线。但做过实际产品的人心里都清楚网络侧的防护有一个隐含假设设备上跑的固件和启动流程本身是可信的。这个假设在真实世界里经常不成立。硬件设备从出厂到运输、再到现场部署整个生命周期里有大量被物理接触的机会。尤其是一些部署在户外、机房、管廊里的设备攻击者有充足的时间和条件拆开外壳、接上调试串口、读取flash芯片。如果攻击者能把固件替换掉往启动链里植入一个后门那前面讲的TLS加密、云端认证、访问控制全部形同虚设——因为所有上层逻辑都建立在“设备的代码是我们写的”这个前提上而代码本身已经被换了。这就是IoT安全和传统IT安全最大的差异IT服务器锁在机房里物理接触门槛高IoT设备暴露在物理世界的各个角落物理攻击是必须纳入威胁模型的。所以IoT安全做到后期一定避不开本地固件安全。这不是“要不要做”的问题而是“什么时候做、做到什么等级”的问题。1.2 为什么固件安全问题在IoT领域尤其突出PC和服务器领域其实很早就有了Secure Boot、UEFI安全启动这类机制。普通用户可能也遇到过华硕主板里那个“Secure Boot”选项打开之后启动时会校验引导程序签名。但IoT设备面临的固件安全问题和PC相比有几个特殊困难不能简单照搬PC的做法。第一量级大且分散。一颗消费级IoT芯片出货量动辄百万级设备部署在用户家里、工厂车间、户外杆塔上任何一个节点被物理拆解分析攻击者都能获得无限次尝试机会。PC的安全启动只需要防住本地操作者而IoT要防的是整个网络中任何一个较真的攻击者。第二生命周期长。一个设备在市面上服役五到八年是常态。这意味着一套密钥体系、一套签名机制、一套OAT升级流程都要在这么长的时间窗口内保持可信、可维护。芯片的加密算法会老化密钥管理的团队会换人这些都得提前考虑。第三成本敏感。IoT产品对BOM成本极度敏感加一颗独立安全芯片就是几块钱的增量。这在硬件团队里是需要反复博弈的事情。我曾经在一款量产的智能网关项目里光是“要不要加SE芯片”就开了四次评审会。最后定下来加不是因为安全团队嗓门大而是因为客户提出了明确的安全合规要求。这三个特点决定了IoT固件安全不能照搬PC的那套做法必须有更精细的取舍。这也是我把这个主题单独拿出来写一整篇的原因。2. 安全启动不是“开机时验一次签”那么简单完整信任链的四个环节2.1 逐级信任每一级代码都要验证下一级很多人第一次接触安全启动以为就是“Bootloader在启动的时候校验一下系统镜像签名”。这个理解不算错但太粗了。真正完整的安全启动是一条信任链从芯片上电的第一条指令开始就生效了。以典型的ARM Cortex-A平台为例上电后大致是这样一个过程Boot ROM是芯片内部Mask ROM里固化的一段代码它不可修改是整个信任链的物理锚点。上电后它首先加载并验证下一级引导程序。Boot ROM验证FSBLFirst Stage Boot Loader验证签名通过后才把控制权交给它。FSBL继续验证下一级引导程序比较常见的是U-Boot这类二级引导器。U-Boot再验证内核镜像、设备树、initramfs。内核启动之后在挂载根文件系统之前还要通过dm-verity这类机制验证根文件系统的完整性。每一级都只信任上一级的验证结果每一级在交出控制权之前都要先验下一级的签名。这样链条才是完整的。任何一个环节断裂比如Boot ROM直接加载了未经校验的FSBL那整条链就废了。所以评估一个平台的安全启动能力时我最先看的就是Boot ROM到底校验什么、用什么校验、校验失败时怎么处理。实测中还有个容易忽略的细节验签不光是“算出签名值对比一下”这么简单。签名算法的实现是否正确、验签用的公钥是否从可信位置读取、哈希算法是否被弱化这些都会成为攻击点。我在review安全启动代码时会重点看有没有为了“兼容旧镜像”而保留的算法降级路径——这种后门一旦存在攻击者很容易把校验算法降到弱哈希上硬碰。2.2 验签只是第一步还有防回滚签名验证解决的是“代码是不是原厂签发的”这个问题但它还有一个同样致命的问题没解决代码是不是最新版本。攻击者可以把设备固件降级到一个有已知漏洞的旧版本上然后利用这个漏洞做文章。如果系统只验签、不验证版本降级攻击就完全绕过了安全启动。所以正规方案里都有一道防回滚机制eFuse里的版本号计数器、OTP里的单调递增计数器或者存储在安全存储里的版本标记。每次启动时校验“镜像版本大于等于当前记录的版本”否则拒绝启动。防回滚的设计有个关键点计数器只能递增不能回退。我在项目里习惯把版本号体系分成两段大版本号对应硬件计数器小版本号放在镜像的元数据里。只有跨大版本升级时才烧一次熔丝计数器。这样既能精确控制版本又不会过早耗尽OTP的可用位数——很多芯片的熔丝位是有限的烧一个少一个这是硬件资源约束必须在方案设计阶段就想清楚。2.3 运行时的信任同样重要启动链解决的是“开机过程中的信任”问题但设备跑起来之后呢如果应用层有漏洞攻击者拿到root权限理论上可以改内存、打补丁、注入代码。这时需要的是运行时保护机制比如TEE可信执行环境、内核安全模块、代码段只读内存保护等。我在实际项目里的核心经验是安全启动和运行时保护必须配套做。很多团队花大力气把启动链做得很严密但应用层一个栈溢出就能让前面所有工作白费。安全启动解决的是“你无法轻易替换我的代码”运行时保护解决的是“你无法在我的进程空间里偷偷跑代码”——两条腿缺一不可。这就像给保险箱装了好几把锁结果取东西的人出门不关门锁再结实也没用。运行时保护的做法不复杂内核开CONFIG_STRICT_KERNEL_RWX关键进程用seccomp限制系统调用敏感操作全部收敛到TEE里执行。这些工作在IoT设备上做起来比重服务器上容易因为IoT设备跑的服务少、进程面小反而更可控。3. 信任根选型熔丝、独立安全芯片、TEE到底选哪个3.1 信任根的本质一个不可篡改的起点把前面说的“Boot ROM是信任根”再往深挖一层Boot ROM里的代码在芯片出厂时就固化了但它也需要一点外部信息来完成验签——它得知道“哪些公钥是可信的”。这个公钥的存放位置就是真正的信任根。所以信任根选型的本质就是回答一个问题把“谁可以签发代码”这个最底层的判断依据放在哪里、用什么方式保护。这个位置必须是攻击者即便拿到完整硬件也无法篡改的否则整个信任链就失去了根基。主流方案大致有下面这几类我放在一起对比方案原理成本增量安全等级典型场景eFuse/OTP熔丝根公钥哈希烧录在芯片一次性可编程存储区最低中消费级IoT、摄像头、路由器独立安全芯片SE密钥存储在SE内部验签在SE内完成CPU只拿到结果中高高支付终端、车联网、工业控制TEE可信执行环境利用CPU的TrustZone等硬件隔离机制在安全世界内存密钥、做验签低中高手机、智能网关、泛智能终端3.2 eFuse方案便宜但必须想清楚风险eFuse方案的核心思路是芯片出厂时留一批一次性可编程的熔丝位把根公钥的哈希烧进去。之后Boot ROM在验签时只认这个哈希对应的公钥。因为熔丝是一次性的谁也没办法改这就成了信任锚。这个方案成本极低尤其适合对BOM成本敏感的消费级产品。我做过的一款智能摄像头就是用的eFuse一颗主控SoC自带eFuse不需要额外加芯片总体成本几乎没增加。但eFuse方案有几个风险点必须提前想清楚根公钥一旦烧进去就改不了。如果根私钥泄露了需要更换密钥对熔丝方案下只能换芯片。所以很多团队会把密钥分成两级根密钥存熔丝和二级代码签名密钥存固件根密钥极少使用日常签名用二级密钥将来真出问题还有回旋余地。这个二级密钥体系我后面实操部分会详细展开。熔丝烧录是永久性操作调试阶段烧错了基本等于报废一颗芯片。所以正式烧熔丝前一定得在工程样品上反复演练把流程跑熟。eFuse的可编程位数量有限做防回滚计数器时要精打细算。3.3 独立安全芯片把密钥锁进保险柜独立安全芯片Secure Element的做法不一样。它在自己的硬件隔离环境里存密钥、做验签CPU只能通过安全通信接口请求它“帮我验一下这个签名”。就算攻击者完全控制CPU也拿不到SE里的密钥。代价就是成本以及集成复杂度。一颗合规的SE芯片要增加几块钱BOM成本还得额外做通信协议、做个人化配置把设备证书和密钥注入SE。对很多IoT产品来说这一步值得尤其是那些要过安全认证的产品。我接触过的几个金融支付终端项目SE基本是标配因为监管和认证要求摆在那里没有SE就过不了测试。我的建议是如果产品要做金融级支付或者要保护的核心知识产权价值足够高直接上SE。这种场景下省下来的几块钱成本远不够覆盖一次安全事件带来的损失。3.4 TEE方案CPU厂商已经帮你做好了ARM阵营的TrustZone、RISC-V阵营的各种TEE方案本质是在同一颗CPU上划出安全世界和普通世界用硬件强制隔离。安全世界里可以跑一个迷你OS专门负责密钥管理和验签。TEE的好处是不用加硬件成本低性能好坏处是安全世界的代码面比较大如果TEE自身有漏洞信任锚就被攻破了。而且TEE方案的调试比较痛苦断点打不进去日志也不好拿。我在一个网关项目里用TEE来管理设备身份密钥光是调试一条TEE侧的内存越界就花了两周。实际项目里我见过不少团队把TEE和eFuse组合使用eFuse存根公钥做最初信任锚TEE负责运行时的密钥保护和验签流程。这个组合在安全性和成本之间平衡得很好也是我个人比较推荐的一个组合。4. 从零搭建安全启动链的实操路径Cortex-A平台加U-Boot的完整流程4.1 密钥体系设计两个层级六个文件前面理论部分比较多这一节我讲一下实操。以我自己做过的一个项目为例芯片平台是Cortex-A系列的SoC引导程序用U-Boot。整个过程分四步走每一步都踩过不少坑。第一步设计密钥体系。我用的是经典的二级密钥结构根密钥RSA-4096私钥离线保存——放硬件加密机或者离线电脑里只用于签发二级密钥证书。日常开发不接触它。次级代码签名密钥RSA-3072用于日常给U-Boot、内核、设备树、根文件系统镜像签名。需要准备的文件包括根密钥对、次级签名密钥对、根公钥证书、次级公钥证书、厂商标识信息。这些文件要分权限管理生产环境和开发环境必须隔离。我见过一个项目因为开发机和产线共用同一套密钥后来开发机中了勒索病毒整个产线被迫停工换密钥——这个代价远远超过“多买一台机器做隔离”的成本。4.2 U-Boot签名配置与FIT镜像处理第二步配置U-Boot支持签名验证。U-Boot对FIT image的支持比较成熟做法是在内核、设备树打包成FIT image时对镜像里的每个节点进行签名。在U-Boot配置中启用签名验证功能把验签公钥以key节点形式编入U-Boot的设备树。配置启动命令从FIT镜像中加载经过验证的节点。确保所有镜像节点的“签名验证失败即停止启动”策略生效而不是仅仅打印一条警告。比较关键的一个点是公钥编入U-Boot设备树的时机。有的做法是编译时直接编入有的做法是运行时从存储介质读取。为了安全应该选择编译时编入或者运行时从一个受保护的分区读取。如果运行时从普通分区读取攻击者只要改掉这个公钥就能用自己的密钥重新签名一个恶意镜像——等于安全启动直接被架空。4.3 从开发模式切换到强制安全模式第三步烧入信任根并切换安全模式。整个切换过程我建议按三个阶段的顺序走第一阶段开发模式未烧熔丝验签逻辑通过日志打印结果但失败不阻断启动。这个阶段验证签名计算、FIT镜像生成、密钥匹配逻辑是否正确。目的就是跑通流程日志打得多一点没关系。第二阶段预生产模式烧入熔丝但保留一个“备份明文加载”的调试后门仅限内部版本。这个阶段验证完整启动链同时确保还有救砖机会。注意后门代码一定不能进入量产版本我在这部分会额外加编译开关只有带特定宏定义才编进去。第三阶段正式模式彻底关闭后门强制验签失败即停机。这个阶段才真正和量产环境一致。从第二阶段切到第三阶段是最容易翻车的时候。我建议在小批量试产板上先切跑完所有回归测试再上大批量。不要在大批量产线上第一次启用安全启动这是用真金白银换教训。4.4 验证你怎么确认安全启动真的生效了第四步验证。验证安全启动不能只看“能正常开机”因为如果验签逻辑根本没执行设备也能正常开机。换句话说正向测试通过说明不了任何问题必须做负向测试。我会专门整理一份“安全启动验证清单”每次发布固件前跑一遍。核心用例是这些用错误的私钥签名一个内核镜像刷进去确认设备拒绝启动。篡改设备树里的任意一个字节确认校验失败。尝试降级到旧版本镜像确认防回滚计数器拦截。把验签公钥替换成攻击者生成的公钥确认启动被拒绝。确认启动日志里看不到任何密钥相关明文信息。这几个用例看起来简单但每一条后面都可能踩坑。比如第一条如果U-Boot配置里把“验签失败”定义成了“打印告警继续启动”那负向测试就完全不生效。所以测试用例不是设计出来好看的是真要跑而且要自动化每次CI都跑一遍。5. OTA升级安全签名验签之外回滚保护与降级攻击才是大头5.1 OTA是全生命周期里风险最高的环节设备出货之后唯一的“写入口”就是OTA升级通道。攻击者最感兴趣的就是这个口。如果你的OTA包没有强签名校验那基本等于把设备的门钥匙放在了门垫下面谁都能捡起来开门。一个安全的OTA流程至少要有这几道关传输通道加密OTA包至少通过HTTPS或MQTT over TLS下发。这不是充分条件但能防中间人篡改。包级签名校验OTA包必须带完整签名设备端在写入flash之前完成验签校验通过才写。注意是“写入前验签”不是“写入后验签”。写入后启动时二次校验避免“验签通过但写入过程被篡改”这类边缘情况。很多攻击者会在写入过程中断点篡改数据。原子性与回滚机制写入失败不能破坏当前运行版本更新失败能回到旧版本继续跑。我在一个工业网关项目里遇到过这样的问题开发团队早期只做了“下载完成后验签”但没有在写入flash之后做二次校验。后来做渗透测试测试人员利用写入时序搞了一次flash级篡改系统竟然正常启动了。从那以后我把“启动时二次校验”写进了所有项目的最低安全要求。5.2 防降级计数器怎么设计才不会被绕过前面提到防回滚计数器这里细说。计数器存在哪里、谁增加它、谁会读它三个问题都要认真回答任何一个回答错了都可能被绕过。存在哪里常用的是eFuse或者OTP的剩余可编程位数。注意eFuse熔丝是一次性的很多芯片熔丝数量有限一个版本烧一位烧完就没了。所以有人会用“分桶”策略一个大版本号对应一个熔丝位小版本号放在镜像头里只在跳大版本时才烧熔丝。谁增加它应该在验签通过之后、启动信任链关键阶段之前由启动代码增加到位。不是随便一个用户态代码都能写否则攻击者可以通过某种方式篡改计数器。谁会读它Boot ROM和各级Bootloader在启动时读而且要保证计数器的读取路径可信。我曾经见过一个方案防回滚计数器的值从flash里读而flash可以被写坏后回落到默认值——这等于防回滚直接被废掉。5.3 A/B分区与失败恢复的工程细节A/B分区方案是目前IoT设备做安全OTA最可靠的玩法之一设备上有两套系统分区当前运行AOTA写入B。写入完成后在下一个启动周期尝试从B启动如果启动失败比如连续三次起不来回退到A。这个方案的核心价值是把“更新的原子性”问题化解得很优雅。用户无感升级失败不砖头业务连续性有保障。但在工程实现上有几个细节需要特别注意两个分区里都得保留验签能力。B分区里同样要做完整验签否则B一旦被恶意写入它自身就失去了可信性。回退机制要有上限。不然攻击者可以利用每次回退来反复折腾设备消耗flash寿命制造持久性拒绝服务。启动计数要存在安全存储里不能被用户清掉来绕过失败检测。这个细节很多团队会忽略我就在代码review时抓住过这种漏洞。A/B分区会让固件体积double对flash容量小的设备是个压力。不过现在主流IoT平台对flash容量已经比较宽松4MB以上的NOR flash很常见成本可控。6. 调试阶段的高频翻车现场我踩过的坑希望你别再踩6.1 密钥丢失比芯片报废更尴尬的事故我在一个项目里曾经因为开发机硬盘故障弄丢了次级签名私钥。当时的尴尬是固件还能继续用旧版本但任何新固件都签不了名了。因为根私钥锁在另一台离线机器里整个补签流程极其繁琐整整折腾了一周才恢复正常的发布节奏。从那以后我立了条规矩密钥必须有三份备份一份加密存放在公司的密钥管理系统一份离线冷存储放在保险柜一份交给独立负责人保管。任何时候接触私钥都要有审计记录。这事听起来像花钱找罪受但真出事故的时候就知道有多值。6.2 签错镜像验签失败但日志里没有任何线索安全启动调试最头疼的问题是签名验证逻辑正确但设备就是起不来日志里又看不到错误原因。我遇到过一次排查了整整两天最后发现是FIT镜像里把设备树的签名节点层级放错了U-Boot验签时找不到对应的公钥节点。这种问题的排查思路要讲顺序先从“验签流程是否真的执行了”开始用debug级别的日志输出确认签名验证被触发再逐层检查配置树里的key节点与镜像签名节点的匹配关系最后确认公钥被编入设备树的时机是在编译阶段还是在运行时。运行时从普通存储读取公钥的方案要优先怀疑公钥本身是否被替换。6.3 熔丝烧录后的“意外”熔丝烧录错误是我见过最惨烈的翻车现场。曾有一个同事在批量生产阶段因为脚本路径写错把生产密钥的哈希烧成了开发密钥的哈希。那批芯片全部只能识别开发密钥而开发密钥对应的私钥一直放在团队内部产品实际上丧失了防伪能力。最后只能全部返工换芯片损失相当惨重。所以烧熔丝的操作我强烈建议这么做独立的生产烧录工具不允许开发环境和生产环境共用一套脚本。烧录前二次确认哈希值用独立的校验工具打印出来人工比对。做小批量试烧、验证通过后再上大批量。保留至少几十颗未烧熔丝的芯片作为备份万一出问题还能补救。6.4 日志泄露敏感信息最容易被忽视的隐患还有一次我review同事提交的代码发现他把验签用的公钥哈希直接打印在启动日志里。虽然公钥哈希本身不算私密信息但这个信息配合物理攻击可以让攻击者更容易推断出你的密钥方案甚至为伪造“合法硬件身份”提供关键线索。安全的基本原则是任何密钥相关材料都不应该出现在日志、调试输出、错误码里。这个习惯成本极低但很多团队就是坚持不下去——因为调试的时候方便。6.5 开发板变砖的救砖路径做安全启动变砖是家常便饭。最有效的救砖路径按优先级排列是这样的如果Boot ROM支持USB下载模式优先用SoC厂商的下载工具直接擦除flash重新烧录。如果熔丝还没烧那一切好说重新烧就行烧了熔丝就要仔细看芯片是否支持解锁机制——很多芯片不支持。有些平台提供芯片厂商专用的调试接口权限控制很严格需要走流程申请。最后一个手段就是换芯片——所以调试阶段一定不要把所有样品都烧掉熔丝这个警告值得反复强调。我的习惯是调试阶段至少保留两块“万能板”不烧熔丝、开启全部调试接口专门用于验证启动逻辑另外几块“模拟量产板”再走完整安全启动流程。两块板子分开管理既能调问题又不至于把最后的救砖筹码全部打光。做硬件安全启动这些年我越来越觉得安全不是某一个环节做到极致而是整条链路上每一个环节都做到“够好”。固件与信任根是整个IoT安全的地基地基不牢上面盖再高的楼都是危房。下一篇我会接着讲设备身份与证书的全生命周期管理把设备从出厂到退役这个过程中的身份问题串起来聊一聊。
返回列表