3、UEFI安全启动:Secure Boot原理,PK/KEK/db/dbx数据库,签名验证流程
好,咱们进入第三讲。Secure Boot,安全启动。
这名字听起来挺唬人,对吧?其实说白了,它就是一套「白名单」机制。UEFI 固件在加载任何操作系统引导程序(比如 bootloader)之前,先检查它的数字签名。签名在白名单里,就放行;不在,就拒绝。就这么简单。
但简单背后,是严谨的密码学支撑。我当年第一次接触这个,也觉得不就是个开关嘛,打开就行了。直到有一次,我在一台工控机上开了 Secure Boot,结果系统死活起不来……嗯,从那以后,我才开始认真研究它背后的数据库结构。
3.1 核心数据库:PK、KEK、db、dbx
Secure Boot 的核心,是四个存储在 NVRAM 里的数据库。它们的关系,像一把层层嵌套的锁。
| 数据库 | 全称 | 作用 | 谁持有私钥 |
|---|---|---|---|
| PK | Platform Key | 平台所有者,最高权限。控制 KEK 的更新。 | 设备厂商或最终用户 |
| KEK | Key Exchange Key | 操作系统或第三方。控制 db/dbx 的更新。 | 微软、各 Linux 发行版等 |
| db | Signature Database | 白名单。存放受信任的签名证书或哈希。 | 由 KEK 签名授权 |
| dbx | Forbidden Signature Database | 黑名单。存放已知恶意或已撤销的签名。 | 由 KEK 签名授权 |
你看这个结构,PK 在最顶层,它只用来签 KEK 的更新。KEK 再往下,签 db 和 dbx 的更新。db 里放的是你信任的证书,比如微软的证书、各大 Linux 发行版的证书。dbx 里放的是你不再信任的证书,比如某个被爆出漏洞的 bootloader 的签名。
一句话总结:PK 锁 KEK,KEK 锁 db/dbx,db/dbx 锁启动镜像。
3.2 签名验证流程:一步步拆解
流程不复杂,但每一步都有讲究。我习惯把它分成三个阶段:
- 固件加载阶段:UEFI 固件启动后,读取 NVRAM 中的 PK、KEK、db、dbx。
- 镜像验证阶段:当固件要加载一个 EFI 应用程序(比如 bootx64.efi)时,先提取它的数字签名。
- 决策阶段:固件拿着这个签名,依次去 dbx 和 db 里查。
具体怎么查?我画个流程图,你一看就明白。
看到了吗?关键点在于「先查黑名单,再查白名单」。dbx 的优先级高于 db。为什么这么设计?你想想看,如果一个证书同时出现在 db 和 dbx 里,那肯定是 dbx 说了算。这就像机场的禁飞名单,优先级永远高于普通乘客名单。
避坑指南:我曾经遇到过一台设备,Secure Boot 死活过不去。查了半天,发现是 dbx 里有一条哈希记录,正好匹配了那个 bootloader 的某个旧版本。但那个 bootloader 已经更新了,签名证书没变,只是代码变了。嗯,这就是哈希匹配的「误伤」。后来我们更新了 dbx,把那条哈希删了,问题解决。
3.3 签名验证的三种方式
UEFI 规范定义了三种验证方式,不是只有证书签名这一种。
| 验证方式 | 描述 | 典型场景 |
|---|---|---|
| 证书签名验证 | 镜像携带 X.509 证书和签名,固件用 db 中的根证书验证签名链。 | Windows Boot Manager、shim |
| 哈希验证 | db/dbx 中直接存放镜像的 SHA-256 哈希值。 | 特定版本的 bootloader 或驱动 |
| 镜像签名验证 | 镜像本身携带一个嵌入式的签名(Authenticated Variable)。 | UEFI 驱动、Option ROM |
我个人最常用的是证书签名验证。因为它灵活。你换一个同厂商的 bootloader 版本,只要证书链没变,就不用更新 db。哈希验证虽然更精确,但维护成本高——每次版本更新,都得更新 db。
3.4 实际部署中的注意事项
讲完原理,咱们聊聊落地。Secure Boot 不是打开开关就万事大吉的。
- PK 的保管:PK 私钥一旦丢失,你就永远无法更新 KEK 和 db/dbx 了。我建议把 PK 私钥放在离线 HSM 里,或者至少用密码保护的文件。
- KEK 的授权:微软的 KEK 证书是预置在大多数 UEFI 固件里的。如果你想用自己的操作系统,需要添加自己的 KEK。
- db 的预置:出厂时,db 里通常只有微软的证书。如果你要装 Linux,需要确保你的 bootloader(比如 shim)的证书在 db 里。
- dbx 的更新:当发现某个 bootloader 有安全漏洞时,厂商会发布 dbx 更新。这个更新必须由 KEK 签名。所以,KEK 的私钥也要保管好。
警告:千万不要在生产环境里把 Secure Boot 设置为「Setup Mode」(即 PK 为空)。在这种模式下,任何镜像都能启动,Secure Boot 形同虚设。我见过有人为了省事,在测试机上这么干,结果忘了改回来,直接部署到产线……嗯,后果很严重。
3.5 一个简单的验证示例
最后,给你看一个实际验证签名的伪代码逻辑。这不是真正的 UEFI 代码,但能帮你理解流程。
// 伪代码:Secure Boot 签名验证
EFI_STATUS VerifyImage(EFI_IMAGE_ENTRY_POINT Image) {
// 1. 提取镜像的签名信息
Signature = ExtractSignature(Image);
if (Signature == NULL) {
return EFI_SECURITY_VIOLATION; // 无签名,拒绝
}
// 2. 检查黑名单 (dbx)
for each (Entry in dbx) {
if (MatchSignature(Signature, Entry)) {
return EFI_SECURITY_VIOLATION; // 在黑名单中,拒绝
}
}
// 3. 检查白名单 (db)
for each (Entry in db) {
if (MatchSignature(Signature, Entry)) {
return EFI_SUCCESS; // 在白名单中,允许
}
}
// 4. 默认拒绝
return EFI_SECURITY_VIOLATION;
}
你看,逻辑就是这么直白。先查黑名单,再查白名单,都不匹配就拒绝。这个「默认拒绝」的设计,是安全的基本原则——白名单机制,永远比黑名单机制安全。