Arch Linux TPM 自动解锁失效的排查与修复(双系统 Arch Linux + Windows 11)

3次阅读
没有评论

适用场景:使用 systemd-cryptenroll 将 LUKS2 密钥封存到 TPM2 实现免密码自动解锁,因 Secure Boot 状态、固件、或 Windows 内核安全设置变化导致 PCR 测量值改变,自动解锁失效。

背景:

TPM 自动解锁的原理是把 LUKS 密钥封存(seal)到 TPM 芯片内,并用一组 PCR(Platform Configuration Register) 值作为解封条件。每次启动时,固件、引导链、内核都会向特定 PCR “测量”自身状态。只有当 PCR 值与封存时完全一致,TPM 才会释放密钥。

常见 PCR 含义(部分):

PCR 测量内容 变化触发
0 固件代码 BIOS/UEFI 更新
2 可选 ROM 显卡固件、外设固件更新
4 引导加载器 bootloader 更换
7 Secure Boot 状态与策略 开关 SB、DBX 更新、密钥变更
8/9 内核命令行、initramfs 内核或 initramfs 更新
11 UKI(Unified Kernel Image) UKI 内容变化

PCR 7 是最常用的绑定目标。它只关心 Secure Boot 是否启用以及策略是否被篡改,对常规系统更新足够宽容。

触发失效的典型操作

最常见的几类原因,按”为什么会动到 PCR 7″分类:

直接动 Secure Boot 配置

  • BIOS 中关闭再开启 Secure Boot
  • 更换 / 重新登记 Secure Boot 密钥(PK/KEK/db)
  • Windows 推送的 DBX 撤销列表更新(Microsoft 不定期通过 KB 推送)

Windows 改了引导配置,间接触发 EFI 变量变化

这是双系统用户最容易出现的问题,也是本文作者本次的问题所在。比如:

  • 为了开/关 Hyper-V、WSL2、虚拟机平台、Windows 沙盒,调整了核心隔离 / 内存完整性 / VBS / HVCI
  • 切换 hypervisor 启动模式(bcdedit /set hypervisorlaunchtype
  • Windows 大版本更新

这类操作本身不直接进 PCR 7。但 Windows 检测到安全配置变化后会自动修改启动链配置,重写 BCD、调整 winload.efi 的加载流程、写入 EFI 变量(BootOrderBootXXXX 等)。EFI 变量变化会被测量进 PCR 7,导致 Linux 这边的 TPM 解锁失效。

固件 / 硬件层面

  • BIOS / UEFI 固件更新(同时影响 PCR 0 和可能影响 PCR 7)
  • 进入 BIOS Setup 后保存退出(部分主板会触发)

Windows 的 BitLocker 也用 TPM,但走的是独立的 NV index 和密钥封装机制,两套互不干扰。修复 Linux 的 TPM 解锁不会影响 BitLocker,反之亦然。所以双系统场景下,Windows 调整安全设置后通常 BitLocker 自己也会要求恢复密钥一次,恢复后两边都能正常工作。

作者配置环境:UKI + 签名 PCR 策略

本文以一种较为完善的 Arch 加密启动方案为参考。如果你的配置更简单(比如 GRUB + 传统 initramfs + 纯 PCR 绑定),原理相同,只是少了 PCR 11 签名那部分。

组件构成

组件 作用
mkinitcpio + sd-encrypt hook 生成 initramfs,启动时通过 systemd 解锁 LUKS
UKI(Unified Kernel Image) 把 kernel + initramfs + cmdline + osrel 打包成单个签名的 .efi
sbctl 管理用户的 Secure Boot 密钥,自动给 UKI 签名
systemd-measure 用私钥对 UKI 的预期 PCR 11 值签名,生成 .pcrsig
systemd-cryptenroll 把 LUKS 密钥封存到 TPM,绑定 PCR 7 + PCR 11 签名策略
systemd-boot 直接加载 /boot/EFI/Linux/ 下的 UKI,无需配置文件

启动链与信任传递

UEFI 固件
  └─ Secure Boot 验证(PCR 7 测量 SB 状态)
      └─ systemd-boot(sbctl 签名)
          └─ UKI(sbctl 签名,PCR 11 测量内容)
              └─ initramfs 内 systemd
                  └─ 向 TPM 请求解封 LUKS 密钥
                      ├─ 检查 PCR 7 == 封存时的值?  ← 硬绑定
                      └─ 检查 PCR 11 是否有合法签名? ← 签名策略
                          └─ 两个条件都满足 → TPM 释放密钥
                              └─ LUKS 解锁 → 挂载根分区

双重 TPM 策略的设计意图

LUKS2 token 里同时包含两类策略:

  1. tpm2-hash-pcrs: 7(硬绑定)
    • 必须当前 PCR 7 == 封存时的值
    • 防御 Secure Boot 被关闭、密钥被篡改等攻击
    • 代价:Secure Boot 状态变化就要重新 enroll
  2. tpm2-pubkey-pcrs: 11 + tpm2-pubkey(签名策略)
    • 不要求 PCR 11 等于固定值
    • 但要求”存在一个由 tpm2-pubkey 对应私钥签名的、声明此 PCR 11 值合法”的签名
    • 签名以 .pcrsig 文件形式由 mkinitcpio 在重建 UKI 时自动生成
    • 代价:私钥必须妥善保管

关键收益:内核更新后 UKI 的 PCR 11 会变,但 mkinitcpio 的 sbctl post-hook 会自动重新签名。只要内核是你自己签的、Secure Boot 状态没变,自动解锁就持续有效,无需人工 re-enroll

关键文件位置

/etc/tpm/pcr_policy_private.key   # PCR 签名私钥(绝不能泄露)
/etc/tpm/pcr_policy_public.key    # PCR 签名公钥(嵌入 LUKS token)
/etc/kernel/uki.conf              # ukify 配置,指定签名密钥路径
/etc/mkinitcpio.d/*.preset        # 各内核的 UKI 输出路径配置
/boot/EFI/Linux/*.efi             # 生成的 UKI 文件
/var/lib/sbctl/keys/              # sbctl 的 Secure Boot 签名密钥

mkinitcpio -P 内核更新自动重新计算PCR 11 值

输出里这一行是核心:

+ /usr/lib/systemd/systemd-measure sign \
    --linux=/boot/vmlinuz-linux \
    --initrd=/tmp/mkinitcpio.xxx \
    --pcrpkey=/etc/tpm/pcr_policy_public.key \
    --private-key=/etc/tpm/pcr_policy_private.key \
    --public-key=/etc/tpm/pcr_policy_public.key \
    --bank=sha256 --phase=enter-initrd

systemd-measure 计算这个 UKI 在 enter-initrd 阶段的预期 PCR 11 值,用私钥签名后嵌入 UKI 的 .pcrsig 节区。然后 sbctl post-hook 用 Secure Boot 密钥再签一次外层 PE 签名。

启动时,systemd 在 initramfs 里读取 .pcrsig,连同当前 PCR 11 值一起提交给 TPM。TPM 验证签名合法且声明的 PCR 11 与当前一致后,按签名策略释放密钥。


修复流程

第 1 步:用 LUKS 密码手动启动

自动解锁失败时,输入当初创建 LUKS 时设置的恢复密码进入系统。

第 2 步:定位加密分区

lsblk -f
sudo cryptsetup status <你的 mapper 名称>

输出里 device: 一行就是底层分区,例如 /dev/nvme1n1p2。后续命令以此为例。

第 3 步:查看当前 TPM 绑定参数(重要)

sudo cryptsetup luksDump /dev/nvme1n1p2

Tokens: 段查找 systemd-tpm2 类型的 token,记下:

  • tpm2-hash-pcrs:硬绑定的 PCR 列表(常见为 7
  • tpm2-pubkey-pcrs:公钥签名策略的 PCR(常见为 11,仅 UKI + 签名策略方案有此字段)
  • tpm2-pubkey:是否存在(决定是否需要 --tpm2-public-key 参数)

同时确认现有密钥槽:

sudo systemd-cryptenroll /dev/nvme1n1p2

应至少有一个 password 类型的槽作为后备——不要在没有密码后备槽的情况下清除 TPM 槽

第 4 步:清除旧 TPM 槽

sudo systemd-cryptenroll --wipe-slot=tpm2 /dev/nvme1n1p2

第 5 步:用相同参数重新登记

方案 A:纯 PCR 绑定(最常见的 systemd-cryptenroll 默认配置)

sudo systemd-cryptenroll \
  --tpm2-device=auto \
  --tpm2-pcrs=7 \
  /dev/nvme1n1p2

方案 B:PCR + 公钥签名策略(UKI + sbctl 用户常见配置)

sudo systemd-cryptenroll \
  --tpm2-device=auto \
  --tpm2-pcrs=7 \
  --tpm2-public-key=/etc/tpm/pcr_policy_public.key \
  --tpm2-public-key-pcrs=11 \
  /dev/nvme1n1p2

--tpm2-pcrs= 后面的值要和 luksDump 里看到的 tpm2-hash-pcrs 一致。如果原来是 0+7 就写 0+7

执行时会要求输入一个现有的 LUKS 密码做授权。

第 6 步:验证

sudo systemd-cryptenroll /dev/nvme1n1p2

应出现新的 tpm2 槽(槽号不一定和原来相同,无影响)。重启测试自动解锁。

配置文件查找指南

不同的 Arch 配置方案,根分区的 TPM 解锁参数存放位置不同:

配置方案 参数位置
传统 /etc/crypttab.initramfs + mkinitcpio /etc/crypttab.initramfs 对应行的 options
sd-encrypt hook + 内核命令行 /proc/cmdline/etc/kernel/cmdline 里的 rd.luks.* 参数
任意方案 cryptsetup luksDump 输出的 Tokens 段

当不确定时,以 luksDump 的 Token 元数据为准,这是 LUKS 头部里实际生效的配置。

PCR 选择建议

选择 安全性 稳定性 适用场景
仅 7 中等(防 evil maid) 推荐默认,平衡安全与维护成本
0+7 较高 中等,固件更新需重做 在意固件被替换的场景
7 + 公钥签名 11 高(内核更新自动适配) UKI + sbctl 方案,最稳健
加 8/9 低,每次内核更新都要重做 不推荐,除非用 UKI + PCR 11

公钥签名策略--tpm2-public-key-pcrs)的精妙之处在于内核更新后,systemd-measure sign 会用私钥对新 UKI 的预期 PCR 11 值签名生成 .pcrsig,TPM 凭签名释放密钥。这样只要内核是你签的、Secure Boot 状态没变,就能持续自动解锁,无需每次重新 enroll。

故障排查

重新登记后仍要输密码

  1. 再次发生 PCR 7 变化:检查是否在重启过程中又动了 BIOS/Secure Boot。重复修复流程。
  2. initramfs 缺少 TPM 支持:确认 /etc/mkinitcpio.conf HOOKS 包含 sd-encrypt,并已安装 tpm2-tss 包。
  3. 公钥签名策略下的 .pcrsig 失效:执行 sudo mkinitcpio -P 重建并重新签名。

查看实际 PCR 值

sudo systemd-analyze pcrs
# 或
cat /sys/class/tpm/tpm0/pcr-sha256/7

可以对比修复前后 PCR 7 的变化,确认是否是 PCR 漂移导致。

TPM 完全不可用

systemctl status systemd-cryptsetup@*.service
journalctl -b | grep -i tpm

如果显示 TPM 设备不存在,检查 BIOS 里 TPM/PTT/fTPM 是否启用。

安全性说明

本流程涉及的所有 luksDump 输出(包括 token 元数据、salt、policy hash、TPM blob、公钥)均可安全公开。LUKS 与 TPM 的安全性建立在密钥保密上,配置元数据公开不削弱安全性。

绝不能公开的内容

  • LUKS passphrase 本身
  • 公钥签名策略的私钥(如 /etc/tpm/pcr_policy_private.key
  • 任何 keyfile 文件内容
  • sbctl 的 Secure Boot 签名私钥
正文完
 0
评论(没有评论)
验证码