EDK II固件签名工具:使用OpenSSL生成UEFI兼容签名

EDK II固件签名工具:使用OpenSSL生成UEFI兼容签名

【免费下载链接】edk2 EDK II 【免费下载链接】edk2 项目地址: https://gitcode.com/gh_mirrors/ed/edk2

引言:UEFI固件签名的重要性与挑战

在现代计算机系统中,固件(Firmware)作为硬件与操作系统之间的桥梁,其安全性直接关系到整个系统的可信启动(Trusted Boot)流程。UEFI(Unified Extensible Firmware Interface,统一可扩展固件接口)规范引入了严格的固件签名验证机制,以防止恶意代码在系统启动早期被执行。然而,许多开发者在实际操作中仍面临签名格式不兼容、工具链配置复杂等问题。本文将系统讲解如何使用OpenSSL工具生成符合UEFI规范的固件签名,并集成到EDK II(EFI Development Kit II)项目中,帮助开发者解决固件签名验证失败的痛点。

读完本文后,您将能够:

  • 理解UEFI固件签名的核心原理与PKCS#7格式要求
  • 使用OpenSSL生成符合UEFI规范的密钥对与证书链
  • 掌握EDK II中固件签名验证的实现逻辑
  • 完成自定义固件镜像的签名与验证全流程

UEFI固件签名基础

UEFI安全启动架构

UEFI安全启动(Secure Boot)是一种确保系统仅加载经过签名的可信固件和操作系统组件的机制。其核心依赖于公钥基础设施(Public Key Infrastructure,PKI),通过验证固件镜像的数字签名来判断其合法性。

mermaid

UEFI签名格式要求

UEFI规范要求固件签名必须采用PKCS#7(Public-Key Cryptography Standards #7)格式,也称为CMS(Cryptographic Message Syntax)。这种格式支持对固件镜像进行数字签名,并包含必要的证书信息,以便验证者确认签名者的身份。

在EDK II中,与PKCS#7签名验证相关的核心函数包括:

BOOLEAN
Pkcs7Verify (
  IN  CONST UINT8  *Pkcs7Data,
  IN  UINTN        Pkcs7DataSize,
  IN  CONST UINT8  *TrustedCert,
  IN  UINTN        TrustedCertSize,
  IN  CONST UINT8  *Data,
  IN  UINTN        DataSize
  );

该函数实现于SecurityPkg中,用于验证给定数据的PKCS#7签名是否有效。其参数包括PKCS#7签名数据、可信证书、待验证数据及其大小。

使用OpenSSL生成UEFI兼容签名

环境准备

在开始之前,请确保您的系统中已安装以下工具:

  • OpenSSL 1.1.1或更高版本
  • EDK II开发环境
  • Git(用于获取EDK II源代码)

首先,克隆EDK II仓库:

git clone https://gitcode.com/gh_mirrors/ed/edk2.git
cd edk2
git submodule update --init

生成密钥对与证书

步骤1:生成私钥

使用以下命令生成2048位RSA私钥:

openssl genrsa -out uefi_private.key 2048
步骤2:创建证书签名请求(CSR)
openssl req -new -key uefi_private.key -out uefi_cert.csr \
  -subj "/CN=UEFI Firmware Signing Key/O=Your Organization/OU=Firmware Team/L=Your City/ST=Your State/C=Your Country"
步骤3:自签名证书

由于UEFI安全启动通常使用自签名证书(在开发环境中),我们可以直接用私钥对CSR进行签名,生成自签名证书:

openssl x509 -req -days 3650 -in uefi_cert.csr -signkey uefi_private.key -out uefi_cert.pem
步骤4:转换证书格式

UEFI固件通常期望证书以DER(Distinguished Encoding Rules)格式存储,而非PEM(Privacy-Enhanced Mail)格式。使用以下命令进行转换:

openssl x509 -in uefi_cert.pem -out uefi_cert.der -outform DER

对固件镜像进行签名

假设我们有一个名为firmware.bin的固件镜像需要签名,使用以下步骤生成PKCS#7签名:

步骤1:生成固件镜像的哈希值

UEFI签名通常对固件镜像的哈希值进行签名,而非直接对整个镜像签名。计算SHA256哈希:

openssl dgst -sha256 -binary -out firmware.sha256 firmware.bin
步骤2:生成PKCS#7签名
openssl cms -sign -in firmware.sha256 -inkey uefi_private.key -signer uefi_cert.pem \
  -out firmware.sig -outform DER -nosmimecap -binary -digest sha256

这里:

  • -sign: 指定进行签名操作
  • -in: 输入文件(固件哈希值)
  • -inkey: 私钥文件
  • -signer: 签名者证书
  • -out: 输出签名文件
  • -outform DER: 指定输出格式为DER
  • -nosmimecap: 不添加S/MIME功能扩展
  • -binary: 处理二进制数据
  • -digest sha256: 使用SHA256哈希算法
步骤3:组合固件镜像与签名

在UEFI中,签名通常附加在固件镜像之后,形成一个包含镜像数据和签名的完整文件。可以使用以下命令将固件镜像和签名组合:

cat firmware.bin firmware.sig > firmware_signed.bin

EDK II中的签名验证实现

EDK II签名验证核心代码分析

在EDK II中,SecurityPkg提供了PKCS#7签名验证的实现。以下是FmpAuthenticatedHandlerPkcs7函数的核心代码片段,该函数用于处理FMP(Firmware Management Protocol)胶囊镜像的PKCS#7签名验证:

RETURN_STATUS
FmpAuthenticatedHandlerPkcs7 (
  IN EFI_FIRMWARE_IMAGE_AUTHENTICATION  *Image,
  IN UINTN                              ImageSize,
  IN CONST UINT8                        *PublicKeyData,
  IN UINTN                              PublicKeyDataLength
  )
{
  RETURN_STATUS  Status;
  BOOLEAN        CryptoStatus;
  VOID           *P7Data;
  UINTN          P7Length;
  VOID           *TempBuffer;

  P7Length = Image->AuthInfo.Hdr.dwLength - (OFFSET_OF (WIN_CERTIFICATE_UEFI_GUID, CertData));
  P7Data   = Image->AuthInfo.CertData;

  // 分配临时缓冲区,用于组合待验证数据和单调计数器
  TempBuffer = AllocatePool (ImageSize - Image->AuthInfo.Hdr.dwLength);
  if (TempBuffer == NULL) {
    return RETURN_OUT_OF_RESOURCES;
  }

  // 复制固件镜像数据
  CopyMem (
    TempBuffer,
    (UINT8 *)Image + sizeof (Image->MonotonicCount) + Image->AuthInfo.Hdr.dwLength,
    ImageSize - sizeof (Image->MonotonicCount) - Image->AuthInfo.Hdr.dwLength
    );

  // 附加单调计数器(UEFI规范要求)
  CopyMem (
    (UINT8 *)TempBuffer + (ImageSize - sizeof (Image->MonotonicCount) - Image->AuthInfo.Hdr.dwLength),
    &Image->MonotonicCount,
    sizeof (Image->MonotonicCount)
    );

  // 执行PKCS#7签名验证
  CryptoStatus = Pkcs7Verify (
                   P7Data,
                   P7Length,
                   PublicKeyData,
                   PublicKeyDataLength,
                   (UINT8 *)TempBuffer,
                   ImageSize - Image->AuthInfo.Hdr.dwLength
                   );
  FreePool (TempBuffer);

  if (!CryptoStatus) {
    return RETURN_SECURITY_VIOLATION;
  }

  return RETURN_SUCCESS;
}

关键数据结构

UEFI固件签名验证涉及以下关键数据结构:

  1. EFI_FIRMWARE_IMAGE_AUTHENTICATION:包含固件镜像的认证信息,定义于MdePkg/Include/Guid/FirmwareAuthentication.h
typedef struct {
  UINT64                          MonotonicCount;
  WIN_CERTIFICATE_UEFI_GUID       AuthInfo;
} EFI_FIRMWARE_IMAGE_AUTHENTICATION;
  1. WIN_CERTIFICATE_UEFI_GUID:UEFI扩展的Windows证书格式,用于携带PKCS#7签名数据。
typedef struct {
  WIN_CERTIFICATE                 Hdr;
  EFI_GUID                        CertType;
  UINT8                           CertData[1];
} WIN_CERTIFICATE_UEFI_GUID;

其中,CertType字段通常设置为gEfiCertPkcs7Guid,表示证书数据为PKCS#7格式。

集成签名验证到EDK II项目

配置EDK II项目

要在EDK II项目中启用签名验证,需要在平台的DSC(Distribution Package Description)文件中包含SecurityPkg并配置相关PCD(Platform Configuration Database):

[Components]
  SecurityPkg/Library/FmpAuthenticationLibPkcs7/FmpAuthenticationLibPkcs7.inf

[PcdsFixedAtBuild]
  gEfiSecurityPkgTokenSpaceGuid.PcdOptionRomImageVerificationPolicy|0x02
  gEfiSecurityPkgTokenSpaceGuid.PcdDriverSignatureVerificationPolicy|0x02

添加可信证书

将之前生成的uefi_cert.der证书添加到EDK II项目中,通常放置在SecurityPkg/Library/PlatformPKProtectionLib/目录下,并在INF文件中引用:

[Binaries.common]
  FILE|uefi_cert.der|RAW|$(PLATFORM_PACKAGE)/Certificates/

实现证书验证逻辑

在平台初始化代码中,注册可信证书并启用签名验证:

EFI_STATUS
EFIAPI
PlatformInitializeSecurity (
  IN EFI_HANDLE        ImageHandle,
  IN EFI_SYSTEM_TABLE  *SystemTable
  )
{
  EFI_STATUS  Status;
  UINT8       *TrustedCert;
  UINTN       TrustedCertSize;

  // 从固件中读取可信证书
  Status = ReadTrustedCertificate (&TrustedCert, &TrustedCertSize);
  if (EFI_ERROR (Status)) {
    return Status;
  }

  // 注册可信证书到安全管理器
  Status = SystemTable->RuntimeServices->SetVariable (
                                           L"PK",
                                           &gEfiGlobalVariableGuid,
                                           EFI_VARIABLE_NON_VOLATILE | EFI_VARIABLE_BOOTSERVICE_ACCESS | EFI_VARIABLE_RUNTIME_ACCESS | EFI_VARIABLE_AUTHENTICATED_WRITE_ACCESS,
                                           TrustedCertSize,
                                           TrustedCert
                                           );
  FreePool (TrustedCert);

  return Status;
}

常见问题与解决方案

签名验证失败的排查流程

当固件签名验证失败时,可按以下步骤排查:

mermaid

常见错误及解决方法

  1. 错误:PKCS7Verify返回FALSE

    • 原因:签名数据损坏或证书不匹配
    • 解决:重新生成签名,确保使用正确的私钥和证书
  2. 错误:证书格式不受支持

    • 原因:证书不是DER格式或使用了不受支持的加密算法
    • 解决:使用openssl x509命令将证书转换为DER格式,确保使用RSA算法
  3. 错误:单调计数器值不匹配

    • 原因:固件镜像中的单调计数器值与签名时使用的值不一致
    • 解决:确保在签名时包含正确的单调计数器值

总结与展望

本文详细介绍了UEFI固件签名的原理、使用OpenSSL生成符合UEFI规范签名的方法,以及EDK II中签名验证的实现。通过遵循这些步骤,开发者可以确保其固件镜像能够通过UEFI安全启动验证,从而提高系统的安全性。

随着UEFI规范的不断演进,固件签名机制也在不断发展。未来,我们可以期待更先进的签名算法(如ECC椭圆曲线加密)和更严格的安全策略在UEFI中的应用。作为开发者,持续关注UEFI规范更新和安全最佳实践至关重要。

参考资料

  1. UEFI Specification Version 2.9, Intel Corporation, 2021
  2. EDK II Developer's Guide, TianoCore Project
  3. OpenSSL Cryptography and TLS Toolkit, OpenSSL Project
  4. "Trusted Boot in UEFI" by Michael Brown, 2018
  5. TianoCore SecurityPkg Documentation, https://github.com/tianocore/edk2/tree/master/SecurityPkg

【免费下载链接】edk2 EDK II 【免费下载链接】edk2 项目地址: https://gitcode.com/gh_mirrors/ed/edk2

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值