4. 安全启动的签名机制:数字签名原理、Authenticode签名、EFI签名工具

安全启动的核心,说白了就是「谁签的名,谁负责」。

UEFI固件在加载一个EFI程序之前,会先检查它的数字签名。签名合法,才放行。签名不对,直接拒掉。这个机制听起来简单,但实际落地时坑不少。我这些年排查过的安全启动故障,至少有一半都跟签名有关——要么是签名工具用错了,要么是证书链没配全。

4.1 数字签名原理:你不需要懂全部数学,但得懂流程

数字签名不是加密,它解决的是「谁写的」和「被改过没」这两个问题。

流程分三步:

  1. 哈希摘要:对原始文件(比如bootmgfw.efi)做一次哈希运算,得到一个固定长度的摘要。
  2. 私钥加密:用签名者的私钥对这个摘要加密,生成签名数据。
  3. 打包嵌入:把签名数据、签名者证书、可能的中间CA证书一起塞进文件里。

验证时,固件用公钥解密签名,拿到原始摘要,再对文件重新算一次哈希。两个摘要一致,说明文件没被篡改,签名者身份也确认了。

关键点:公钥必须提前被固件信任。安全启动的信任链,就是从固件内置的「允许签名数据库」(DB)开始的。

我在项目中遇到过一个问题:明明签名验证通过了,但启动还是失败。后来发现是固件里DB证书过期了。嗯,证书有效期也是个容易忽略的点。

4.2 Authenticode签名:PE文件的专属签名格式

Windows下的EFI文件,本质上是PE格式。微软为PE文件定制了一套签名规范,叫Authenticode。

Authenticode签名不是简单地把签名数据塞进文件尾部,它有一套严格的嵌入规则:

  • 签名数据放在PE文件的Certificate Table目录指向的位置。
  • 签名计算时,需要跳过PE头里的CheckSum字段和Certificate Table目录本身。
  • 支持嵌套签名(多个签名同时存在),但安全启动只认第一个有效的。
签名类型 适用场景 验证方式
Embedded签名 单个EFI文件 固件直接解析PE头,提取签名
Catalog签名 驱动包、多个文件 通过安全编录(.cat)文件间接验证

我的经验:如果你自己编译了一个EFI应用,想用安全启动加载它,最简单的方式就是用Embedded签名。Catalog签名适合批量分发场景,但配置起来麻烦不少。

4.3 EFI签名工具:SignTool vs sbsign

签名工具分两大阵营:Windows生态的SignTool,和Linux/开源生态的sbsign。两个我都用过,各有各的脾气。

4.3.1 SignTool(Windows SDK自带)

SignTool是微软官方工具,支持Authenticode签名。基本用法:

signtool sign /fd SHA256 /a /s MY /n "MyCompany" /t http://timestamp.digicert.com bootmgfw.efi

参数说明:

  • /fd SHA256:指定哈希算法。安全启动强制要求SHA256,别用SHA1。
  • /a:自动选择最佳签名证书。
  • /s MY:从当前用户的MY证书存储区找证书。
  • /n:按证书主题名称筛选。
  • /t:添加时间戳。这个很重要——证书过期后,时间戳能证明签名是在有效期内做的。

避坑指南:我曾经因为忘了加/fd SHA256,默认用了SHA1,结果固件直接报「签名算法不支持」。SHA1在安全启动里已经被废弃了,千万别用。

4.3.2 sbsign(Linux下的efitools)

如果你在Linux环境下开发UEFI应用,sbsign是首选。它来自efitools包,用法更简洁:

sbsign --key mykey.key --cert mycert.pem --output signed.efi unsigned.efi

参数说明:

  • --key:私钥文件(PEM格式)。
  • --cert:签名证书(PEM格式)。
  • --output:输出签名后的文件。

sbsign生成的签名格式与Authenticode兼容,固件能直接识别。我习惯在CI/CD流水线里用sbsign,因为它不需要图形界面,脚本化非常方便。

特性 SignTool sbsign
平台 Windows Linux
签名格式 Authenticode Authenticode兼容
时间戳支持 原生支持 需额外工具
证书管理 Windows证书存储 文件系统PEM
自动化友好度 中等 高

4.4 签名验证流程:固件到底怎么查?

固件验证签名的顺序,我画了张图帮你理解:

安全启动签名验证流程 加载EFI文件 解析PE头,定位Certificate Table 提取签名数据与证书 验证证书链是否在DB中 哈希比对:通过则加载,否则拒绝 注意点 证书链必须完整 中间CA也要在DB中 常见失败原因 证书过期 / 哈希算法不匹配

流程看起来不复杂,但实际调试时,固件不会告诉你「证书链断了」还是「哈希不匹配」。它只会抛一个笼统的错误码,比如SECURITY_VIOLATION。这时候就得靠经验去猜了。

我的调试习惯:先用sbverify(efitools里的另一个工具)在操作系统里验证签名是否合法。如果sbverify报错,那固件肯定也过不了。省得反复重启机器。

4.5 常见签名故障与排查思路

  • 签名算法不匹配:固件只认SHA256,你用了SHA1。用SignTool时务必加/fd SHA256。
  • 证书链不完整:签名证书的签发者(CA)不在固件的DB里。要么把CA证书导入DB,要么用自签名证书并直接导入DB。
  • 证书过期:如果签名时没加时间戳,证书过期后固件会拒掉。加时间戳能解决这个问题。
  • 文件被二次签名:有些工具会在已有签名上再盖一个签名,导致固件解析混乱。签名前先检查文件是否已有签名。

我曾经踩过的坑:有一次客户反馈说「同一个EFI文件,在A机器上能启动,在B机器上就报安全启动错误」。排查了半天,发现B机器的固件DB里缺少中间CA证书。所以,证书链的完整性不光取决于签名方,还取决于目标机器的固件配置。

签名机制是安全启动的基石。理解它,你才能知道固件在「想什么」。下一层,我们会深入DB/DBX的配置,看看怎么管理这些信任证书。