3、密码学基础(下):公钥基础设施(PKI)、证书链、X.509证书格式解析
好,咱们接着聊。上一节我们把非对称加密和哈希算法讲透了,这一节要聊的是——这些东西怎么在实际的嵌入式系统里落地。说白了,就是怎么让Bootloader相信“你是谁”。
你想想看,光有RSA或者ECC算法,你只能做签名和验签。但问题来了:公钥从哪来?怎么保证这个公钥没被换过?谁来给公钥做担保?这一连串的问题,就是PKI要解决的。
3.1 公钥基础设施(PKI)到底是什么?
PKI,全称Public Key Infrastructure。名字挺唬人,其实说白了就是一套“信任体系”。
我个人的理解是:PKI = 证书 + 颁发机构 + 验证流程。这三样缺一不可。
在嵌入式安全里,PKI最常见的应用场景就是安全启动。Bootloader手里握着根证书的公钥,然后去验证下一级镜像的签名。这个过程中,证书链起到了“信任传递”的作用。
核心概念:PKI不是算法,而是一套管理公钥的规则。它解决了“公钥分发”这个最头疼的问题。
3.2 证书链——信任是怎么传递的?
证书链,也叫信任链。这个概念我在做车规级MCU安全启动时体会特别深。
举个例子:
- 根证书(Root CA):自签名,是信任的起点。Bootloader里硬编码的就是它的公钥。
- 中间证书(Intermediate CA):由根证书签发,用来签发最终实体证书。
- 实体证书(Leaf Certificate):签发给具体的固件或镜像。
为什么要搞这么复杂?直接根证书签固件不行吗?
嗯,这里要注意:根证书一旦泄露,整个信任体系就崩了。所以根证书通常离线保存,只在安全环境里使用。中间证书可以放在线签发服务器上,即使泄露了,吊销它就行,根证书不受影响。
我的经验:在量产项目中,我建议至少用两级证书链。根证书放在HSM(硬件安全模块)里,中间证书放在CI/CD流水线里。这样既安全又灵活。
3.3 X.509证书格式解析
X.509是证书的标准格式。你在嵌入式里看到的.der、.pem、.crt文件,基本都是这个格式。
一个X.509证书包含以下关键字段:
| 字段 | 说明 | 在Bootloader中的用途 |
|---|---|---|
| 版本号 | 通常是v3 | 决定证书结构 |
| 序列号 | 唯一标识 | 用于吊销列表查询 |
| 签名算法 | 如sha256WithRSAEncryption | 告诉验签用哪个算法 |
| 颁发者 | 签发该证书的CA名称 | 验证证书链时匹配 |
| 有效期 | 起止时间 | 检查证书是否过期 |
| 主体 | 证书持有者名称 | 标识镜像发布者 |
| 公钥 | RSA或ECC公钥 | 用于验签 |
| 扩展项 | 如Key Usage、Basic Constraints | 限制证书用途 |
| 签名值 | CA对证书的签名 | 验证证书完整性 |
我曾经在调试一个安全启动问题时,发现Bootloader验签总失败。查了两天才发现——证书里的Key Usage扩展没有设置digitalSignature标志。Bootloader验签时检查了这个标志,直接拒掉了。嗯,这种坑踩过一次就记住了。
3.4 在Bootloader中如何解析X.509证书?
嵌入式环境资源有限,你不能像PC那样用OpenSSL全套库。我一般用两种方式:
- 预解析成结构体:在编译时把证书的关键字段提取出来,硬编码到代码里。Bootloader只做验签,不做完整解析。
- 轻量级解析器:用mbedTLS或WolfSSL的证书解析模块,只保留DER解析功能,去掉ASN.1的完整编解码。
下面是一个简化的证书解析示例(伪代码):
// 从固件头部读取证书
uint8_t *cert = get_cert_from_firmware();
// 解析证书基本信息
cert_info_t info;
parse_x509_cert(cert, &info);
// 检查有效期
if (!check_validity(&info)) {
return ERR_CERT_EXPIRED;
}
// 检查Key Usage
if (!check_key_usage(&info, DIGITAL_SIGNATURE)) {
return ERR_KEY_USAGE;
}
// 用根证书公钥验证该证书签名
if (!verify_cert_signature(cert, root_pubkey)) {
return ERR_CERT_SIG_INVALID;
}
// 从证书中提取固件签名公钥
extract_pubkey_from_cert(cert, &fw_pubkey);
// 用固件公钥验证固件签名
if (!verify_firmware_signature(fw_pubkey)) {
return ERR_FW_SIG_INVALID;
}
return SUCCESS;
重要提醒:千万不要在Bootloader里做完整的ASN.1解析。那会占用大量Flash和RAM。我见过一个项目,因为用了完整解析库,Bootloader体积膨胀了3倍,最后不得不重新设计。
3.5 证书链验证的完整流程
在实际项目中,证书链验证的流程是这样的:
- Bootloader从Flash中读取根证书公钥(硬编码在代码里)
- 读取固件头部的证书链(通常包含中间证书和实体证书)
- 用根证书公钥验证中间证书的签名
- 用中间证书的公钥验证实体证书的签名
- 从实体证书中提取固件签名公钥
- 用该公钥验证固件镜像的签名
每一步都不能省。我曾经在项目中偷懒,跳过了中间证书的验证,结果被攻击者利用了一个中间人攻击的漏洞。从那以后,我再也不敢省略任何一步。
3.6 嵌入式PKI的最佳实践
最后,分享几个我在项目中总结的经验:
- 证书格式选DER:DER是二进制格式,解析快,体积小。PEM是Base64编码,多了编码解码的开销。
- 根证书公钥放OTP:一次性可编程存储器,烧进去就改不了。这是最安全的做法。
- 证书链长度控制:我建议不超过3级。每多一级,Bootloader的验证时间就多一份,而且Flash占用也增加。
- 考虑证书吊销:虽然Bootloader里做CRL(证书吊销列表)检查比较奢侈,但至少要在固件更新流程中支持。
好了,这一节的内容就到这。PKI和证书链是安全启动的基石,理解透了,后面的章节就好办了。下一节我们聊具体的签名方案实现,到时候会用到今天讲的所有知识。