Featured image of post Arch Linux 配置 UKI+TPM PCR policies 解锁 LUKS 分区

Arch Linux 配置 UKI+TPM PCR policies 解锁 LUKS 分区

封面图:https://www.bilibili.com/opus/992929256980873221

前言

一直以来,我都是使用 TPM 绑定 PCR 7+12 hash 来自动解锁 LUKS 分区。然而,一来这样没有测量内核(PCR#11),二来这样任何内核参数的修改都会触发解锁失败,带来了很多不方便。TPM PCR policies 提供了一种机制,允许对 PCR 测量值进行签名,当系统启动时,TPM 会通过和硬盘密钥一起写入的公钥来验证该签名,当签名验证成功时,释放硬盘密钥。而像 PCR#11 这样的只和内核映像相关的测量值很容易计算。因此,我们可以在生成内核映像之后,提前计算测量值并进行签名;下次启动时,TPM 验证该签名正确,就会释放密钥,从而允许更新内核映像并不破坏 TPM 自动解锁。

本文使用以下工具:

  • sbctl
  • systemd-boot
  • mkinitcpio
  • systemd-ukify
  • systemd-cryptenroll

本文希望达成以下结果:

  • 将内核、内核参数、initrd 等组件打包成统一内核映像(UKI)。当我自己在系统内执行此生成时,不会导致 TPM 自动解锁失败,从而允许内核升级和内核参数修改;而外部的修改尝试会导致解锁失败,即禁止内核替换以及启动时编辑内核参数。

注意:安全启动和 TPM policies 都会利用签名来进行验证。下文中尽量区分开这两种情况。我在进行此次配置之前已经配置好了 sbctl 安全启动,因此不再涉及相关内容。sbctl 如果已经配置好了和 mkinitcpio 协作的,不需要修改,配置依然有效。如果你还没有配置,可以参考 安全启动#sbctl 进行配置,sbctl 自带所有钩子,只需要配置 sbctl 本身即可,不需要手动签名,非常简单。

生成 UKI

我之前使用的是一般的内核+initrd方式启动,因此首先需要切换到 UKI。

统一内核映像(Unified Kernel Image,UKI)是一个可执行文件,可直接从 UEFI 固件进行启动,也可以无需或通过简单的配置由引导加载器自动生成。它将一个 UEFI boot stub 程序(例如 systemd-stub(7))、一个 Linux 内核映像、一个 initrd 以及其它资源打包到了单个 UEFI PE 文件中。

该文件,也就是包括其下的所有元件都可以很方便地进行签名,以便与安全启动一起使用。

我使用 mkinitcpio+systemd-ukify 的方式生成 UKI。使用 mkinitcpio 是因为它与 Arch Linux 集成更好,像是 sbctl 包就有 mkinitcpio hook,可以自动对 mkinitcpio 生成的文件进行安全启动签名。使用 systemd-ukify 是因为它可以自动对内核映像进行 TPM policies 签名。这两个组件配合得非常好,安装 systemd-ukify 后,mkinitcpio 就会自动用它来生成 UKI,并且 mkinitcpio 的配置也可以自动传递给 systemd-ukifysystemd-ukify 这边只需要添加 TPM policies 相关的配置即可。

首先安装 systemd-ukify

然后进行 mkinitcpio 的配置。该部分主要参考 统一内核映像#mkinitcpio。首先配置内核命令行,也就是root=啊,quiet splash之类的,可以在/etc/kernel/cmdline下进行单文件配置,或者在/etc/cmdline.d/*.conf下进行多文件配置。将原本启动引导器里面配置的参数移过来即可。然后,编辑/etc/mkinitcpio.d/$LINUX.preset$LINUX通常是内核包名),取消*_uki选项前面的注释,并修改其值为合适的路径,主要是修改esp为你自己的esp分区名。最后,就可以先尝试生成一下 UKI。

1
2
sudo mkdir -p esp/EFI/Linux
sudo mkinitcpio -p $LINUX

应该没有错误输出,且有Using ukify to build UKI字样。

然后,就可以进行 systemd-ukify 的配置。首先复制配置模板:

1
sudo cp /usr/lib/kernel/uki.conf /etc/kernel/uki.conf

然后,编辑/etc/kernel/uki.conf文件。我们使用 mkinitcpio 进行大部分配置,因此 systemd-ukify 这里我们只进行最简单的配置,仅仅配置 PCRSignature 小节。取消

1
2
3
#[PCRSignature:NAME]
#PCRPrivateKey=/etc/systemd/tpm2-pcr-private-key.pem
#PCRPublicKey=/etc/systemd/tpm2-pcr-public-key.pem

这三行的注释。把 NAME 修改为 default

然后,根据这个路径生成相应的签名密钥对。

1
2
3
sudo ukify genkey \         
        --pcr-private-key=/etc/systemd/tpm2-pcr-private-key.pem \
        --pcr-public-key=/etc/systemd/tpm2-pcr-public-key.pem

完成后再次生成 UKI

1
sudo mkinitcpio -p $LINUX

应该能看到类似

1
2
3
  -> Using ukify to build UKI
Using config file: /etc/kernel/uki.conf
+ /usr/lib/systemd/systemd-measure sign ...

这样的输出。至此,有签名的 UKI 就生成完成了。

我使用 systemd-boot 引导,它会自动检测 UKI,不需要额外写配置。如果你使用其它引导器,请自行查阅相关文档。

写入 TPM 和加密密钥

在完成 UKI 签名之后,就可以写入 TPM 和加密密钥了。这个过程其实很简单,因为我们在默认路径生成了 pubkey,systemd 检测到有 pubkey 就会自动把它写入 TPM 并绑定到 PCR#11。因此只需要

1
2
#$DISK_DEVICE_PATH 就是 /dev 下你的加密硬盘路径
sudo systemd-cryptenroll --tpm2-device=auto $DISK_DEVICE_PATH

你可以用

1
sudo cryptsetup luksDump $DISK_DEVICE_PATH | grep pcr

检查,应该可以看到

1
tpm2-pubkey-pcrs: 11

字样。

当然,更流行的做法是加上 PCR#7 的绑定。PCR#7 是安全启动状态,比如关闭安全启动就不能自动解锁,加强安全性。PCR#7 一般不会变化,如果变了一般是 fwupd 导致的,而要预计算这个变化需要先解决这个 issue。因此目前还是只能用静态哈希来绑定,不能用 PCR policy。

1
2
# --wipe-slot=tpm2 是抹除之前写入的和 tpm 关联的密钥
sudo systemd-cryptenroll --wipe-slot=tpm2 --tpm2-device=auto --tpm2-pcrs=7 $DISK_DEVICE_PATH

重启电脑,应该可以自动解锁。

清理工作

既然有了 UKI,那一般的 initrd image 就用不上了。编辑 /etc/mkinitcpio.d/$LINUX.preset,注释掉*_image选项,然后删除对应的initranfs-*-.img文件。另外引导器里面相应的引导入口也可以删掉了,对于 systemd-boot,自定义引导入口位于 $esp/loader/entries/。如果只用 UKI,那所有引导入口都可以删除,因为 systemd-boot 会自动检测 UKI,不需要自己写引导入口。

使用 Hugo 构建
主题 StackJimmy 设计