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)的封装机制。理解这些底层格式,是后续进行密钥烧录、证书替换和变量更新的前提。

Secure Boot 密钥体系与格式链路 openssl 密钥生成 RSA-2048/3072 ECC P-256/384 X.509 证书 自签名 / CA 签发 Subject/Issuer/SKI EFI Signature List GUID + 签名列表 PKCS7 / 证书 / SHA256 认证变量 EFI_VARIABLE 时间戳+签名 ESL 内部结构(EFI_SIGNATURE_LIST + EFI_SIGNATURE_DATA) ┌─────────────────────────────────────────────────────────────────────┐ │ SignatureType GUID (16B) │ SignatureListSize (4B) │ HeaderSize (4B) │ │ SignatureHeaderSize (4B) │ SignatureSize (4B) │ 签名数据... │ └─────────────────────────────────────────────────────────────────────┘ 认证变量写入流程(UpdateVariable) ① 构造 EFI_VARIABLE_AUTHENTICATION_2 结构(时间戳 + 证书链) ② 对待写入数据(Attributes + DataSize + Data)进行 SHA256 摘要 ③ 使用私钥对摘要签名 → ④ 固件验证签名 → ⑤ 校验通过后写入 NVRAM

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 转换。

经验提醒:RSA-2048 在当前安全基线中仍被接受,但 NIST 已建议 2030 年后逐步淘汰 2048 位密钥。新项目建议直接使用 RSA-3072 或 ECC P-384,避免后续因安全策略升级而重新烧录 PK。

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 字段,并正确计算各长度字段。

实战场景:假设需要将 PK.crt(DER 格式,大小 800 字节)封装为 ESL。SignatureType 设为 EFI_CERT_X509_GUID(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 签名(含签名者证书)

写入认证变量的流程如下:

  1. 构造变量数据(即 ESL 内容)和变量属性(如 EFI_VARIABLE_NON_VOLATILE | EFI_VARIABLE_BOOTSERVICE_ACCESS | EFI_VARIABLE_RUNTIME_ACCESS)。
  2. 计算待签名数据:Attributes + DataSize + Data 的拼接结果。
  3. 使用当前 PK 或 KEK 的私钥对上述数据做 PKCS#7 签名(附加签名者证书链)。
  4. 调用 SetVariable() 或使用工具(如 efi-updatevar、sbkeysync)写入 NVRAM。
风险提示:认证变量的时间戳必须单调递增。如果新写入的变量时间戳早于当前 NVRAM 中的值,固件会拒绝更新。在批量部署或脚本自动化时,务必使用系统当前时间或 NTP 同步时间,避免因时间回拨导致密钥更新失败。

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 封装阶段严格计算长度字段,认证变量写入阶段保证签名和时间戳有效。掌握这些底层细节,才能在生产环境中稳定地完成密钥烧录与安全配置管理。