4. 固件安全更新(OTA)
OTA更新,说白了就是给设备“打补丁”。但Matter设备不像手机,你没法插USB线刷机。一旦OTA流程有漏洞,整栋楼的设备都可能被攻破。我参与过几个智能家居项目,说实话,OTA这块是最容易出“幺蛾子”的环节。
4.1 OTA更新的安全流程
Matter的OTA流程,核心就三个角色:OTA Provider(提供固件的设备,比如网关)、OTA Requestor(请求更新的设备,比如灯泡)、DCL(分布式合规数据库,用来校验固件来源)。
整个流程我习惯分成四个阶段:
- 发现阶段:Requestor通过BDX协议找到Provider,询问“有没有新固件?”
- 查询阶段:Provider返回固件版本、大小、摘要等信息。Requestor判断是否需要更新。
- 下载阶段:通过BDX块传输协议,分块下载固件。每块都有校验。
- 应用阶段:下载完成后,验证签名,写入Flash,重启。
关键点:Matter规定,OTA过程必须使用TLS加密通道。也就是说,固件在传输过程中是加密的。但注意,加密只防窃听,不防篡改——签名验证才是最后一道防线。
我在项目中遇到过一个问题:设备在下载过程中突然断电,重启后固件只写了一半。嗯,这里就要用到“双备份”策略了。
4.2 签名验证机制
签名验证,说白了就是确认“这个固件确实是厂家发的,没人动过手脚”。Matter用的是ECDSA(椭圆曲线数字签名算法),曲线是secp256r1(也就是P-256)。
验证流程是这样的:
- 固件包包含:固件数据 + 签名值 + 签名者证书链。
- 设备用DCL根证书验证签名者证书链的合法性。
- 用签名者公钥验证固件数据的签名。
- 全部通过,才允许更新。
你想想看,如果证书链验证失败,说明这个固件来源不可信。我曾经见过一个案例:某厂商把根证书硬编码在代码里,结果根证书过期了,所有设备都无法OTA——这就是典型的“证书生命周期管理”没做好。
我的建议:签名验证一定要在RAM里做,不要在Flash里做。为什么?因为Flash可能已经被损坏了。先把固件下载到RAM,验证通过再写入Flash,这样更安全。
下面是一个简化的签名验证流程:
// 伪代码:固件签名验证
bool verify_ota_firmware(uint8_t* firmware, size_t len,
uint8_t* signature, uint8_t* cert_chain) {
// 1. 验证证书链
if (!verify_cert_chain(cert_chain, dcl_root_cert)) {
return false; // 证书链不合法
}
// 2. 提取签名者公钥
public_key_t* pub_key = extract_pub_key(cert_chain);
// 3. 验证固件签名
if (!ecdsa_verify(firmware, len, signature, pub_key)) {
return false; // 签名不匹配
}
return true;
}
4.3 回滚保护策略
回滚攻击,是OTA里最阴险的招数。攻击者拿到一个旧版本固件(可能有已知漏洞),强制设备降级到那个版本。你想想看,你辛辛苦苦修了漏洞,结果人家一个OTA又给你降回去了。
Matter怎么防回滚?核心机制是版本号比较。
| 策略 | 说明 | 我踩过的坑 |
|---|---|---|
| 版本号递增 | 固件版本号必须单调递增,不能回退 | 版本号用uint32,但有人用字符串比较,结果"9" > "10" |
| 安全版本号 | Matter定义的Cluster Revision,存储在不可篡改区域 | 有人把版本号存在普通Flash里,一擦就没了 |
| 熔丝/OTP | 硬件级别的防回滚,版本号写入后不可更改 | 成本高,但最安全。我建议关键设备用这个 |
警告:千万不要把版本号存在可被用户随意擦写的区域。我曾经见过一个产品,版本号存在配置文件里,用户恢复出厂设置后版本号归零,然后就能刷回旧固件了——这等于没防。
具体实现时,我习惯这样做:
- 设备启动时,从安全存储区(比如eFuse或受保护的Flash页)读取当前版本号。
- OTA下载完成后,比较新固件的版本号是否大于当前版本号。
- 如果小于或等于,直接拒绝更新。
- 更新成功后,将新版本号写入安全存储区。
这里有个细节:版本号比较要用无符号整数,别用有符号的。为什么?因为版本号可能很大(比如0xFFFFFFFF),有符号比较会出问题。
4.4 整体流程示意图
下面这张图,是我自己总结的OTA安全更新全流程。你看一眼,基本就明白整个链路了。
4.5 避坑指南
最后,我把自己踩过的坑整理一下,你写代码时注意避开:
- 签名验证不能跳过:哪怕是在开发阶段,也要走完整验证流程。我见过有人为了调试方便,把验证逻辑注释掉了,结果发布时忘了打开——那批设备全成了“裸奔”状态。
- 版本号用uint32,别用字符串:字符串比较“9”和“10”会出问题。用整数,简单粗暴。
- 双备份区要足够大:至少能容纳一个完整固件。我见过有人只留了50%空间,结果新固件比旧固件大,更新失败。
- OTA失败要有重试机制:网络不稳定是常态。建议重试3次,每次间隔递增(比如1分钟、5分钟、15分钟)。
- 日志要记录OTA过程:出问题时,日志是唯一的线索。记录版本号、签名结果、失败原因。
小技巧:OTA完成后,建议做一次固件完整性校验。用SHA-256算一遍整个Flash的摘要,和预期值对比。这样能发现Flash写入过程中是否出现了位翻转。
嗯,OTA安全更新这块,说白了就是“传输加密、签名验证、版本防回滚”三件套。每件都做到位,基本就稳了。但别忘了,安全是一个链条,OTA只是其中一环——下一环是什么?你自己琢磨琢磨。