4. OTA升级镜像格式与安全机制

在 Silicon Labs 的 Gecko Bootloader 体系中,OTA 升级镜像并不是直接把应用固件的 .bin 或 .hex 丢进 Flash 就能运行,而是要先封装成 GBL(Gecko Bootloader)文件。GBL 既承担“容器”的角色,把应用镜像、元数据和校验信息组织在一起,也承担“安全边界”的角色,通过签名、加密和版本控制,决定 Bootloader 是否愿意接受并跳转到这份新固件。理解 GBL 的结构与安全机制,是排查“升级包被拒绝”“升级后无法启动”“回滚失败”等问题的前提。

GBL 文件结构:从容器到可执行镜像

GBL 文件由若干“标签(Tag)”顺序拼接而成,每个标签以 4 字节 Tag ID 开头,后跟 4 字节长度字段,再跟数据区。Bootloader 解析时按标签逐个读取,遇到 GBL_TAG_END 结束。常见的标签类型包括:

  • GBL_TAG_HEADER:声明 GBL 格式版本、目标 Bootloader 版本、镜像类型(应用、Bootloader 自身、升级脚本等)。
  • GBL_TAG_PROGRAM_DATA:真正要写入 Flash 的固件数据块,可多次出现,按地址顺序排列。
  • GBL_TAG_ERASE:指示 Bootloader 在写入前擦除指定 Flash 区域,通常出现在数据块之前。
  • GBL_TAG_METADATA:携带版本号、产品 ID、时间戳等元数据,供版本回滚保护判断使用。
  • GBL_TAG_SIGNATURE:对前面所有标签内容计算的数字签名,用于完整性校验与来源认证。
  • GBL_TAG_ENCRYPTION:声明加密算法与参数,指示数据块是否被加密。
  • GBL_TAG_END:结束标记,Bootloader 读到它才认为镜像完整。

一个典型的 GBL 文件顺序是:HEADER → ERASE → PROGRAM_DATA(多段)→ METADATA → SIGNATURE → END。如果启用了加密,PROGRAM_DATA 中的数据是密文,SIGNATURE 则对密文或明文计算,取决于签名与加密的先后顺序。Silicon Labs 的 commander gbl create 命令会自动按这个顺序生成文件,例如:

commander gbl create app.gbl --app app.bin --sign key.pem --encrypt key.bin

其中 --sign 指定 ECDSA 私钥,--encrypt 指定 AES 密钥。生成的 GBL 文件大小通常比原始 .bin 大几百字节到几 KB,多出来的部分就是标签头、元数据和签名。

签名验证:Bootloader 如何判断“这份固件可信”

签名验证是 GBL 安全机制的核心。Silicon Labs 使用 ECDSA(通常为 secp256r1)对 GBL 内容做数字签名。Bootloader 内部预置了对应的公钥(或公钥的哈希),升级时执行以下判断:

  1. 读取 GBL 中除 SIGNATURE 标签外的所有字节,按相同算法计算摘要。
  2. 用预置公钥验证 SIGNATURE 标签中的签名是否与摘要匹配。
  3. 若匹配,认为镜像完整且来源可信;若不匹配,直接拒绝升级并返回错误码。

这里有一个容易踩坑的点:签名覆盖的范围必须与 Bootloader 计算范围一致。如果使用 commander gbl create 时对已加密数据签名,而 Bootloader 先解密再验签,就会失败。因此签名与加密的顺序必须在生成端和验证端保持一致。实际工程中建议先签名后加密,或严格按 Silicon Labs 文档的默认顺序执行。

加密选项:保护固件不被读取

GBL 支持 AES-128 或 AES-256 加密,通常采用 CTR 模式。加密只作用于 PROGRAM_DATA 中的数据块,标签头和元数据保持明文,以便 Bootloader 在解密前就能解析结构。密钥通过安全渠道预置在设备端,不能随 GBL 文件一起分发。

加密带来的直接好处是:即使攻击者从空中截获 GBL 文件,也无法直接提取固件内容进行逆向。但加密会增加 Bootloader 解密开销,且密钥管理本身成为新的安全责任。如果密钥泄露,加密就形同虚设。因此在实际项目中,加密通常与安全启动、安全调试接口配合使用,而不是单独依赖。

版本回滚保护:防止降级到有漏洞的旧版本

版本回滚保护用于阻止攻击者把设备降级到已知存在漏洞的旧固件。其机制是:GBL 的 METADATA 标签中携带一个单调递增的版本号,Bootloader 在升级前比较新镜像版本号与当前已安装版本号。只有新版本号大于或等于当前版本号时,才允许升级;否则拒绝。

版本号通常由应用固件在编译时写入,例如通过 --version 参数指定:

commander gbl create app_v2.gbl --app app.bin --version 2 --sign key.pem

设备端 Bootloader 会把当前版本号保存在非易失存储中,升级成功后更新。需要注意的是,版本号一旦提升就不能回退,否则回滚保护就失去意义。如果确实需要降级,必须通过擦除设备或使用特殊调试命令清除版本记录,这通常只在产线或研发阶段允许。

GBL 文件结构与安全验证流程 GBL 文件标签序列 HEADER ERASE PROGRAM_DATA (可多段,密文) METADATA 版本号 SIGNATURE END Bootloader 验证流程 ① 解析标签结构 读取 HEADER / METADATA ② 版本回滚检查 新版本 ≥ 当前版本? ③ 签名验证 ECDSA 公钥校验 ④ 解密并写入 AES-CTR 解密 → Flash 拒绝:版本回滚 拒绝:签名无效 升级成功,跳转应用 关键参数与安全边界 • 签名算法:ECDSA secp256r1,公钥预置于 Bootloader,私钥仅用于生成端 • 加密算法:AES-128/256 CTR,密钥通过安全通道预置,不随 GBL 分发 • 版本号:单调递增,存储于非易失区,升级成功后更新,不可回退

实际工程中的判断与排查

当升级失败时,可以根据 Bootloader 返回的错误码快速定位。例如返回 BOOTLOADER_ERROR_GBL_INVALID_SIGNATURE,说明签名验证不通过,应检查生成 GBL 时使用的私钥与设备端公钥是否配对、签名范围是否一致。返回 BOOTLOADER_ERROR_GBL_INVALID_VERSION,说明版本回滚保护拦截了本次升级,需要确认新固件版本号是否确实大于当前版本。返回 BOOTLOADER_ERROR_GBL_INVALID_FORMAT,则可能是标签顺序错误或文件被截断。

在产线或研发阶段,可以通过串口日志观察 Bootloader 解析 GBL 的每一步。例如在 bootloader 的 debug 输出中,会打印每个标签的 ID 和长度,以及签名验证结果。这些日志是判断“问题出在生成端还是设备端”的直接依据。

建议在项目初期就确定签名与加密策略:是否启用加密、密钥如何分发、版本号如何管理。这些决策一旦确定,后续所有 OTA 包都必须遵循同一套规则,否则会出现“同一设备能升级 A 包却拒绝 B 包”的混乱。
版本回滚保护一旦启用,降级操作会被拒绝。如果现场设备因新固件缺陷需要紧急回退,必须提前规划好应急通道,例如保留一个可擦除版本记录的调试接口,或在 Bootloader 中预留强制升级模式。不要等到故障发生时才临时修改 Bootloader。

总结来说,GBL 文件结构决定了 Bootloader 如何解析镜像,签名验证决定了镜像是否可信,加密选项决定了镜像是否可读,版本回滚保护决定了镜像是否允许被安装。四者共同构成 OTA 升级的安全边界。理解它们之间的配合关系,才能在升级失败时快速定位是格式问题、密钥问题、版本问题还是加密问题,而不是盲目重试或反复烧录。