4、密钥生成与格式:使用openssl生成RSA/ECC密钥对、X.509证书格式、EFI Signature List(ESL)与认证变量(Authenticated Variable)的结构
Secure Boot 的安全根基建立在密钥体系之上。平台固件通过一组层次化的密钥(PK、KEK、db、dbx)来验证启动链路上每个组件的签名,而这些密钥的载体形式、编码规则与存储方式直接决定了安全策略能否被正确执行。本章聚焦于密钥生成与格式解析,涵盖 openssl 生成 RSA/ECC 密钥对、X.509 证书结构、EFI Signature List(ESL)的二进制布局,以及认证变量(Authenticated Variable)的封装机制。理解这些底层格式,是后续进行密钥烧录、证书替换和变量更新的前提。
4.1 使用 openssl 生成 RSA 与 ECC 密钥对
Secure Boot 的密钥对生成并不依赖专用硬件工具,openssl 命令行工具即可完成。RSA 与 ECC 的选择需要权衡兼容性与性能:RSA 在旧平台和第三方工具链中兼容性最好,而 ECC(尤其是 NIST P-256)在签名验证速度上更优,且密钥长度更短。实际项目中,PK(Platform Key)和 KEK(Key Exchange Key)通常使用 RSA-2048 或 RSA-3072,而 db/dbx 中的证书签名密钥可根据 OEM 策略选择 ECC。
核心操作流程
# 生成 RSA-2048 私钥(PK 或 KEK 使用)
openssl genrsa -out PK.key 2048
# 生成 ECC P-256 私钥(db 签名证书使用)
openssl ecparam -genkey -name prime256v1 -out db.key
# 从私钥提取公钥
openssl rsa -in PK.key -pubout -out PK.pub
openssl ec -in db.key -pubout -out db.pub
# 查看密钥详细信息
openssl rsa -in PK.key -text -noout | head -20
openssl ec -in db.key -text -noout | head -20
生成密钥时需注意:私钥文件权限必须设置为 600 或更严格,防止未授权读取;生产环境中建议在 HSM(硬件安全模块)或专用签名服务器中生成并保管私钥,开发阶段可使用本地文件,但需建立密钥管理台账。对于 ECC 密钥,openssl 默认输出 SEC1 格式,部分固件工具链可能需要 PKCS#8 格式,可通过 openssl pkcs8 -topk8 -nocrypt 转换。
4.2 X.509 证书格式与 Secure Boot 的映射
Secure Boot 中的 PK、KEK、db、dbx 均以 X.509 证书(或证书链)作为载体。X.509 v3 证书包含版本、序列号、签名算法、颁发者、有效期、主体、公钥信息及扩展项。在 Secure Boot 场景中,固件并不验证证书链的完整 CA 层级,而是直接比对证书的公钥或哈希值是否匹配预设的数据库条目。
| 证书字段 | Secure Boot 中的用途 | 典型值/说明 |
|---|---|---|
| Subject(主体) | 标识密钥所有者 | CN=OEM Platform Key, O=Example Inc. |
| Issuer(颁发者) | 自签名时与 Subject 相同 | 自签名证书中 Issuer = Subject |
| Subject Key Identifier (SKI) | 固件快速索引证书 | SHA-1 哈希公钥的 20 字节值 |
| Basic Constraints | 标记是否为 CA 证书 | PK/KEK 通常 CA:TRUE,db 证书可为 FALSE |
| Key Usage | 限制密钥用途 | digitalSignature, keyCertSign |
# 生成自签名 X.509 证书(PK 示例)
openssl req -new -x509 -key PK.key -out PK.crt -days 3650 \
-subj "/C=CN/O=Example Secure Boot/CN=Platform Key" \
-addext "basicConstraints=critical,CA:TRUE" \
-addext "keyUsage=critical,keyCertSign,cRLSign,digitalSignature"
# 查看证书内容
openssl x509 -in PK.crt -text -noout
固件在验证 db 中的证书时,通常提取证书的 SHA-256 哈希或公钥进行比对。因此,证书的序列号、有效期等元数据在 Secure Boot 校验中并不重要,但证书的签名算法必须与固件支持的算法一致(如 sha256WithRSAEncryption 或 ecdsa-with-SHA256)。
4.3 EFI Signature List(ESL)结构解析
EFI Signature List 是 UEFI 规范定义的二进制容器,用于将证书、哈希或签名打包成固件可解析的数据块。无论是 db、dbx 还是 KEK,其 NVRAM 变量内容均为 ESL 格式。理解 ESL 的字节布局是手工构造或解析变量的关键。
typedef struct {
EFI_GUID SignatureType; // 签名类型 GUID
UINT32 SignatureListSize; // 整个 ESL 结构大小(含本头)
UINT32 SignatureHeaderSize;// 签名头大小(通常为 0)
UINT32 SignatureSize; // 单个签名数据大小
} EFI_SIGNATURE_LIST;
typedef struct {
EFI_GUID SignatureOwner; // 所有者 GUID
UINT8 SignatureData[]; // 签名数据(证书/哈希)
} EFI_SIGNATURE_DATA;
常见的 SignatureType GUID 包括:EFI_CERT_SHA256_GUID(用于存储文件哈希)、EFI_CERT_X509_GUID(用于存储 DER 编码证书)、EFI_CERT_RSA2048_SHA256_GUID(用于存储 RSA 公钥)。构造 ESL 时,需要将证书 DER 编码后填入 SignatureData 字段,并正确计算各长度字段。
a5c059a1-94e4-4aa7-87b5-ab155c2bf072),SignatureListSize = 16(GUID)+ 4 + 4 + 4 + 16(Owner GUID)+ 800 = 844 字节,SignatureSize = 16 + 800 = 816 字节。使用 Python 的 struct 模块或 openssl 的 asn1parse 可辅助构造。4.4 认证变量(Authenticated Variable)的封装与写入
UEFI 变量分为普通变量和认证变量。Secure Boot 相关的 PK、KEK、db、dbx 必须使用认证变量机制写入,以防止未授权修改。认证变量的核心是 EFI_VARIABLE_AUTHENTICATION_2 结构,它包含时间戳和数字签名,固件在写入前会验证签名是否由当前 PK 或 KEK 对应的私钥签发。
typedef struct {
EFI_TIME TimeStamp; // 签名时间戳
WIN_CERTIFICATE_UEFI_GUID AuthInfo; // 证书类型 + 签名数据
} EFI_VARIABLE_AUTHENTICATION_2;
// AuthInfo 中的 CertType 通常为 EFI_CERT_TYPE_PKCS7_GUID
// 签名数据为待写入变量内容的 PKCS#7 签名(含签名者证书)
写入认证变量的流程如下:
- 构造变量数据(即 ESL 内容)和变量属性(如
EFI_VARIABLE_NON_VOLATILE | EFI_VARIABLE_BOOTSERVICE_ACCESS | EFI_VARIABLE_RUNTIME_ACCESS)。 - 计算待签名数据:
Attributes + DataSize + Data的拼接结果。 - 使用当前 PK 或 KEK 的私钥对上述数据做 PKCS#7 签名(附加签名者证书链)。
- 调用
SetVariable()或使用工具(如efi-updatevar、sbkeysync)写入 NVRAM。
4.5 案例拆解:从密钥生成到 ESL 封装的全流程
某 ODM 厂商需要为一批工控主板预置 Secure Boot 密钥。初始状态:主板固件处于 Setup 模式(PK 未设置)。目标:生成 PK、KEK、db 密钥并封装为 ESL,最终通过 UEFI Shell 写入。
环境约束:开发机为 Ubuntu 20.04,openssl 版本 1.1.1,目标固件仅支持 RSA-2048 和 SHA-256。操作前需确认固件版本支持 SetVariable 认证机制。
操作步骤:
# 1. 生成 PK 密钥与证书
openssl genrsa -out PK.key 2048
openssl req -new -x509 -key PK.key -out PK.crt -days 3650 \
-subj "/C=CN/O=ODM/CN=ODM PK" -addext "basicConstraints=critical,CA:TRUE"
# 2. 生成 KEK 密钥与证书
openssl genrsa -out KEK.key 2048
openssl req -new -x509 -key KEK.key -out KEK.crt -days 3650 \
-subj "/C=CN/O=ODM/CN=ODM KEK" -addext "basicConstraints=critical,CA:TRUE"
# 3. 生成 db 密钥与证书(用于签名 bootloader)
openssl genrsa -out db.key 2048
openssl req -new -x509 -key db.key -out db.crt -days 3650 \
-subj "/C=CN/O=ODM/CN=ODM db" -addext "basicConstraints=critical,CA:FALSE"
# 4. 将证书转为 DER 格式
openssl x509 -in PK.crt -outform DER -out PK.der
openssl x509 -in KEK.crt -outform DER -out KEK.der
openssl x509 -in db.crt -outform DER -out db.der
# 5. 使用 efi-updatevar 工具封装并写入(需在 UEFI Shell 或 Linux 下)
# 此处以 efi-updatevar 为例:
efi-updatevar -f PK.esl PK
efi-updatevar -f KEK.esl KEK
efi-updatevar -f db.esl db
结果验证:写入完成后,重启进入固件设置界面,确认 Secure Boot 状态从 Setup 模式切换为 User 模式,且 PK/KEK/db 条目均显示正确的证书信息。若写入失败,可通过串口日志查看 SetVariable 返回的 EFI_STATUS 码(如 EFI_SECURITY_VIOLATION 表示签名验证失败)。
复盘经验:本次操作中,最初使用 ECC P-256 生成 db 密钥,但目标固件不支持 ECDSA 签名验证,导致 db 更新失败。更换为 RSA-2048 后成功。建议在项目启动前查阅固件安全特性文档,确认支持的签名算法和密钥长度。
4.6 常见误区与总结
- 误区一:认为 X.509 证书的有效期会影响 Secure Boot 验证。实际上,固件只校验证书哈希或公钥,不检查有效期,但证书过期可能导致操作系统层面的验签失败。
- 误区二:混淆 ESL 中的 SignatureSize 与证书文件大小。SignatureSize 包含 SignatureOwner GUID 的长度,计算时容易遗漏 16 字节。
- 误区三:直接使用 openssl 生成的 PEM 证书写入 NVRAM。固件要求 DER 编码,必须转换格式。
- 误区四:忽略认证变量的时间戳要求,导致批量部署时变量更新被拒绝。
总结而言,Secure Boot 密钥管理的关键在于对格式的精确把控:密钥生成阶段选择合适的算法与长度,证书阶段确保 DER 编码和扩展项正确,ESL 封装阶段严格计算长度字段,认证变量写入阶段保证签名和时间戳有效。掌握这些底层细节,才能在生产环境中稳定地完成密钥烧录与安全配置管理。