第二章:安全启动原理——PK、KEK、db、dbx数据库的作用与关系,签名验证流程
安全启动(Secure Boot)这玩意儿,说白了就是UEFI固件的一道锁。它确保你的电脑只运行经过签名的、可信的代码。我刚开始接触这个模块时,也觉得挺绕的——不就是几个数据库嘛,能有多复杂?后来在项目中踩过坑才明白,这几个数据库之间的关系,就像一把钥匙开一把锁,环环相扣。
2.1 四个核心数据库:PK、KEK、db、dbx
先记住一句话:PK是根,KEK是门卫,db是白名单,dbx是黑名单。嗯,这样理解就简单多了。
2.1.1 PK(Platform Key,平台密钥)
PK是整个安全启动体系的最高权限。它由平台所有者(通常是OEM厂商)持有。我个人习惯把PK比作「公司的公章」——只有盖了公章的文档才被认可。
- 作用:验证KEK的合法性。只有用PK签名的KEK才被信任。
- 存储位置:UEFI NVRAM变量中,变量名为
PK。 - 更新方式:只能用当前的PK私钥签名新的PK公钥来更新。
重要:PK一旦被清空或损坏,安全启动就会进入「设置模式」(Setup Mode),此时任何操作系统都可以启动。我在项目中遇到过客户误删PK导致整批设备需要返厂的情况……嗯,那场面挺尴尬的。
2.1.2 KEK(Key Exchange Key,密钥交换密钥)
KEK是PK的下级。它负责验证db和dbx的更新。说白了,KEK就是「部门经理」——公章(PK)授权它去管理具体的白名单和黑名单。
- 作用:验证db和dbx数据库的签名。
- 存储位置:UEFI NVRAM变量中,变量名为
KEK。 - 典型场景:微软的KEK通常预装在大多数PC中,用于管理Windows的启动加载器签名。
我的经验:调试时经常遇到KEK列表为空的情况。这时候安全启动会直接跳过KEK验证,进入「自定义模式」。你想想看,这其实是个安全隐患——所以生产环境中KEK绝对不能为空。
2.1.3 db(Allowed Database,允许数据库)
db是白名单。它存储了被允许启动的EFI应用程序、驱动程序和操作系统的签名或哈希值。
- 作用:只有签名在db中的镜像才能被加载。
- 存储位置:UEFI NVRAM变量中,变量名为
db。 - 内容示例:微软的Windows启动加载器签名、第三方驱动签名等。
2.1.4 dbx(Revoked Database,撤销数据库)
dbx是黑名单。它存储了被撤销的签名或哈希值。一旦某个签名被加入dbx,对应的镜像就永远无法启动了。
- 作用:阻止已知恶意或过期的签名镜像启动。
- 存储位置:UEFI NVRAM变量中,变量名为
dbx。 - 更新方式:通过UEFI固件更新或操作系统更新来添加新的撤销条目。
注意:dbx的更新是不可逆的。我曾经在调试时不小心把正确的签名加进了dbx,结果那个版本的驱动再也无法加载了……所以操作dbx时一定要三思而后行。
2.2 四个数据库的关系图
下面这张图展示了PK、KEK、db、dbx之间的层级关系。我习惯用「树形结构」来理解——PK是根,KEK是枝干,db和dbx是叶子。
2.3 签名验证流程
安全启动的签名验证流程,其实就是一个「逐级审查」的过程。我把它拆成四个步骤,你跟着走一遍就明白了。
步骤1:加载镜像
UEFI固件在启动时,会尝试加载EFI应用程序或驱动程序。比如bootmgfw.efi(Windows启动管理器)。
步骤2:检查dbx黑名单
固件先检查镜像的签名是否在dbx中。如果在,直接拒绝启动,并报错「Security Violation」。
关键点:dbx的优先级高于db。也就是说,即使镜像的签名在db白名单中,只要它也在dbx黑名单中,照样不能启动。我曾经在调试时忽略了这一点,浪费了整整两天时间……
步骤3:检查db白名单
如果镜像签名不在dbx中,固件会继续检查它是否在db中。如果在,允许启动。如果不在,进入下一步。
步骤4:检查KEK和PK
如果镜像签名既不在dbx也不在db中,固件会尝试用KEK和PK来验证镜像的签名链。如果验证通过,允许启动;否则拒绝。
用代码来表示这个流程,大概是这样的:
// 伪代码:安全启动验证流程
EFI_STATUS SecureBootVerifyImage(EFI_IMAGE *Image) {
// 1. 检查dbx
if (IsInDbx(Image->Signature)) {
return EFI_SECURITY_VIOLATION; // 黑名单,拒绝
}
// 2. 检查db
if (IsInDb(Image->Signature)) {
return EFI_SUCCESS; // 白名单,通过
}
// 3. 检查KEK和PK签名链
if (VerifySignatureChain(Image, KEK, PK)) {
return EFI_SUCCESS; // 签名链验证通过
}
return EFI_SECURITY_VIOLATION; // 全部失败,拒绝
}
2.4 实际调试中的注意事项
我在联调安全启动时,总结了几条经验,分享给你:
- 先确认模式:安全启动有「用户模式」和「设置模式」两种。在设置模式下,所有验证都会被跳过。很多调试问题其实是因为固件意外进入了设置模式。
- 检查NVRAM空间:PK、KEK、db、dbx都存储在NVRAM中。如果NVRAM空间不足,更新数据库会失败。我遇到过客户因为NVRAM溢出导致安全启动配置丢失的情况。
- 签名格式要正确:UEFI支持多种签名格式,包括X.509证书、RSA2048、SHA256哈希等。签名格式不匹配会导致验证失败。
- 注意大小端:UEFI使用的是小端字节序。如果你在调试时看到签名数据是反的,别慌——检查一下字节序。
我的调试技巧:在固件中加一个调试打印,把签名验证的每一步结果都输出到串口。这样你能清楚地看到是哪个环节出了问题。我曾经靠这个技巧,半小时就定位到了一个dbx条目冲突的问题。
2.5 总结
PK、KEK、db、dbx这四个数据库,构成了安全启动的信任链。PK是根信任,KEK是中间层,db和dbx是具体的执行层。签名验证流程就是沿着这条链逐级检查——先查黑名单,再查白名单,最后查签名链。
你想想看,这个设计其实挺巧妙的。它既保证了灵活性(可以通过更新db来添加新的可信镜像),又保证了安全性(dbx可以快速撤销有问题的签名)。
好了,这一章的内容就到这里。下一章我们会深入代码层面,看看这些数据库在UEFI固件中是怎么实现的。