UEFI/安全启动
Secure Boot是UEFI标准中的一项安全功能,旨在为pre-boot process添加一层保护;通过维护被授权或禁止的在启动时运行的经过加密签署的二进制文件列表,它有助于使得核心引导组件(引导管理器、内核、initramfs)不被篡改。
因此,它可以被视为对个人使用环境的securing所做努力的延续或补充,减少其他软件安全解决方案(如系统加密)无法轻易覆盖的攻击面,同时完全不同且不依赖于它们。安全启动只是作为当前安全实践的一个组成部分独立存在,具有自己的一套优缺点。
检查安全启动状态[编辑 | 编辑源代码]
在启动操作系统之前[编辑 | 编辑源代码]
此时,必须查看固件设置。如果机器已启动并正在运行,则在大多数情况下必须重新启动。
您可以在引导过程中通过按特殊键来访问固件配置。使用的特殊键取决于固件。它通常是Esc
、F2
、Del
或者Fn
键中的一个。有时在启动过程中特殊键的名称会显示一小段时间。主板说明书往往有关于此的记载。如果你想要按下特殊键,请在启动机器后立即按下该键,甚至是在屏幕实际显示任何内容之前。
进入固件设置后,请注意不要擅自更改任何设置。 通常在每个设置的底部都会有导航说明和设置的简短帮助。设置本身可能由几个页面组成,您必须导航到正确的位置。有一些安全启动设置可能简单地表示为安全启动选项,可以将其设置为打开或关闭 .
在启动操作系统之后[编辑 | 编辑源代码]
使用systemd检查系统上的安全启动状态的一种简单方法是使用systemd-boot:
$ 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
。
引导安装介质[编辑 | 编辑源代码]
安全启动支持最初是在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 的禁用安全启动。
重新制作安装镜像[编辑 | 编辑源代码]
一个人或许想要像上文所说的那样重新制作安装镜像。举个例子,#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,了解如何处理这种情况。
实施安全启动[编辑 | 编辑源代码]
为了确保理想的安全启动设置,有以下几个特定条件:
- UEFI基本被认为是可信的(尽管有一些众所周知的批评和漏洞[2]),且必须被强密码保护。
- 不使用默认的制造商或第三方密钥,因为它们被证明会大大削弱安全启动的安全模型[3]。
- UEFI直接读取带有EFISTUB的内核镜像(而不是引导加载程序),包含微码(如果适用)以及 initramfs。以便在整个启动过程维持安全启动建立的信任链并减少攻击面。
- 使用完整驱动器加密,这样在创建内核镜像和签署的过程中所涉及的工具与文件就无法被具有机器物理访问权限的人访问并篡改。
- 可以使用TPM(可信平台模块)获得进一步的提升,但因为相关工具与支持使这很难实施。
#使用自己的密钥中描述了一个简单且完全自力更生的设置,而#使用已签署的引导加载程序使用了第三方签署的中间工具。
使用自己的密钥[编辑 | 编辑源代码]
实现安全启动需要下列密钥:
- 平台密钥 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[编辑 | 编辑源代码]
备份当前变量[编辑 | 编辑源代码]
在创建新的密钥和修改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 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密钥:
- 创建新的密钥,
- 转换其为EFI签名列表,
- 签署EFI签名列表,
- 登记已签署的证书更新文件。
$ 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#使用你自己的密钥。
使用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
使用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]
OUT_DIR="EFI/Linux"
可以让你的已签署的内核镜像直接被识别而不需要必须的配置。请看systemd-boot(7) § FILES与Systemd-boot#增加启动选项。一旦完成配置,只需要以root运行sbupdate
以完成首次镜像生成。
warning: data remaining[26413568 vs 26423180]: gaps between PE/COFF sections?
。那些是无害的且可以被安全的无视掉。[6]将固件置于 "设置模式 "下[编辑 | 编辑源代码]
当平台密钥被删除时,安全启动处于设置模式。要将固件置于设置模式,请进入固件设置工具,并找到删除或清除证书的选项。#在启动操作系统之前中介绍了如何进入设置工具。
在固件中登记密钥[编辑 | 编辑源代码]
使用下列某种方式登记'db、KEK和PK证书。
使用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
文件来说都可以是安全的:
个人电脑的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[编辑 | 编辑源代码]
通常情况下,启用安全引导下,仅使用私人自定义密钥签署微软引导程序(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也可以启动。
- 为了使Windows操作系统可以被引导,Microsoft Windows Production PCA 2011 证书必须被包含进
使用微软的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中,它们就执行它,如果没有,他们就启动一个密钥管理工具,允许注册散列值或密钥。
微软第三方UEFI CA证书的登记需要在固件设置中启用,以启动用该证书签署的EFI二进制文件和OpROM。
PreLoader[编辑 | 编辑源代码]
运行时,PreLoader尝试启动loader.efi
。如果loader.efi
的散列值不在MokList中,PreLoader将会启动HashTool.efi
。在HashTool中,你必须登记你想要启动的EFI二进制的散列值,也就是你的引导加载程序(loader.efi
)以及内核的散列值。
设置PreLoader[编辑 | 编辑源代码]
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
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命令检查并调整启动顺序。
删除已登记的散列值[编辑 | 编辑源代码]
在MOK数据库中登记的每一条散列值都会吃掉NVRAM的一小块空间。你可能想删除无用的散列值,以释放空间并防止过时的程序启动。
# cp /usr/share/efitools/efi/KeyTool.efi esp/EFI/systemd/KeyTool.efi
引导启动至Preloader,你会看到KeyTool引导项。然后你就可以在MOK数据库中编辑散列值。
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[编辑 | 编辑源代码]
安装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的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 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-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 Sheel,并对它们进行签署,然后用被签署的文件重新打包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)