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

资讯详情

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

STM32C5 OEMiROT安全启动与固件更新实战指南

STM32C5 OEMiROT安全启动与固件更新实战指南 1. 项目概述OEMiROT、STM32C5与工具链的组合为什么值得折腾先说结论如果你做的是带联网能力、OTA升级需求或者出货后要防抄板、防固件被篡改的产品OEMiROT基本是绕不开的一环。而ST官方在STM32C5这颗主打性价比与安全特性的MCU上已经把OEMiROT的底层驱动、bootloader模板和配套工具链整合得比较完整了代价是——官方资料分散在AN5056、AN5126、UM2237这些文档和X-CUBE-SBSFU包里第一次上手的人很容易在CubeMX的图形界面和IDE的编译配置之间来回卡壳。我这次的实际目标很简单在STM32C5系列的某个具体型号上用STM32CubeMX生成带OEMiROT安全启动和安全固件更新SFU的初始工程然后用STM32CubeIDE完成编译、烧录和验证整个流程最终得到一个可复现的OEMiROT最小示例。这里说的OEMiROT全称是OEM Immutable Root of Trust简单理解就是在芯片内部固化一段不可篡改的启动代码由它来校验后续的Bootloader和应用固件是否完整、是否被授权从而建立一条从芯片上电到用户App运行的信任链。文章面向的读者是那种已经会用STM32CubeMX生成普通外设工程、但对安全启动体系还处于“知其然不知其所以然”阶段的嵌入式工程师。读完这篇文章你不仅能跑通一个OEMiROT示例更重要的是能理解CubeMX里那些安全相关选项为什么这么设计以及在CubeIDE里报错时该怎么自己排查。2. 整体设计思路拆解OEMiROT在STM32C5上到底做了一件什么事2.1 STM32C5这颗芯片在安全启动上的底气STM32C5系列是ST面向中低端应用推出的Cortex-M33内核MCU核心卖点之一就是TrustZone。你可以把TrustZone想象成一套硬件级别的“双房间”隔离机制安全世界Secure World和非安全世界Non-Secure World。普通业务代码跑在非安全世界即使被攻破也碰不到安全世界里的密钥、证书和关键代码。OEMiROT的Bootloader代码、密钥存储、固件签名校验逻辑恰恰就是放在安全世界里由芯片内置的BootROM在复位后第一时间接管然后逐级完成校验。另一个关键点是STM32C5内部集成了用于密钥管理和加密运算的硬件外设包括AES、RSA、ECC这些算法加速器和真随机数发生器TRNG。没有硬件加速时做一次RSA签名验证在MCU上可能要几百毫秒甚至更久有了硬件加速之后这个时间会被压缩到毫秒级这在产品启动时的时间预算里相当重要。2.2 为什么必须用CubeMX和CubeIDE这对组合而不是手动搭工程很多人习惯用寄存器或者HAL库手写启动过程但OEMiROT这套体系牵扯的东西太多Flash的分区表、OTP一次性可编程区域里的密钥、TrustZone的SAU和IDAU配置、中断向量表的重映射、Bootloader和应用固件各自的链接脚本。任何一个地方写错轻则启动卡死重则芯片变砖。用CubeMX生成工程可以保证这些细节和ST官方BootROM的期望一致尤其是SAU安全属性单元的配置手写极容易遗漏某个内存区域的安全属性。CubeMX在STM32C5的OEMiROT支持上做得比较“傻瓜化”你在Security Manager配置界面里勾选OEMiROT填好密钥文件和版本号它就会自动生成带有安全启动框架的工程并且把Flash的分区布局以图形界面的方式展示出来。生成之后的项目里已经包含了SBSFUSecure Boot and Secure Firmware Update的核心代码你需要做的是根据自己板子的实际情况调整参数和编写业务App。2.3 方案选型的取舍OEMiROT与其他ROT方案的对比ST在安全启动这件事上提供了好几套方案比如OEMiROT、STiROT、带TZ的OEMiROT等。STiROT是ST自己提供完整固件、OEM只负责定制参数的方案适合不想关心底层实现、快速交付的场景但这也意味着安全逻辑对厂商透明对某些安全敏感度高的客户来说不一定愿意接受。OEMiROT则把校验逻辑的主动权交还给开发者OEM可以自己定制Bootloader的校验流程、密钥存储方式甚至更新策略灵活性更高但对应的学习曲线和出错概率也更大。在STM32C5这种同时支持TrustZone和HDP硬件调试端口保护的芯片上选择OEMiROTTZ的组合能获得最强隔离效果代价是工程复杂度高一些。3. STM32CubeMX中的OEMiROT详细配置手把手操作与参数含义3.1 准备工作的坑CubeMX版本和固件包的匹配先说一下我踩的第一个坑CubeMX版本太旧或者STM32C5的固件包版本不匹配会导致OEMiROT相关的选项压根不出现。我一开始用的是某个较老的CubeMX版本打开Security Manager之后只有空荡荡的界面一度以为是自己芯片型号选错了。后来排查发现是固件包版本太低CubeMX的版本更新之后再通过Help - Manage Embedded Software Packages把STM32C5系列的固件包升级到至少1.1.0以上OEMiROT的选项才完整出现。提示推荐至少使用STM32CubeMX 6.12及以上版本STM32C5的固件包使用最新版本。别偷懒版本不匹配的问题在安全功能上特别明显有些选项被隐藏掉不是ST故意不给而是旧版固件包根本没实现对应的代码生成模板。具体操作步骤如下打开CubeMX选择 New Project在Part Number里填入STM32C5系列的具体型号我这里用的是STM32C504选好封装后进入主界面。左侧 Connectivity、System Core这些分类先不用管优先点开 Security Manager 这一项你会发现它和普通外设的配置方式完全不同需要按步骤来。3.2 Security Manager中的OEMiROT配置步骤在Security Manager界面里第一步是选择ROT模式。下拉框里有No ROT、OEMiROT、STiROT等选项我选了OEMiROT。此时界面会展开一系列子选项包括OEMiROT boot path即启动路径这里有两个选择一个是直接校验App另一个是校验Bootloader再校验App。实际项目建议选带独立Bootloader的路径因为后续要OTA升级的话Bootloader里实现的SFU安全固件更新逻辑是必须的不然后续升级流程会非常难扩展。Key management这里需要加载或者生成密钥文件。ST官方推荐用RSA-3072或者ECC-256我为了做演示选了ECC-256因为它的密钥尺寸更小、签名验证速度更快。Flash layout这里会显示Flash分区的示意包括BootROM区域、OEMiROT Bootloader区域、Primary App区域和Secondary App区域。Secondary App区域不是必须的但如果你打算做A/B分区升级它一定要预留。配置完这些还要注意Secure/Non-Secure内存的划分。在STM32C5的Memory Protection单元里你需要把Flash起始地址开始的保护区域设置为Secure包括Bootloader以及存放密钥的OTP区域而App所在的区域根据业务需求设置为Non-Secure。你在CubeMX里调整这些设置时它会实时同步更新SAU配置非常直观。3.3 key文件生成、配置和烧录注意事项密钥管理是整个配置里最核心也是最容易出问题的部分。ST提供了两种方式生成密钥一是在CubeMX里直接生成二是用Python脚本调用OpenSSL生成。我建议在CubeMX里直接生成省事而且生成时会自动把公钥写入工程代码里私钥则单独保存为一个.pem文件。生成密钥时CubeMX会弹出一堆选项比如密钥长度、公钥导出格式、hash算法。ECC的话选P-256曲线hash用SHA-256就足够了。生成的.pem私钥文件务必保存好因为后续固件签名还要用它。公钥会自动变成C数组嵌进OEMiROT的固件里在工程代码里搜索key_public之类的符号就能找到。烧录时有个细节密钥最初是写到OTP区域的OTP的特点是只能写一次。所以调试阶段如果发现密钥写错了芯片基本就废了只能换一颗。我自己的做法是调试阶段先不烧OTP而是用ST官方提供的调试模式绕过签名校验等所有逻辑都验证OK了再烧OTP和正式密钥。具体做法是把TZEN和HDP相关的字节在CubeMX的Option Bytes配置里设置为可调试状态等量产时再收紧。3.4 触类旁通TZEN位、RDP等级与调试权限的联动这里补充一个很多人搞不明白的联动关系STM32C5的TZEN位TrustZone启用一旦置位调试接口的权限就分成了Secure和Non-Secure两套。你拿着ST-Link去连芯片默认只能访问Non-Secure世界想要看到安全世界里的代码执行状态必须在调试器里配置好认证机制。在CubeMX的Option Bytes里TZEN置位之后RDP读保护等级建议至少设置到1级否则外部调试器还是能直接把Flash里的固件内容读出来那整个OEMiROT的意义就大打折扣了。3.5 代码生成后的目录结构解读点右上角的GENERATE CODE之后CubeMX会生成一个包含多个工程文件的目录。这里有一个很常见的误解生成的工程并不是一个单体的IDE工程而是分成了好几块。其中比较重要的是OEMiROT_Boot这是OEMiROT的Bootloader工程独立编译OEMiROT_App这是用户应用工程示例里会包含一个LED闪烁之类的简单AppSBSFU相关目录存放安全固件更新服务代码如果你用CubeIDE打开生成的.ioc文件默认只会加载一个工程其他工程需要手动导入。我建议先用CubeIDE打开OEMiROT_Boot工程进行编译再打开App工程编译两个工程是分开烧录的。4. STM32CubeIDE环境下的构建与调试从编译报错到启动验证4.1 工程导入与双工程编译的先后顺序把CubeMX生成的目录保存好之后打开STM32CubeIDE选择File - Open Projects from File System把整个生成目录导入。此时你会看到列表里有几个独立的工程如果只导入了一个别慌右键点击Project Explorer空白处选择Import - Existing Projects into Workspace再逐个添加即可。编译顺序很重要先编译OEMiROT_Boot再编译App。因为App的签名文件是由Boot工程里的工具链在编译时生成的不对实际上签名是在烧录阶段由脚本完成的但Boot编译的时候会生成一些映射文件App链接时需要用到。如果你先编译App大概率会遇到找不到某个符号或者Flash地址越界的报错。导入完OpenSSL库之后继续编译Boot工程你会遇到第一个让新手头皮发麻的报错undefined reference to gcry_*之类的。这个是因为OEMiROT的代码在做密钥校验和安全更新时用到了Libgcrypt加密库而这个库在Windows或者Linux宿主机的CubeIDE环境里需要先编译成静态库并链接进工程。CubeMX生成的工程里已经包含了Libgcrypt的源码路径但在Windows上默认不会自动编译需要手动构建。我的做法是先打开Boot工程下的lib目录找到Libgcrypt相关的构建脚本。在Windows下需要用MSYS2或者直接下载ST预编译的库。如果你实在不想折腾可以直接用ST官方提供给CubeIDE的预编译库替换在工程属性 - C/C Build - Settings - Linker - Libraries里把gcrypt的路径和名称填好就行。4.2 链接脚本与Flash分区地址的核对OEMiROT能正常工作很大程度上依赖Flash分区的物理地址是否和BootROM的期望一致。在CubeMX生成的工程里链接脚本文件后缀是.ldBoot工程和App工程各有一个。你需要重点核对以下几个地址BootROM的起始地址通常是0x0这个由芯片固定OEMiROT Bootloader的起始地址在0x08000000或者更高取决于你预留的BootROM大小Primary App的起始地址一定要和Cryptographic签名里的载荷地址一致否则启动校验会失败我遇到过一种情况App工程的.ld文件里FLASH起始地址比Bootloader的末尾多出了一个扇区导致App烧录之后Bootloader在跳转时直接硬故障。排查方式很简单用CubeIDE的Memory Explorer查看当前Flash的写入情况对比Linker脚本里的地址范围基本一眼就能看出问题。注意在修改.ld文件时不要把App的起始地址刚好放在某个Flash扇区的中间SEMC之类的特性会出问题实际上对于内部Flash来说起始地址必须在扇区边界上否则Flash擦写时会把相邻区域的数据一起抹掉。4.3 Bootloader代码中key的配置与编译在Boot工程里搜索key_config.h之类的头文件这里存放的是你在CubeMX里配置的密钥信息比如密钥长度、公钥数组、签名长度等。如果你在CubeMX里重新生成了密钥这个头文件会自动更新但如果你手动改过代码重新生成时会覆盖需要注意备份。默认情况下OEMiROT的Boot代码主流程是从OTP区域读取公钥配置校验Non-Secure App的头部和签名校验通过后关闭安全外设的访问权限跳转到App的Reset_Handler这三个步骤里第一步最容易出问题因为OTP区域在调试阶段可能还没有写入正确的公钥。ST提供了一个方便的调试宏在boot_config.h里把OEMIROT_DEBUG_ENABLE定义为1这样即使OTP空着也会使用代码里硬编码的公钥进行校验。等调试结束后再关闭这个宏并烧录真正的OTP密钥。4.4 烧录过程中ST-Link调试器的选择与配置烧录OEMiROT工程时ST-Link的连接设置和平时的普通工程不太一样因为涉及到Secure区域的访问权限。在CubeIDE的Run Configurations - Debugger界面里需要把Mode Setup设置为Under Reset否则芯片上电后立即跳进BootROM执行校验调试器根本没有机会halt住CPU。如果你用的是ST-Link V3还能在Target Settings里配置Secure Access的认证方式。烧录顺序上我第一次接触时试过先烧App再烧Boot结果App校验自然失败。正确的流程是先把Boot工程编译产生的elf文件烧进去再把App的elf烧进去而且烧完Boot之后不要急着断开直接在同一个调试会话里加载App的elf。如果一切正常复位运行后板子上的LED会按照App里的逻辑闪烁。4.5 借助Secure Manager和调试日志定位跳转失败的原因如果复位后板子没有任何反应优先级最高的排查手段是看串口日志。OEMiROT的Boot代码本身支持通过串口输出调试信息在CubeMX里配置一个UART然后在boot_debug.h里把OEMIROT_DEBUG开关打开编译后重新烧录复位后就能看到一条条详细的执行日志比如[OK] Image 0 authenticated或者[ERR] Signature verification failed有了日志你就能判断问题出在哪个环节。我遇到过最典型的情况是公钥对的匹配没问题但App的版本号比Boot里存储的最小版本号低导致拒绝启动。这个版本号在CubeMX的Security Manager里可以配置也可以在App的头部结构体里修改很多人会忽略它以为只是记录信息其实它是防回滚攻击的关键机制。5. 常见问题与排查技巧实录OEMiROT调试路上的必踩坑清单5.1 周期检查清单从编译报错到运行异常的定位路径我把这段时间遇到的高频问题整理成了下面的速查表方便大家在碰到同类问题时按图索骥症状可能原因排查方法CubeMX中无OEMiROT选项STM32C5固件包版本过低或CubeMX版本过低升级CubeMX到6.12升级STM32C5固件包到最新版编译时报找不到gcrypt头文件Libgcrypt库未正确引入在工程属性里手动添加gcrypt源码路径或预编译库烧录后无任何输出Boot地址错误或调试器连接模式不对检查.ld链接脚本地址把Debugger设置为Under Reset模式[ERR] Signature verification failed公钥不匹配或App未签名/签名错误确认烧录的App是用同一个私钥签名检查OTP公钥App启动后马上HardFaultTrustZone的SAU配置错误或App入口地址不对核对SAU配置确保App在Non-Secure区域且向量表正确复位后不停重启防回滚版本号冲突或App头部CRC错误查看串口日志确认是版本号问题还是校验问题这些问题的共同特点是没有一个能靠“直觉”绕过去每一步都要回到配置里核实一遍。我Debug的时候习惯拿一张纸把Boot流程写下来每通过一个环节就划掉一个很快就能锁定问题点。5.2 烧录和调试时遇到“芯片被锁”的解救方案OEMiROT调试中最大的噩梦是芯片因为RDP等级或者OTP烧写错误被锁死表现为ST-Link连不上、CubeIDE报Cannot access target。有一种情况是可解的如果之前设置的是RDP Level 1可以通过全片擦除回到Level 0相当于重获调试权限。操作方法是按住板子复位键在CubeIDE里点击下载等连接上的瞬间松开复位键这时来得及把RDP选项改回来。如果RDP已经是Level 2那就是永久锁死只能换芯片了。为了避免这种不可逆的损失我的个人建议是OEMiROT的调试前期先用官方评估板跑通流程评估板上往往会预留好特殊调试接口比直接把密钥和OTP烧进自己的产品板要安全得多。等所有步骤都跑通且确认无误再在自己的板子上烧写生产级密钥。5.3 对新手最有用的三条实操心得写到这里把一些零散的体会做个总结这些是当时没人告诉我、全靠踩坑换来的经验第一OEMiROT不是一套“烧录完就完事”的功能它是一条完整的工具链涉及CubeMX配置、密钥管理、Boot编译、App签名、烧录顺序。任何一个环节的工具版本不匹配都会导致诡异的问题。建议在一开始就把所有工具的版本号写在一个文本文件里出问题先核对版本比Debug代码更省时间。第二调试阶段千万不要做全流程安全收紧。先关闭OTP密钥用调试宏硬编码公钥先别开启RDP高级别保护先用ST-Link的Under Reset模式连芯片。所有这些都确认OK之后再一步步收紧这样出问题还能有回旋余地。第三App工程的链接脚本一定要在CubeMX生成之后、写业务代码之前先检查一遍。就算你在CubeMX里配置了安全的Flash布局工程生成时可能因为固件包版本不同出现地址偏移这就是为什么很多人“照着教程做”却总是启动失败的原因。6. 从示例到产品的扩展OEMiROT后续还能怎么玩跑通一个OEMiROT示例只是一个起点。我在实际项目中还做过几种扩展这里简单分享下思路方便你根据自己的需求去选型。如果你的产品需要Wi-Fi或者蜂窝网络的远程升级那么OEMiROT的SFU服务要配一个上位机工具ST官方提供了STiROT_UpdateTool或者OEMiROT_UpdateTool的Python脚本负责把待升级的固件签名好并打包成特定的传输格式。你的App端需要实现接收新固件、写入Secondary分区、然后请求Boot跳转到升级模式这几个步骤。这个过程和单纯的Boot校验相比又多了一个“下载中断如何恢复”的设计难点好在OEMiROT本身对Secondary App的完整性校验做得足够严谨下载一半断电下次还能重新开始。如果产品要过一些安全认证比如SESIP或者PSAOEMiROT也是最基础的前提条件认证机构会验证你确实存在一条可验证的信任链并且密钥管理方式合理。我在这个阶段就遇到过审计机构要求提供密钥管理文档的情况所以从项目初期就要把密钥生成、存储、流转的流程记录下来不然补材料的时候会非常痛苦。另外一个细节如果你未来要把OEMiROT从STM32C5移植到其他同样支持TrustZone的STM32系列CubeMX生成的大部分配置是通用的可移植性比手写的Bootloader要好得多。唯一需要调整的是Flash分区的大小和地址因为不同型号的Flash容量、扇区结构不同。建议翻一下对应系列的参考手册里的Flash编程章节逐个扇区划分清楚再动工。7. 个人经验这样学习OEMiROT效率最高说实话OEMiROT整套学习曲线不短因为它比普通的外设开发多了一个“安全体系”的维度。如果让我重新学一遍我会按下面这个顺序来先把ST官方的AN5056关于STM32 TrustZone的概述和AN5126关于安全启动的详解通读一遍不用全背下来但对“信任根是什么、校验流程分哪几步、Flash怎么分区”要有个整体印象这样后面操作CubeMX时才不容易迷路。然后直接在官方评估板上按这篇文章的步骤跑一遍过程中不用自己写一行业务代码纯用CubeMX生成的示例工程验证启动链路。等你看到一个LED在Boot校验完成后亮起来就会对整套机制建立信心。接下来再尝试修改App加入自己的外设驱动和业务流程同时学会利用OEMiROT_UpdateTool做一次完整的固件签名和升级演练。这一步走完你才真正算摸到了产品级SBSFU的门槛。最后再回头去看安全相关的设计文档比如密钥管理规范、防回滚机制、安全固件更新的备份恢复策略这时候你会发现之前看不懂的地方全部都能串起来了。最后再分享一个我自己一直保持的习惯每做一个OEMiROT相关的项目都会把CubeMX的.ioc文件、所有密钥文件的命名规范、烧录脚本和版本号记录打包归档。因为安全启动这种东西半年后再回来看如果当时的记录不够清晰连确认自己当前在用的是哪一版密钥都困难。文档化虽然不是技术活但在实际工作中能省下的时间远超想象。
返回列表