跳转到内容

UEFI/安全启动

来自 Arch Linux 中文维基
< UEFI

安全启动(Secure Boot)UEFI标准中的一项安全功能,旨在为pre-boot process添加一层保护;通过维护被授权或禁止的在启动时运行的经过加密签署的二进制文件列表,它有助于使得核心引导组件(引导管理器、内核、initramfs)不被篡改。

因此,它可以被视为对个人使用环境的securing所做努力的延续或补充,减少其他软件安全解决方案(如系统加密)无法轻易覆盖的攻击面,同时完全不同且不依赖于它们。安全启动只是作为当前安全实践的一个组成部分独立存在,具有自己的一套优缺点

注意:为了更深入地了解Linux中的安全启动。见Rodsbooks' Secure Bootother online resources。文章重点介绍了如何在 Arch Linux 中设置安全启动。

检查安全启动状态

在启动操作系统之前

此时,必须查看固件设置。如果机器已启动并正在运行,则在大多数情况下必须重新启动。

您可以在引导过程中通过按特殊键来访问固件配置。使用的特殊键取决于固件。它通常是EscF2Del或者Fn键中的一个。有时在启动过程中特殊键的名称会显示一小段时间。主板说明书往往有关于此的记载。如果你想要按下特殊键,请在启动机器后立即按下该键,甚至是在屏幕实际显示任何内容之前。 进入固件设置后,请注意不要擅自更改任何设置。 通常在每个设置的底部都会有导航说明和设置的简短帮助。设置本身可能由几个页面组成,您必须导航到正确的位置。有一些安全启动设置可能简单地表示为安全启动选项,可以将其设置为打开或关闭 .

在启动操作系统之后

使用systemd检查系统上的安全启动状态的一种简单方法是使用bootctl(1)

注意:以下命令,并非要求你强制使用systemd-boot,作为你的引导启动管理器,才能正确让你得到你需要的内容(安全启动是否开启),它更类似于其他*ctl systemd实用程序(localectl、timedatectl……),此处即为有关启动事项的综合管理工具,并且不会影响你的配置。
$ bootctl
System:
      Firmware: UEFI 2.80 (American Megatrends 5.26)
 Firmware Arch: x64
   Secure Boot: enabled (user)
  TPM2 Support: yes
  Measured UKI: yes
  Boot into FW: supported
...

在这里,我们看到安全启动已启用并强制执行用户模式(user mode)。Secure Boot的其他值包括disabled,Setup Mode的其他值包括setup[1]

检查机器是否使用安全启动的另一种方法是使用以下命令:

$ od --address-radix=n --format=u1 /sys/firmware/efi/efivars/SecureBoot-8be4df61-93ca-11d2-aa0d-00e098032b8c

如果启用了安全启动,此命令将返回1作为五个列表中的最后一个整数,例如:

6  0  0  0  1

但是请注意,如果使用了功能缺失的引导加载程序,内核可能不知道安全引导(即使它在固件中启用)。 这可以通过在系统启动后不久检查内核消息来验证:

# journalctl -kg 'secure boot'
kernel: Secure boot disabled
kernel: Secure boot could not be determined

能启用的内核会打印Secure boot enabled

引导安装介质

官方安装镜像不支持安全启动(archlinux/archiso#69)。对于安全启动的支持最早在 archlinux-2013.07.01-dual.iso 中引入,但随后在 archlinux-2016.06.01-dual.iso 中被移除。当时,prebootloader 被替换为了 efitools,尽管后者使用的是未签名的 EFI 二进制文件。自那之后,官方安全介质就再未支持过安全启动。

为了在开启安全启动的系统上引导安装介质,你需要禁用安全启动,或者修改镜像以添加已签名的引导加载程序(Bootloader)。

Archboot 镜像提供了一种在安装介质中使用安全启动的方式。

禁用安全启动

可以通过 UEFI 固件接口禁用安全启动功能。如何访问固件配置请访问#在启动操作系统之前.

如果使用热键不起作用并且您可以启动 Windows,您可以通过以下方式强制重新启动到固件配置(对于 Windows 10):设置 > 更新和安全 > 恢复 > 高级启动(立即重新启动)> 故障排除 > 高级选项 > UEFI 固件设置 > 重启

请注意,某些主板(Packard Bell 笔记本电脑就是这种情况)只有在您设置了管理员密码(之后可以删除)时才允许禁用安全启动。 另请参阅 Rod Smith 的禁用安全启动

重新打包安装镜像

参见#ISO 重新打包

编辑安装介质

如果使用的是 U 盘安装介质,则可以通过手动编辑介质上的 EFI 系统分区来添加对安全启动的支持。

插入 U 盘,然后挂载该分区:

# mount /dev/disk/by-label/ARCHISO_EFI /mnt

接着按照使用自己的密钥中的说明安装任意已签名的 Bootloader。例如,若要安装 #PreLoader

# mv /mnt/EFI/BOOT/BOOTx64.EFI /mnt/EFI/BOOT/loader.efi
# cp /usr/share/preloader-signed/PreLoader.efi /mnt/EFI/BOOT/BOOTx64.EFI
# cp /usr/share/preloader-signed/HashTool.efi /mnt/EFI/BOOT/HashTool.efi

实施安全启动

为了确保理想的安全启动设置,有以下几个特定条件:

  1. UEFI基本被认为是可信的(尽管有一些众所周知的批评和漏洞[2]),且必须被强密码保护
  2. 不使用默认的制造商或第三方密钥,因为它们被证明会大大削弱安全启动的安全模型[3]
  3. UEFI直接加载用户签名的、兼容EFI boot stub英语EFI boot stub统一内核镜像(无需引导加载程序),包含微码(如果适用)以及 initramfs。以便在整个启动过程维持安全启动建立的信任链并减少攻击面。
  4. 使用完整驱动器加密,这样在创建内核镜像和签署的过程中所涉及的工具与文件就无法被具有机器物理访问权限的人访问并篡改。
  5. 可以使用TPM(可信平台模块)获得进一步的提升。

#使用自己的密钥中描述了一个简单且完全自力更生的设置,而#使用已签署的引导加载程序使用了第三方签署的中间工具。

使用 GRUB 引导加载程序在启用安全启动前需要额外的步骤,参见GRUB#启用安全启动

使用自己的密钥

警告:使用自定义密钥替换平台密钥(PK)会瘫痪某些机器(比如笔记本)上的硬件,甚至无法进入UEFI/BIOS设置恢复。这是因为某些硬件(例如显卡)的固件(OpROMs)在启动时微软使用微软第三方 CA 证书进行签署。许多联想 Thinkpad X、P 和 T 系列笔记本电脑以及华硕 TUF Gaming 笔记本电脑均属于这种情况。

实现安全启动需要下列密钥:

平台密钥 Platform Key (PK):最高级的密钥。
密钥交换密钥 Key Exchange Key (KEK):用于签署签名数据库和禁止的签名数据库更新的密钥。
签名数据库 Signature Database (db):包含密钥和/或被允许的EFI二进制文件的散列值。
禁止的签名数据库 Forbidden Signatures Database (dbx):包含密钥和/或被禁止的EFI二进制文件的散列值。

详细解释请阅读所有UEFI密钥的含义

为了使用安全启动,你至少需要PKKEKdb密钥。 虽然你可以添加多个KEK,db和dbx证书,但只允许一个平台密钥(PK)。

一旦安全启动处于“用户模式”,密钥只能通过用更高级别的密钥签署(使用sign-efi-sig-list)来更新。平台密钥可以自己签署。

确定 OpROM 签名

按以下步骤检查是否拥有任何已签名的 OpROM。

列出所有带 OpROM 的设备:

$ find /sys/devices/ -name rom
/sys/devices/pci0000:80/0000:80:01.0/0000:81:00.0/rom
...

以上展示了一个带有已签名 EFI ROM 的 Intel DC SSD 示例。对每个设备重复以下步骤。

$ lspci -s 81:00.0
81:00.0 Non-Volatile memory controller: Intel Corporation PCIe Data Center SSD (rev 01)

转储(Dump)ROM 内容:

# echo 1 > /sys/devices/pci0000:80/0000:80:01.0/0000:81:00.0/rom
# cat /sys/devices/pci0000:80/0000:80:01.0/0000:81:00.0/rom > ~/intel_ssd.rom
# echo 0 > /sys/devices/pci0000:80/0000:80:01.0/0000:81:00.0/rom

获取并编译 TianoCore 的 EDK II 工具

$ make -C BaseTools

解析 ROM 文件:

$ ./BaseTools/Source/C/bin/EfiRom -d ~/intel_ssd.rom
Image 1 -- Offset 0x0
  ROM header contents
    Signature              0xAA55
    PCIR offset            0x001C
    Signature               PCIR
    Vendor ID               0x8086
    Device ID               0x0953
    Length                  0x001C
    Revision                0x0003
    DeviceListOffset        0x00
    Class Code              0x000000
    Image size              0x6A00
    Code revision:          0x0000
    MaxRuntimeImageLength   0x00
    ConfigUtilityCodeHeaderOffset 0x00
    DMTFCLPEntryPointOffset 0x00
    Indicator               0x80   (last image)
    Code type               0x03   (EFI image)
  EFI ROM header contents
    EFI Signature          0x0EF1
    Compression Type       0x0001 (compressed)
    Machine type           0x8664 (X64)
    Subsystem              0x000B (EFI boot service driver)
    EFI image offset       0x0038 (@0x38)

结果显示,该 OpROM 在偏移量(Offset)0x0 处的 Image 1 偏移量 0x0038 位置包含一个压缩的 EFI 镜像。提取该 EFI 区域:


$ dd if=intel_ssd.rom ibs=1 skip=$(( 0x00 + 0x38 )) of=intel_ssd.rom.efi.compressed

解压缩该镜像:

$ ./BaseTools/Source/C/bin/TianoCompress -d --uefi -v ~/intel_ssd.rom.efi.compressed -o ~/intel_ssd.rom.efi
$ file ../intel_ssd.rom.efi
../intel_ssd.rom.efi: PE32+ executable for EFI (boot service driver), x86-64, 6 sections

使用 sbsigntools 检查签名,可以看到它是使用微软的 DB 证书签名的:

$ sbverify --list ~/intel_ssd.rom.efi
signature 1
image signature issuers:
 - /C=US/ST=Washington/L=Redmond/O=Microsoft Corporation/CN=Microsoft Corporation UEFI CA 2011
image signature certificates:
 - subject: /C=US/ST=Washington/L=Redmond/O=Microsoft Corporation/OU=MOPR/CN=Microsoft Windows UEFI Driver Publisher
   issuer:  /C=US/ST=Washington/L=Redmond/O=Microsoft Corporation/CN=Microsoft Corporation UEFI CA 2011
 - subject: /C=US/ST=Washington/L=Redmond/O=Microsoft Corporation/CN=Microsoft Corporation UEFI CA 2011
   issuer:  /C=US/ST=Washington/L=Redmond/O=Microsoft Corporation/CN=Microsoft Corporation Third Party Marketplace Root
警告:为了防止设备软变砖(Soft brick),如果发现显卡带有已签名的 ROM,请确保在录入自定义密钥后,你的 Secure Boot DB(允许数据库)中依然保留了必要签发者(Issuers)证书。

备份当前变量

在创建新的密钥和修改EFI变量之前,建议备份当前的变量,以便在发生错误时可以恢复。

安装 efitools 软件包后,运行以下命令来备份所有四个主要的安全启动变量。

$ for var in PK KEK db dbx ; do efi-readvar -v $var -o old_${var}.esl ; done

如果你在新电脑或主板上执行这条命令,你提取的变量很可能是微软提供的。

创建密钥

手动流程

要生成密钥,请执行以下步骤。

你将需要多种格式的私钥和证书。

.key
PEM格式的私钥,用于签署EFI二进制和EFI签名列表。
.crt
PEM格式的证书,用于sbsign(1)sbvarsign(1)sign-efi-sig-list(1)
.cer
DER格式的证书,用于固件。
.esl
EFI签名列表的证书,用于sbvarsign(1)efi-updatevar(1)KeyTool与固件。
.auth
EFI签名列表中的证书,带有认证头(即签名的证书更新文件),用于efi-updatevar(1)sbkeysyncKeyTool与固件。

创建一个GUID用于自我识别:

$ uuidgen --random > GUID.txt

平台密钥:

$ openssl req -newkey rsa:4096 -nodes -keyout PK.key -new -x509 -sha256 -days 3650 -subj "/CN=my Platform Key/" -out PK.crt
$ openssl x509 -outform DER -in PK.crt -out PK.cer
$ cert-to-efi-sig-list -g "$(< GUID.txt)" PK.crt PK.esl
$ sign-efi-sig-list -g "$(< GUID.txt)" -k PK.key -c PK.crt PK PK.esl PK.auth

签署一个空白文件用来在“用户模式”中移除平台密钥:

$ sign-efi-sig-list -g "$(< GUID.txt)" -c PK.crt -k PK.key PK /dev/null no_PK.auth

密钥交换密钥:(生成KEK.auth的指令没有加-a,所以文件只能用来update KEK实现替换。如果要生成用于add添加的KEK.auth,则需要在指令里添加-a,同理db等。PK只能有一个所以直接替换就好,删除的话用本页面介绍的指令生成的rm_PK.auth。)

$ openssl req -newkey rsa:4096 -nodes -keyout KEK.key -new -x509 -sha256 -days 3650 -subj "/CN=my Key Exchange Key/" -out KEK.crt
$ openssl x509 -outform DER -in KEK.crt -out KEK.cer
$ cert-to-efi-sig-list -g "$(< GUID.txt)" KEK.crt KEK.esl
$ sign-efi-sig-list -g "$(< GUID.txt)" -k PK.key -c PK.crt KEK KEK.esl KEK.auth

签名数据库密钥:

$ openssl req -newkey rsa:4096 -nodes -keyout db.key -new -x509 -sha256 -days 3650 -subj "/CN=my Signature Database key/" -out db.crt
$ openssl x509 -outform DER -in db.crt -out db.cer
$ cert-to-efi-sig-list -g "$(< GUID.txt)" db.crt db.esl
$ sign-efi-sig-list -g "$(< GUID.txt)" -k KEK.key -c KEK.crt db db.esl db.auth
辅助脚本

关于这个主题的参考页[4]的作者提供了一个辅助/快捷脚本(需要python)。一个轻度修改的版本也被打包为sbkeysAUR

为了使用它,请在安全位置创建一个文件夹(比如/etc/efi-keys/,如果你接下来计划统一内核镜像的创建与签署自动化过程)并且运行:

# mkdir /etc/efi-keys
# cd !$
# curl -L -O https://www.rodsbooks.com/efi-bootloaders/mkkeys.sh
# chmod +x mkkeys.sh
# ./mkkeys.sh
<输入一个通用名,以嵌入至密钥中,比如 "Secure Boot">

这将产生所需的不同格式的文件。

更新密钥

一旦安全启动进入“用户模式”,任何对KEK、db以及dbx的改变都需要更高级的密钥签署。

比如,如果你想要更新你的db密钥:

  1. 创建新的密钥
  2. 转换其为EFI签名列表,
  3. 签署EFI签名列表,
  4. 登记已签署的证书更新文件。
$ cert-to-efi-sig-list -g "$(< GUID.txt)" new_db.crt new_db.esl
$ sign-efi-sig-list -g "$(< GUID.txt)" -k KEK.key -c KEK.crt db new_db.esl new_db.auth

如果你想添加另一个密钥至签名数据库中且不替换你的db密钥,你需要使用选项-a(请看sign-efi-sig-list(1)):

$ sign-efi-sig-list -a -g "$(< GUID.txt)" -k KEK.key -c KEK.crt db new_db.esl new_db.auth

new_db.auth被创建,登记它

签署EFI二进制文件

安全启动激活时(比如在“用户模式”中),只有已被签署的EFI二进制文件(比如应用程序、驱动统一内核镜像)可以被启动。

使用sbsigntools手动操作

安装sbsigntools来使用sbsign(1)签署EFI二进制文件。

提示:
  • 为了检查一个二进制文件是否被签署并列出它的签名,使用sbverify --list /path/to/binary
  • rEFInd启动管理器的refind-install脚本可以签署rEFInd的EFI二进制文件并把它们与db证书一起拷贝到ESP中。 请参考rEFInd#使用你自己的密钥
注意:如果不带--output使用sbsign的话,生成文件将会是filename.signed。详情请看sbsign(1)

使用sbsign来签署你的内核与启动管理器,比如:

# sbsign --key db.key --cert db.crt --output /boot/vmlinuz-linux /boot/vmlinuz-linux
# sbsign --key db.key --cert db.crt --output esp/EFI/BOOT/BOOTx64.EFI esp/EFI/BOOT/BOOTx64.EFI
警告:只对内核进行签署并不能保护initramfs不被篡改。查看统一内核镜像以了解如何生成一个组合镜像并通过sbsign手动签署。
使用pacman钩子签署内核

你也可使用mkinitcpio的pacman钩子在安装与更新时签署一个内核。

复制/usr/share/libalpm/hooks/90-mkinitcpio-install.hook/etc/pacman.d/hooks/90-mkinitcpio-install.hook以及复制 /usr/share/libalpm/scripts/mkinitcpio-install/usr/local/share/libalpm/scripts/mkinitcpio-install

/etc/pacman.d/hooks/90-mkinitcpio-install.hook里,将:

Exec = /usr/share/libalpm/scripts/mkinitcpio-install

替换为:

Exec = /usr/local/share/libalpm/scripts/mkinitcpio-install

/usr/local/share/libalpm/scripts/mkinitcpio-install里,将:

install -Dm644 "${line}" "/boot/vmlinuz-${pkgbase}"

替换为:

sbsign --key /path/to/db.key --cert /path/to/db.crt --output "/boot/vmlinuz-${pkgbase}" "${line}"

如果你正在使用systemd-boot,有一个专用的pacman钩子会半自动的做这个任务。

用sbupdate全自动生成和签署统一内核

sbupdate是一个专门为在Arch Linux上自动生成和签署统一内核镜像而制作的工具。它通过Pacman#钩子处理内核的安装、移除和更新。

安装sbupdate-gitAUR并按照项目主页上的说明进行配置。[5]

提示:如果正在使用systemd-boot,设置OUT_DIR="EFI/Linux"可以让你的已签署的内核镜像直接被识别而不需要必须的配置。请看systemd-boot(7) § FILESSystemd-boot#增加启动选项

一旦完成配置,只需要以root运行sbupdate以完成首次镜像生成。

注意:sbupdate的输出经常包含错误比如warning: data remaining[26413568 vs 26423180]: gaps between PE/COFF sections?。那些是无害的且可以被安全的无视掉。[6]

将固件置于 "设置模式 "下

当平台密钥被删除时,安全启动处于设置模式。要将固件置于设置模式,请进入固件设置工具,并找到删除或清除证书的选项。#在启动操作系统之前中介绍了如何进入设置工具。

在固件中登记密钥

使用下列某种方式登记'dbKEKPK证书。

提示:如果dbx(禁止的签名数据库)是空白的,在下面的说明中,它可以被安全地排除在外。
警告:登记平台密钥会使安全启动进入“用户模式”,也就意味着会离开“设置模式”,所以登记应该完成在所有流程的最后。
使用sbkeysync

安装sbsigntools。按照以下目录结构创建文件夹/etc/secureboot/keys -

/etc/secureboot/keys
├── db
├── dbx
├── KEK
└── PK

比如,这样用:

# mkdir -p /etc/secureboot/keys/{db,dbx,KEK,PK}

接下来复制之前生成的每一个.auth文件到它们所对应的位置(比如,把PK.auth放进/etc/secureboot/keys/PK,以此类推)。

检查sbkeysync会对你系统的UEFI密钥库做什么改变。

# sbkeysync --pk --dry-run --verbose

最后,使用sbkeysync来登记你的密钥。

# sbkeysync --verbose
# sbkeysync --verbose --pk


提示:
  • 如果sbkeysync返回了写错误,那么在执行sbkeysync前先运行chattr -i /sys/firmware/efi/efivars/{PK,KEK,db}*来暂时改变文件属性,使EFI密钥可以写入efivars目录中。详情见chattr(1)
  • 如果为PK.auth得到了一个permission denied错误,你可以使用命令efi-updatevar -f /etc/secureboot/keys/PK/PK.auth PK登记密钥。
  • 如果都失败了,这可能是因为固件对BIOS设置上锁了。对于戴尔和联想的系统,你或许需要重置你的BIOS密码:Sysfs固件认证文档

对于联想系统:

# cat > /sys/class/firmware-attributes/thinklmi/authentication/Admin/current_password 
my-super-secret-password
^D

对于戴尔系统:

# cat > /sys/class/firmware-attributes/dell-wmi-sysman/authentication/Admin/current_password 
my-super-secret-password
^D
然后再次尝试。

下一次启动时,UEFI应该会切回用户模式并且执行安全启动策略。

使用固件设置工具

复制所有的*.cer*.esl*.auth文件(除了noPK.auth)到一个FAT格式的文件系统(你可以使用EFI 系统分区)。

警告:不要noPK.auth文件复制到你个人电脑的EFI 系统分区(ESP)!如果你这样做了,假如有人偷了你的电脑,这个人可以删除UEFI安全启动固件中的个人平台密钥,再一次打开你电脑上的“设置模式”,然后用他们自己的平台密钥替换你的安全启动密钥(PK、KEK、db、dbx),这将完全违背UEFI安全启动的目的。 应当只有你自己可以替换平台密钥,只有你自己可以访问noPK.auth文件。因此把noPK.auth文件保存在一个只有你自己能访问的安全的地方。这些地方对于noPK.auth文件来说都可以是安全的:
  • 当你使用KeyTool时,可以使用带有EFI 系统分区的外部U盘。要注意,KeyTool只能读取未加密存储中的文件。
  • 当你使用sbkeysync时,运用静态数据加密

个人电脑的EFI 系统分区一定要按照UEFI的规范进行加密,否则就可以在另一台电脑上被挂载且被读取(如果你的电脑被偷了,硬盘可以被取出来挂载到另一台电脑上)。复制noPK.auth到你电脑的ESP后再删除也是不可取的,因为在FAT32格式的EFI 系统分区上被删除的文件可以通过像是PhotoRec这样的工具被还原

启动固件设置工具,登记dbKEKPK证书。 固件会有各种不同的界面,请看使用固件的设置工具更换密钥,以了解如何登记密钥。

如果使用的工具支持,最好使用.auth.esl而不是.cer

使用KeyTool

KeyTool.efi包含在efitools包中,复制它到ESP。为了能在登记密钥后使用它,请用sbsign对它签署。

# sbsign --key db.key --cert db.crt --output esp/KeyTool-signed.efi /usr/share/efitools/efi/KeyTool.efi

使用固件设置工具,引导加载程序或者UEFI Shell启动KeyTool-signed.efi,然后登记密钥。

参见使用KeyTool更换密钥了解KeyTool的菜单选项。

与其他操作系统进行双重启动

微软 Windows

这篇文章的某些内容需要扩充。

原因:是否有可能通过用自定义密钥签署其启动程序来启动Windows? (在 Talk:UEFI/安全启动#使用自定义密钥启动Windows 中讨论)

通常情况下,启用安全引导下,仅使用私人自定义密钥签署微软引导程序(EFI/Microsoft/Boot/bootmgfw.efi)而没有在UEFI安全启动变量中登记“Microsoft Windows Production PCA 2011”不可能启动Windows:

  • 如果bootmgfw.efi同时包含来自“Microsoft Windows Production PCA 2011”你自己的安全启动数据库密钥(也就是说有两个签名),那么UEFI固件实现(比如INSYDE Corp. 4096.01 (UEFI Version 2.31, Version F.70, Release Date: 07/18/2016, BIOS Revision 15.112, Firmware Revision: 29.66))将不会启动bootmgfw.efi,并且会抛出一个安全违规错误(Selected boot image did not authenticate. Press ENTER to continue.)。像是这样的UEFI固件实现可能只能读取第一个签名而忽略第二个。只有第二个签名的证书被注册到UEFI安全启动变量中,所以安全启动验证失败。
  • 如果从bootmgfw.efi中剥离/移除“Microsoft Windows Production PCA 2011”证书,而只在文件中添加了你自己的安全启动数据库密钥的签名,虽然UEFI会启动该文件但Windows将启动一个恢复/修复环境。Windows会抱怨说:Windows的安装被破坏了(因为bootmgfw.efi中缺失了“Microsoft Windows Production PCA 2011”证书)。

所以为了实现双系统

  • 你必须把bootmgfw.efi的散列值添加到DB变量中允许的散列值列表中,而且每次Windows更新改变bootmgfw.efi时,你都必须更新DB变量。这非常繁琐,容易出错,而且不被微软支持,例如,Bitlocker在这种设置下将无法正常工作(Bitlocker每次解密Windows分区时都会要求你提供恢复密码)。
  • 或者你必须将微软的证书添加到UEFI安全启动的变量中,微软有两个DB证书和一个KEK证书

使用微软的GUID(77fa9abd-0359-4d32-bd60-28f4e78f784b),根据微软的多个DER格式的DB证书创建多个EFI签名列表。然后为了方便,把它们合并进一个文件中:

$ sbsiglist --owner 77fa9abd-0359-4d32-bd60-28f4e78f784b --type x509 --output MS_Win_db.esl MicWinProPCA2011_2011-10-19.crt
$ sbsiglist --owner 77fa9abd-0359-4d32-bd60-28f4e78f784b --type x509 --output MS_UEFI_db.esl MicCorUEFCA2011_2011-06-27.crt
$ cat MS_Win_db.esl MS_UEFI_db.esl > MS_db.esl

可选操作(为了严格符合的微软UEFI安全启动要求):使用微软的GUID(77fa9abd-0359-4d32-bd60-28f4e78f784b),根据微软的KEK格式的DB证书创建EFI签名列表:

 $ sbsiglist --owner 77fa9abd-0359-4d32-bd60-28f4e78f784b --type x509 --output MS_Win_KEK.esl MicCorKEKCA2011_2011-06-24.crt

使用你自己的KEK签署一个DB变量更新。使用带有-a选项的sign-efi-sig-list添加而不是替换一个DB证书:

$ sign-efi-sig-list -a -g 77fa9abd-0359-4d32-bd60-28f4e78f784b -k KEK.key -c KEK.crt db MS_db.esl add_MS_db.auth

可选操作(为了严格符合微软的UEFI安全启动要求):使用你自己的PK签署一个KEK变量更新。使用带有-a选项的sign-efi-sig-list添加而不是替换一个KEK证书:

$ sign-efi-sig-list -a -g 77fa9abd-0359-4d32-bd60-28f4e78f784b -k PK.key -c PK.crt KEK MS_Win_KEK.esl add_MS_Win_KEK.auth

按照#在固件中登记密钥的内容来登记add_MS_db.auth,为了严格符合微软的UEFI安全启动要求,将add_MS_Win_KEK.auth放入UEFI安全启动数据库的变量中。

使用已签署的引导加载程序

使用已签署的引导加载程序意味着使用由微软的密钥签署的引导加载程序。有两个已知的签署的引导加载程序:PreLoader和shim。它们的目的是连锁加载其他EFI二进制文件(通常是引导加载程序)。由于微软不会签署一个自动启动任何未签署的二进制文件的引导加载程序,PreLoader和shim使用一个叫做机器所有者密钥列表的白名单,缩写为MokList。如果二进制文件的SHA256散列值(Preloader和shim)或签署二进制文件的密钥(shim)在MokList中,它们就执行它,如果没有,他们就启动一个密钥管理工具,允许注册散列值或密钥。

警告:微软所谓的“Secured-core PCs”在出厂时并没有登记微软第三方UEFI CA证书(Microsoft Corporation UEFI CA 2011) [7]。唯一登记的DB证书是Microsoft Windows Production PCA 2011证书,该证书专门用于签署Windows的引导加载程序。

微软第三方UEFI CA证书的登记需要在固件设置中启用,以启动用该证书签署的EFI二进制文件和OpROM。

PreLoader

运行时,PreLoader尝试启动loader.efi。如果loader.efi的散列值不在MokList中,PreLoader将会启动HashTool.efi。在HashTool中,你必须登记你想要启动的EFI二进制的散列值,也就是你的引导加载程序loader.efi)以及内核的散列值。

注意:每次你更新任何二进制文件(如引导加载程序或内核)时,你都需要登记它们的新散列值。
提示:rEFInd加载管理程序的refind-install脚本可以复制rEFInd与PreLoader的EFI二进制文件到ESP。请看REFInd#使用_PreLoader
设置PreLoader
注意:efitools包中的PreLoader.efiHashTool.efi没有被签署,所以它们的作用是有限的。你可以从preloader-signedAUR获取被签署的PreLoader.efiHashTool.efi,或者手动下载它们

安装preloader-signedAURHashTool.efi然后复制PreLoader.efiHashTool.efi引导加载程序目录中。对于systemd-boot,使用:

# cp /usr/share/preloader-signed/{PreLoader,HashTool}.efi esp/EFI/systemd

现在复制一份引导加载程序的二进制文件并重命名为loader.efi。对于systemd-boot,使用:

# cp esp/EFI/systemd/systemd-bootx64.efi esp/EFI/systemd/loader.efi

最终,创建一个新的NVRAM引导项来启动PreLoader.efi

# efibootmgr --unicode --disk /dev/sdX --part Y --create --label "PreLoader" --loader /EFI/systemd/PreLoader.efi

X替换为驱动器字符并把Y替换为EFI 系统分区的分区号。

这个启动项应当添加进启动列表中并作为第一项启动,用efibootmgr命令检查,必要时调整启动顺序。

回撤

如果启动这个自定义NVRAM启动项时出现问题,复制HashTool.efiloader.efi到UEFI系统自动启动的默认加载程序位置:

# cp /usr/share/preloader-signed/HashTool.efi esp/EFI/BOOT/
# cp esp/EFI/systemd/systemd-bootx64.efi esp/EFI/BOOT/loader.efi

复制一份PreLoader.efi并重命名:

# cp /usr/share/preloader-signed/PreLoader.efi esp/EFI/BOOT/BOOTx64.EFI

对于特别顽固的UEFI实现,可以将PreLoader.efi复制到Windows系统使用的默认加载程序位置:

# mkdir -p esp/EFI/Microsoft/Boot
# cp /usr/share/preloader-signed/PreLoader.efi esp/EFI/Microsoft/Boot/bootmgfw.efi
注意:如果与Windows双启动,请先备份原始的bootmgfw.efi,因为替换它可能会导致Windows更新的问题。

如前所述,将HashTool.efiloader.efi复制到'esp/EFI/Microsoft/Boot/

当系统在启用安全启动下启动时,按照上述步骤登记loader.efi/vmlinuz-linux(或任何使用的内核镜像)。

如何在启动时使用?

当出现一条消息说:Failed to Start loader... I will now execute HashTool.。要使用HashTool来登记loader.efivmlinuz.efi的散列值,请遵循以下步骤。这些步骤假定了一个重制的archiso安装介质的项目。你得到的确切的项目取决于你的引导加载程序的设置。

  • 选择 OK
  • 在HashTool的主菜单,选择Enroll Hash,选择\loader.efi然后选Yes确认。同样,再选择Enroll Hasharchiso来进入archiso的目录,然后选择vmlinuz.efi,用Yes确认。接下来选择Exit返回至启动设备选单。
  • 在启动设备选单中选择Arch Linux archiso x86_64 UEFI CD
移除PreLoader
注意:既然你要删除东西,那么最好先备份。

卸载preloader-signedAUR然后

并简单地删除复制的文件并恢复配置。 对于systemd-boot使用:

# rm esp/EFI/systemd/{PreLoader,HashTool}.efi
# rm esp/EFI/systemd/loader.efi
# efibootmgr --unicode --bootnum N --delete-bootnum
# bootctl update

这里N指的是用来启动PreLoader.efi所创建的NVRAM启动项。 如有必要,用efibootmgr命令检查并调整启动顺序。

注意:上述命令涵盖了最简单的情况;如果你创建、复制、重命名或编辑了更多的文件,那么你也必须处理它们。如果PreLoader是你的操作引导项,你还需要#禁用安全启动
删除已登记的散列值

在MOK数据库中登记的每一条散列值都会吃掉NVRAM的一小块空间。你可能想删除无用的散列值,以释放空间并防止过时的程序启动。

安装efitools然后复制KeyTool.efi

# cp /usr/share/efitools/efi/KeyTool.efi esp/EFI/systemd/KeyTool.efi

引导启动至Preloader,你会看到KeyTool引导项。然后你就可以在MOK数据库中编辑散列值。

shim

这篇文章的某些内容需要扩充。

原因:需要进一步测试。 (在 Talk:UEFI/安全启动#shim 中讨论)

在运行时,shim会尝试启动grubx64.efi。如果MokList没有包含grubx64.efi的散列值或它所签署的密钥,shim将会启动MokManager(mmx64.efi)。你必须在MokManager里登记你想要启动的EFI二进制文件(你的引导加载程序grubx64.efi与内核)的散列值,或者登记你想要签署的密钥。

注意:
  • 如果你使用#shim与散列值,每次你升级任何二进制文件(比如引导加载程序或内核)后都需要重新登记它们的散列值。
  • 在版本15.3之后,shim不再启动没有合法.sbat部分的EFI二进制文件。想要验证一个EFI二进制文件是否具有这个部分,运行objdump -j .sbat -s /path/to/binary.efi。详情看:SBAT文档
  • 或许值得注意的是,说实话,如果你对安全启动带来的安全性并不感兴趣,之所以想要开启它仅仅是为了满足Windows 11的要求,你或许可以考虑关闭shim的验证过程:mokutil --disable-validation。这种情况下你不必签署grub(或许sbat仍然需要)以及内核镜像,且还可以通过chainloader从grub启动Windows。
设置shim
提示:rEFInd引导程序中的refind-install脚本可以签署rEFInd的EFI二进制文件并与shim和MOK证书一并复制到ESP中。详情见rEFInd#使用 shim

安装shim-signedAUR.

重命名当前的引导加载程序grubx64.efi

# mv esp/EFI/BOOT/BOOTx64.EFI esp/EFI/BOOT/grubx64.efi

复制shimMokManager到ESP中的启动引导文件夹,然后将shimx64.efi重命名为引导加载程序之前的文件名:

注意:
  • 确保你不要复制同一目录下的fbx64.efi,除非你真的有一个有效的bootx64.csv可以使用。否则shim不会执行grubx64.efi,这将无法正常工作,只会重置机器。
# cp /usr/share/shim-signed/shimx64.efi esp/EFI/BOOT/BOOTx64.EFI
# cp /usr/share/shim-signed/mmx64.efi esp/EFI/BOOT/

最终,创建一个新的NVRAM引导项来启动BOOTx64.EFI

# efibootmgr --unicode --disk /dev/sdX --part Y --create --label "Shim" --loader /EFI/BOOT/BOOTx64.EFI

shim可以通过机器所有者密钥或MokList所存储的散列值验证二进制文件。

机器所有者密钥 - Machine Owner Key (MOK)
一个由用户生成并被用来签署EFI二进制文件的密钥。
散列值 - hash
一个EFI二进制文件的SHA256散列值。

使用散列值更简单但是每次升级你的引导加载器或内核后需要添加它们新的散列值进MokManager。而使用MOK你只需要添加密钥一次,但是每次引导加载器或内核升级后都需要重新签署。

shim与散列值

如果shim无法找到在MokList中找到grubx64.efi的SHA256散列值,它将会启动MokManager(mmx64.efi)。

MokManager中选择Enroll hash from disk,找到grubx64.efi然后添加它到MokList。重复这一步骤添加你的内核vmlinuz-linux。完成之后,选择Continue boot,你的引导加载程序便会启动并成功加载内核。

shim与密钥

安装sbsigntools

你需要:

.key
PEM格式的私有密钥,用于签署EFI二进制文件。
.crt
PEM格式的证书,用于sbsign
.cer
DER格式的证书,用于MokManager

创建一个机器所有者密钥(MOK):

$ openssl req -newkey rsa:4096 -nodes -keyout MOK.key -new -x509 -sha256 -days 3650 -subj "/CN=my Machine Owner Key/" -out MOK.crt
$ openssl x509 -outform DER -in MOK.crt -out MOK.cer

签署你的引导加载程序(名为grubx64.efi)以及内核:

# sbsign --key MOK.key --cert MOK.crt --output /boot/vmlinuz-linux /boot/vmlinuz-linux
# sbsign --key MOK.key --cert MOK.crt --output esp/EFI/BOOT/grubx64.efi esp/EFI/BOOT/grubx64.efi

每次它们更新后你都需要这么做。你可以使用一个Pacman钩子自动化签署过程,比如:

/etc/pacman.d/hooks/999-sign_kernel_for_secureboot.hook
[Trigger]
Operation = Install
Operation = Upgrade
Type = Package
Target = linux
Target = linux-lts
Target = linux-hardened
Target = linux-zen

[Action]
Description = Signing kernel with Machine Owner Key for Secure Boot
When = PostTransaction
Exec = /usr/bin/find /boot/ -maxdepth 1 -name 'vmlinuz-*' -exec /usr/bin/sh -c 'if ! /usr/bin/sbverify --list {} 2>/dev/null | /usr/bin/grep -q "signature certificates"; then /usr/bin/sbsign --key MOK.key --cert MOK.crt --output {} {}; fi' ;
Depends = sbsigntools
Depends = findutils
Depends = grep

或者使用一个 mkinitcpio钩子

/etc/initcpio/post/kernel-sbsign
#!/usr/bin/env bash

kernel="$1"
[[ -n "$kernel" ]] || exit 0

# use already installed kernel if it exists
[[ ! -f "$KERNELDESTINATION" ]] || kernel="$KERNELDESTINATION"

keypairs=(/path/to/db.key /path/to/db.crt)

for (( i=0; i<${#keypairs[@]}; i+=2 )); do
    key="${keypairs[$i]}" cert="${keypairs[(( i + 1 ))]}"
    if ! sbverify --cert "$cert" "$kernel" &>/dev/null; then
        sbsign --key "$key" --cert "$cert" --output "$kernel" "$kernel"
    fi
done

复制MOK.cer到一个FAT格式的文件系统(你可以使用EFI 系统分区)。

重新启动并启用“安全启动”。如果shim未能找到在MokList中找到签署的grubx64.efi证书,它将启动MokManager(mmx64.efi)。

MokManager中选择Enroll key from disk,找到MOK.cer并把它添加到MokList。完成之后,选择Continue boot,你的引导加载程序便会启动并成功加载任何由你的机器所有者密钥签署的二进制文件。

shim与密钥和GRUB

这篇文章的某些内容需要扩充。

原因:GRUB#UEFI 系统提到了一个叫做grub-mkstandalone的工具,但是这里并没有出现。 (在 Talk:UEFI/安全启动 中讨论)

本文或本章节可能需要合并到GRUB

附注: 关于构建自己的EFI二进制文件并选择模块或许应该放进GRUB页面。(在 Talk:UEFI/安全启动 中讨论)

只有GRUB的EFI二进制文件包含所有能够读取vmlinuzinitramfs镜像所在文件系统的模块,才能在安全启动模式下成功启动。

从GRUB版本2.06.r261.g2f4430cc0之后,通过insmod在安全启动下读取模块不再被允许,因为这将违反不侧载任意代码的构想。如果GRUB模块没有被嵌入进EFI二进制文件,GRUB会尝试通过insmod侧载它们,这会在启动时报出如下错误信息:

error: prohibited by secure boot policy

根据Ubuntu的官方构建脚本,Ubuntu嵌入了下列GRUB模块在它已签署的GRUB EFI二进制文件grubx64.efi中。

  • “base”模块,为了从CD或者单一分区硬盘中启动所必需含有: all_video, boot, btrfs, cat, chain, configfile, echo, efifwsetup, efinet, ext2, fat, font, gettext, gfxmenu, gfxterm, gfxterm_background, gzio, halt, help, hfsplus, iso9660, jpeg, keystatus, loadenv, loopback, linux, ls, lsefi, lsefimmap, lsefisystab, lssal, memdisk, minicmd, normal, ntfs, part_apple, part_msdos, part_gpt, password_pbkdf2, png, probe, reboot, regexp, search, search_fs_uuid, search_fs_file, search_label, sleep, smbios, squash4, test, true, video, xfs, zfs, zfscrypt, zfsinfo
  • “platform-specific”模块,为了x86_64-efi架构所必需含有,例如:
    • play:为了在启动中播放声音
    • cpuid:为了启动中的CPU支持
    • linuxefi:为了支持UEFI启动
    • tpm:为了支持度量启动(Measured boot)与可信平台模块
  • “advanced”模块,包含下列模块:
    • cryptodisk:为了从plain模式加密的硬盘启动
    • gcry_algorithm:为了支持特定的散列和加密算法
    • luks:为了从LUKS加密的硬盘启动
    • lvm:为了从LVM逻辑分卷硬盘启动
    • mdraid09, mdraid1x, raid5rec, raid6rec:为了从RAID虚拟硬盘中启动

你必须以一个shell变量的形式构建你的GRUB模块列表,我们将它表示为GRUB_MODULES。你可以使用最新的Ubuntu脚本作为一个出发点,并修剪掉你系统中不需要的模块。修剪模块将使启动过程相对更快,并在ESP分区上节省一些空间。

如果GRUB从UEFI shim加载器启动,为了提高安全性,你还需要在EFI二进制文件中包含一个安全启动高级目标(SBAT)文件/部分。这个SBAT文件/部分包含了关于GRUB二进制文件的元数据(版本、维护者、开发者、上游URL),并且使shim更容易阻止某些有安全漏洞的GRUB版本被加载[8][9],正如UEFI shim引导加载程序的安全启动生命流程的提升文档中所解释的那样。

如果grubx64.efi中的SBAT部分丢失,UEFI shim引导加载程序的第一阶段将无法启动grubx64.efi

如果安装了GRUB,/usr/share/grub/sbat.csv中提供了一个SBAT的.csv文件样本。

使用提供的/usr/share/grub/sbat.csv文件和所有需要的GRUB_MODULES重新安装GRUB并签署:

# grub-install --target=x86_64-efi --efi-directory=esp --modules=${GRUB_MODULES} --sbat /usr/share/grub/sbat.csv
# sbsign --key MOK.key --cert MOK.crt --output esp/EFI/GRUB/grubx64.efi esp/EFI/GRUB/grubx64.efi
# cp esp/GRUB/grubx64.efi esp/boot/grubx64.efi

重新启动,在MokManager中选择密钥,安全启动就应该可以工作了。

移除shim

卸载shim-signedAUR,移除被复制的shimMokManager文件并重命名为你的引导加载程序默认名。

保护安全启动

防止有物理访问权限的人禁用安全启动的唯一方法是用密码保护固件设置。

大多数UEFI固件提供这样的功能,通常列在固件设置中的“安全”部分。

提示与技巧

sbctl

sbctl是一种用户友好的设置安全启动和签署文件方式。

注意:sbctl并不能在所有的硬件上工作。它的实际工作效果取决于制造商。

创建并登记密钥

在开始之前,进入你的固件设置将安全启动模式设置为设置模式。每个设备都有所不同。

当你重新登录后,检查安全启动的状态:

$ sbctl status

你应当看到sbctl并未安装,并且“设置模式”(Setup Mode)已经开启,以及安全启动已关闭。

若显示“设置模式”(Setup Mode)关闭,请进入BIOS手动重置进入Setup Mode。

接下来创建你的自定义安全启动密钥:

# sbctl create-keys

(在sbctl 0.8以及更新版本之后,执行此命令默认会把密钥创建在/var/lib/sbctl/keys/文件夹内)

将你的密钥与微软的密钥登记入UEFI中:

# sbctl enroll-keys -m
警告:启用安全启动时,一些固件会用微软的密钥进行签署和验证。不对设备进行验证可能会使它们变砖。如果想仅登记你的密钥而不登记微软的,请运行:# sbctl enroll-keys只有在你清楚了解自己在做什么的情况下才可以这样做。

再次检查安全启动状态:

$ sbctl status

sbctl现在应该已经安装好了,但是在用你刚刚创建的密钥对启动文件进行签署之前,安全启动将无法工作。

签署

为了安全启动工作,检查需要被签署的文件:

# sbctl verify

现在签署所有的未被签署的文件。通常是内核引导加载程序需要被签署。比如:

# sbctl sign -s /boot/vmlinuz-linux
# sbctl sign -s /boot/EFI/BOOT/BOOTX64.EFI

需要被签署的文件将取决与你系统的结构,内核以及引导加载程序。

现在万事大吉!重新启动你的系统,并在固件设置中重新打开安全启动。如果引导加载程序与操作系统被加载,安全启动应当开始工作了。用下列命令确认:

$ sbctl status

使用pacman钩子自动签署

sbctl带有一个Pacman钩子可以在内核systemd或者引导加载程序更新后自动地签署所有的新文件。

提示:如果你通过Systemd-boot启用了systemd-boot-update.service,那么引导加载程序只会在重启后升级,导致sbctl的Pacman钩子因此而不签署新的文件。 变通的方法是直接在/usr/lib/中小心的签署引导加载程序,这样bootctl installupdate将会自动识别并复制.efi.signed(如果有的话)到ESP,而不是普通的.efi文件。详情见bootctl(1)
# sbctl sign -s -o /usr/lib/systemd/boot/efi/systemd-bootx64.efi.signed /usr/lib/systemd/boot/efi/systemd-bootx64.efi

用自定义密钥签署官方ISO

使用自定义密钥的安全启动支持可以添加到官方ISO中,只需提取出引导加载程序(BOOTx64.EFIBOOTIA32.EFI)、内核与UEFI Shell,并对它们进行签署,然后用被签署的文件重新打包ISO。

安装libisoburnmtoolssbsigntools

首先解压出相关的文件以及El-Torito启动镜像:

$ osirrox -indev archlinux-YYYY.MM.DD-x86_64.iso \
	-extract_boot_images ./ \
	-extract /EFI/BOOT/BOOTx64.EFI BOOTx64.EFI \
	-extract /EFI/BOOT/BOOTIA32.EFI BOOTIA32.EFI \
	-extract /shellx64.efi shellx64.efi \
	-extract /shellia32.efi shellia32.efi \
	-extract /arch/boot/x86_64/vmlinuz-linux vmlinuz-linux

mkarchiso所使用的,xorrisofs(1)-rational-rock选项,会使ISO 9660中的文件变为只读,且在解压后仍然如此。我们需要让这些文件变为可写,以便它们可以被修改:

$ chmod +w BOOTx64.EFI BOOTIA32.EFI shellx64.efi shellia32.efi vmlinuz-linux

使用一个已登记的数据库密钥和证书签署这些文件:

$ sbsign --key db.key --cert db.crt --output BOOTx64.EFI BOOTx64.EFI
$ sbsign --key db.key --cert db.crt --output BOOTIA32.EFI BOOTIA32.EFI
$ sbsign --key db.key --cert db.crt --output shellx64.efi shellx64.efi
$ sbsign --key db.key --cert db.crt --output shellia32.efi shellia32.efi
$ sbsign --key db.key --cert db.crt --output vmlinuz-linux vmlinuz-linux

将引导加载程序与UEFI Shell复制到eltorito_img2_uefi.img中。它将被用作为EFI系统分区,并被列为一个El-Torito UEFI启动镜像。eltorito_img2_uefi.img的大小是固定的,但mkarchiso增加了1 MiB的空余空间,用于对齐以及为后续的扇区保留,所以签署所带来的大小增加应该不是问题。

$ mcopy -D oO -i eltorito_img2_uefi.img BOOTx64.EFI BOOTIA32.EFI ::/EFI/BOOT/
$ mcopy -D oO -i eltorito_img2_uefi.img shellx64.efi shellia32.efi ::/

使用修改过的El-Torito UEFI引导映像,重新打包ISO,并在ISO 9660中加入签署的引导加载程序文件、UEFI Shell和内核。

$ xorriso -indev archlinux-YYYY.MM.DD-x86_64.iso \
	-outdev archlinux-YYYY.MM.DD-x86_64-Secure_Boot.iso \
	-boot_image any replay \
	-append_partition 2 0xef eltorito_img2_uefi.img \
	-map BOOTx64.EFI /EFI/BOOT/BOOTx64.EFI \
	-map BOOTIA32.EFI /EFI/BOOT/BOOTIA32.EFI \
	-map shellx64.efi /shellx64.efi \
	-map shellia32.efi /shellia32.efi \
	-map vmlinuz-linux /arch/boot/x86_64/vmlinuz-linux

启动生成的archlinux-YYYY.MM.DD-x86_64-Secure_Boot.iso文件。

另见