UEFI/安全启动 - Arch Linux 中文维基

跳转到内容

# UEFI/安全启动

来自 Arch Linux 中文维基
< UEFI
翻译状态：

- 本文（或部分内容）译自 Unified_Extensible_Firmware_Interface/Secure_Boot，最近一次同步于 2023-02-03，若英文版本有所更改，则您可以帮助同步与翻译更改的内容。
- 您可以在 ArchWiki 的对应页面找到本文翻译的原始修订历史。
- 本文可能与英文原文存在出入。

相关文章

- Arch 的启动流程
- UEFI
- 安全

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

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

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

## 检查安全启动状态

### 在启动操作系统之前

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

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

### 在启动操作系统之后

使用systemd检查系统上的安全启动状态的一种简单方法是使用systemd-boot：

注意：以下命令，并非要求你强制使用systemd-boot,作为你的引导启动管理器，才能正确让你得到你需要的内容（安全启动是否开启），它更类似于其他*ctl systemd实用程序（localectl、timedatectl……），此处即为有关启动事项的综合管理工具，并且不会影响你的配置。

    $ bootctl status
    System:
         Firmware: UEFI 2.70 (American Megatrends 5.15)
      Secure Boot: enabled
       Setup Mode: user
     Boot into FW: supported
    ...

在这里，我们看到安全启动已启用并强制执行。Secure Boot的其他值包括`disabled`，Setup Mode的其他值包括`setup`[1]。

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

    $ od --address-radix=n --format=u1 /sys/firmware/efi/efivars/SecureBoot-*

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

    6  0  0  0  1

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

    # dmesg | grep -i secure

    [    0.013442] Secure boot disabled
    [    0.013442] Secure boot could not be determined

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

## 引导安装介质

注意：官方安装镜像不支持安全启动(FS#53864)。 要成功启动安装介质，您需要#禁用安全启动。

安全启动支持最初是在`archlinux-2013.07.01-dual.iso`中添加的，后来在`archlinux-2016.06.01-dual.iso`中被删除。 当时 prebootloader被替换为efitools包，即使后者使用未签署的EFI二进制文件。 从那以后，官方安装介质中就不再支持 Secure Boot。

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

### 禁用安全启动

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

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

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

### 重新制作安装镜像

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

原因：增加明确的说明。 (在 Talk:UEFI/安全启动 中讨论)

一个人或许想要像上文所说的那样重新制作安装镜像（英语：Remastering the Install ISO）。举个例子，#PreLoader中的已签署EFI程序`PreLoader.efi`与#PreLoader中的`HashTool.efi`可在此采用。另一个选项就是可以借用其他支持安全启动的GNU+Linux发行版中的`BOOTx64.EFI`（shim）以及`grubx64.efi`并根据需要修改GRUB配置。在这种情况下，上述发行版安装介质中的安全启动认证链应以 `grubx64.efi`为终点(Ubuntu的例子），这样GRUB就会从archiso启动未签署的内核和initramfs。

注意，到目前为止，本文假定你可以访问机器的ESP。但是当安装一台以前从未有操作系统的机器时，就没有ESP了。您应该阅读其他文章，例如UEFI#从 ISO 创建 UEFI 可启动 USB，了解如何处理这种情况。

## 实施安全启动

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

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

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

### 使用自己的密钥

警告：使用自己的密钥替换平台的密钥会瘫痪某些机器（比如笔记本）上的硬件，甚至无法进入UEFI/BIOS设置恢复。这是因为某些硬件的固件（OpROMs）在启动时使用微软的密钥进行签署。

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

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

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

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

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

#### 安装 efitools

执行接下来的大部分内容需求安装efitools包。

#### 备份当前变量

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

运行以下命令来备份所有四个主要的安全启动变量。

    $ efi-readvar -v PK -o old_PK.esl
    $ efi-readvar -v KEK -o old_KEK.esl
    $ efi-readvar -v db -o old_db.esl
    $ efi-readvar -v dbx -o old_dbx.esl

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

#### 创建密钥

##### 手动流程

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

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

.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)、sbkeysync、KeyTool与固件。

创建一个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) § FILES与Systemd-boot#增加启动选项。

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

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

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

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

#### 在固件中登记密钥

使用下列某种方式登记'db、KEK和PK证书。

提示：如果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这样的工具被还原。

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

如果使用的工具支持，最好使用.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`证书：

- - 为了使Windows操作系统可以被引导，Microsoft Windows Production PCA 2011 证书必须被包含进`DB`变量。
  - 为了使用第三方二进制，比如UEFI驱动、其他ROM、shim包等，Microsoft Corporation UEFI CA 2011 证书（又叫作微软第三方UEFI CA证书）必须被包含进`DB`变量。
  - 为了“通过更新`DBX`和潜在更新`DB`来撤销不良镜像对新的Windows签名镜像做准备”，Microsoft Corporation KEK CA 2011 证书应当被包含进`KEK`变量。然而没有“Microsoft Corporation KEK CA 2011”证书，Windows也可以启动。

使用微软的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.efi`与`HashTool.efi`没有被签署，所以它们的作用是有限的。你可以从preloader-signedAUR获取被签署的`PreLoader.efi`与`HashTool.efi`，或者手动下载它们。

安装preloader-signedAUR和`HashTool.efi`然后复制`PreLoader.efi`和`HashTool.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.efi`和`loader.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.efi`和`loader.efi`复制到`'esp/EFI/Microsoft/Boot/`。

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

##### 如何在启动时使用？

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

- 选择 OK
- 在HashTool的主菜单，选择Enroll Hash，选择`\loader.efi`然后选Yes确认。同样，再选择Enroll Hash和`archiso`来进入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

复制shim与MokManager到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二进制文件包含所有能够读取vmlinuz与initramfs镜像所在文件系统的模块，才能在安全启动模式下成功启动。

从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，移除被复制的shim与MokManager文件并重命名为你的引导加载程序默认名。

## 保护安全启动

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

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

## 提示与技巧

### sbctl

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

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

#### 创建并登记密钥

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

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

    $ sbctl status

你应当看到sbctl并未安装以及安全启动已关闭。

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

    # sbctl create-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 install`与`update`将会自动识别并复制.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.EFI`与`BOOTIA32.EFI`）、内核与UEFI Shell，并对它们进行签署，然后用被签署的文件重新打包ISO。

安装libisoburn包、mtools包与sbsigntools包。

首先解压出相关的文件以及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`文件。

## 另见

- Understanding the UEFI Secure Boot Chain by tianocore
- Wikipedia:Unified Extensible Firmware Interface#Secure boot
- Dealing with Secure Boot by Rod Smith
- Controlling Secure Boot by Rod Smith
- UEFI secure booting (part 2) by Matthew Garrett
- UEFI Secure Boot by James Bottomley
- efitools README
- Will your computer's "Secure Boot" turn out to be "Restricted Boot"? — Free Software Foundation
- Free Software Foundation recommendations for free operating system distributions considering Secure Boot
- Intel's UEFI Secure Boot Tutorial
- Secure Boot, Signed Modules and Signed ELF Binaries
- National Security Agency docs: UEFI Defensive Practices Guidance and unclassified UEFI Secure Boot customization
- sbkeysync & maintaining uefi key databases by Jeremy Kerr
- Secure your boot process: UEFI + Secureboot + EFISTUB + Luks2 + lvm + Arch Linux (2020-07)
- How is hibernation supported, on machines with UEFI Secure Boot? (Security StackExchange)
- Authenticated Boot and Disk Encryption on Linux by Lennart Poettering (2021-09-23)

分类：​

- 启动过程

隐藏分类：​

- 使用“模板:合并”标记的页面或章节

UEFI/安全启动
添加话题