配置基于硬件安全的 Linux:TPM2/FIDO2 解密 LUKS 全盘加密和 U2F 用户登录

前置知识:UEFI 下 Linux 的引导过程

由于 BIOS 下实现 TPM2 解密 LUKS 较为困难,本文仅介绍 UEFI 下 Linux 的引导过程。本文所给出的方案,也仅适用于 UEFI (非 CSM) 环境。

简单的说,UEFI 下 Linux 的引导过程如下:

  1. 读取 EFI 分区中的 EFI/BOOT/BOOTX64.EFI,并执行。
  2. 该 EFI 程序找到 /boot 分区,并读取配置文件。这一步是可选的。
  3. 根据配置文件,加载 Linux 内核和 initramfs。这一步是可选的:大多数发行版都有 initramfs,如 Arch Linux、openSUSE、Ubuntu 等。一些嵌入式发行版没有 initramfs,如 OpenWrt。
  4. 执行 initramfs 的 init 脚本,init 脚本挂载主目录,然后使用 exec 启动 systemd 或其他 init 程序。

需要注意的是,引导器、initramfs 和系统 init 是三个不同的东西:

  • 引导器:负责加载 Linux 内核和 initramfs,如 GRUB、systemd-boot、rEFInd 等。
  • initramfs:一个微型的、用户态的 Linux 镜像,负责挂载主目录,启动系统 init 程序。常用的 initramfs 可能基于 busybox 或 systemd。
    • 值得注意的是,initramfs 和 initramfs 生成器并不是同一个东西。常见的 initramfs 生成器有 dracut、mkinitcpio 等。
  • 系统 init:是 PID=1 的进程,负责启动并管理进程和服务。常见的系统 init 有 systemd-init、sysvinit、OpenRC 等。
    • 事实上,早在 initramfs 时期,PID=1 的进程就存在了。通常这个进程是一个 shell 脚本,但也有的 initramfs 不同。随后,initramfs 的 init 脚本会通过 exec 方式启动系统 init。因此,它们具有相同的 PID=1

此外,这三个东西一般情况下是可以自由组合的。可以使用「GRUB + Systemd-based initramfs + Systemd-init」,也可以使用「systemd-boot + Busybox-based initramfs + SysVinit」。主流的发行版选择的是「GRUB + Busybox-based initramfs + Systemd-init」。如果你非常喜欢 Systemd,那么你可以使用「Systemd-boot + Systemd-based initramfs + Systemd-init」。

兼容问题

  • Systemd-boot 引导器
    • /boot 目录必须和 / 在同一分区
    • 需要 EFI 驱动以支持 XFS、Btrfs 等文件系统,且 似乎 不支持 btrfs subvolume 作为根文件系统
    • 需要修改 /root 分区的 GPT GUID 才能支持 / 加密
  • GRUB 引导器
    • 不完整支持 LUKS2
    • 不支持 TPM2/FIDO2 解密 LUKS
  • Systemd-based initramfs
    • 不支持 LUKS1

除了上述兼容问题外,由于引导器和 initramfs 无法互通加密密钥,每次启动您都要重复解锁两次 LUKS 卷。可以选择不加密 /boot 分区来解决此问题。

分区方式

基于以上前置知识,我使用了以下的分区方案:

Partition Filesystem Mount Point
/sda1 EFI (FAT32) /efi
/sda2 XFS /boot
/sda3 LUKS cr_root
/dev/mapper/cr_root Btrfs, compress=zstd:3 /
/dev/mapper/cr_root Btrfs, subvol=/@/home /home
/dev/mapper/cr_root Btrfs, subvol=/@/root /root
/dev/mapper/cr_root Btrfs, subvol=/@/var /var
/dev/mapper/cr_root Btrfs, subvol=/@/srv /srv
/dev/mapper/cr_root Btrfs, subvol=/@/opt /opt

注意:

  1. 不要忘了将 EFI 的 GPT GUID 配置为 EFI System Partition (ESP)。
  2. Btrfs 目前的实现中,所有 subvolume 共享 compress 参数,且无法分别指定。因此,上表的分区方案中所有的 subvolume 都是使用 zstd 压缩的。
  3. XFS 无法缩减卷大小,如果您有缩减卷大小需求,可以格式化为其他支持的文件系统。
  4. fstabcrypttab 和内核参数中,请 总是使用 UUID 标识分区,不要使用 label 标识分区。否则,systemd-based initramfs 可能会重复解密分区,或无法找到分区。
  5. 请确保您为 LUKS 设置了一个足够长的密码。如果您使用密钥,请确保您已经在在安全且便于销毁或擦除的外部存储器上备份了密钥。否则您的加密数据可能被轻易地使用穷举法破解。对于 SSD 和 SMR 机械磁盘的用户,开启 discard 选项(在之后配置的 crypttab 或内核参数中)以启用 TRIM 对于改善性能是必要的,但是 LUKS 不将其作为默认选项,因为会带来 潜在的安全问题。我 个人认为,从不 在 LUKS 上使用弱密码可以避免这个问题并安全的开启 discard 选项。笔者在此的观点仅代表个人,不构成对任何人的信息安全建议,您应该自己决定是否开启 discard 选项,如有更高的安全需要请咨询您所在单位的安全部门或信息安全专家。

同时,我决定采取下面的引导方式:

  • 引导器:GRUB
  • initramfs:systemd-based initramfs

开启 LUKS 的自动解密

配置 TPM2 解密 LUKS

1
systemd-cryptenroll --tpm2-device=auto --tpm2-pcrs=0+7 /dev/sda3

使用上面的命令,将 LUKS 密钥注册到 TPM。注册后,TPM 和你原先的加密方式均可解密 LUKS。其中,/dev/sda3 是被 LUKS 加密的分区。

PCR 是储存 TPM 状态的寄存器。PCR 7 指的是安全引导(Secure Boot)状态。即,如果安全引导开启/关闭的状态发生变化,则清空 TPM 中的 LUKS 密钥。

注意

  1. 仅仅关联 PCR 7 是不够的。如果需要防范冷启动攻击,您需要注册 PCR 2、4、8、9。其中,PCR 8 和 PCR 9 是内核和 initramfs 的签名,您需要在每次更新内核和 initramfs 时更新 TPM 中注册的值,否则 系统将无法启动。请查看 PCR 值定义表 并自行决定关联哪些 PCR。
  2. 部分 UEFI 固件设置必须关联至少一个 PCR 值,TPM 才会解密 LUKS。另一些 UEFI 固件要求必须注册 PCR 7 且保持 Secure Boot 为开启状态,TPM 才会工作(例如笔者的惠普笔记本)。
  3. 在配置 initramfs 开机自动解密前,请勿重启。

配置 FIDO2 解密 LUKS

若需要使用 FIDO2 解密 LUKS,您可以使用下面的命令:

1
systemd-cryptenroll --fido2-device=auto /dev/sda3

其中,/dev/sda3 是被 LUKS 加密的分区。

注意
在配置 initramfs 开机自动解密前,请勿重启。

配置开机自动解密

安装依赖

您首先需要安装 TPM2 和 FIDO2 所必须的软件,如 tpm-toolstpm-tsslibfido2。不同发行版的包名称可能不同。

配置解密

然后,您需要通过编辑内核参数或 crypttab 来开启 initramfs 中的开机自动解密。

  • 若您的发行版的 initramfs 支持 crypttab,请编辑 /etc/crypttab
  • 若您的发行版的 initramfs 不支持 crypttab,请编辑 /etc/default/grub 使用 rd.luks.options 内核参数。

注意
大多数发行版都支持 crypttab,但是不少发行版(如 Arch Linux)的 initramfs 不支持 crypttab。

以 openSUSE Tumbleweed 为例,编辑 /etc/crypttab

1
<luks_name> UUID=<luks_uuid> <luks_options>,tpm2-device=auto

1
<luks_name> UUID=<luks_uuid> <luks_options>,fido2-device=auto

其中,<luks_name> 是 LUKS 卷的映射名称,<luks_uuid> 是 LUKS 物理卷的 UUID,<luks_options> 是 LUKS 的选项。这些都已在系统安装时配置完成,请不要修改。请仅添加 tpm2-device=autofido2-device=auto。前者对应 TPM2 解密,后者对应 FIDO2 解密。

注意

  1. 请不要修改 UUID 识别方式为 LABEL 或其他卷识别方式。否则,systemd-based initramfs 可能会重复解密分区,或无法找到分区。
  2. 不同发行版的 <luks_options> 可能不同。您需要参阅您的发行版手册。

更新 initramfs

千万不要忘了更新 initramfs,否则就前功尽弃、无法启动系统了!

以 openSUSE Tumbleweed 为例,运行下面的命令:

1
dracut -f

请确保输出没有任何 error。

注意
部分发行版默认不会将 crypttab 包含在 initramfs 中。您需要配置您的 initramfs 生成器(dracutmkinitcpio 等)将 crypttab 包含在 initramfs 中。笔者使用的 openSUSE Tumbleweed 已默认将 crypttab 包含在 initramfs 中。

完成以上操作后,您可以安全的重新启动了。请务必记住或备份好您的 LUKS 密码或密钥。

您也可以配置 plymouth 来美化开机时密码输入的界面。

注意
如果使用 plymoth,在等待 FIDO 密钥确认时,会显示要求输入密码的界面。此时不输入任何密码,直接敲击 Enter,并在 FIDO 密钥上确认,即可解锁 LUKS 卷。

配置 U2F 账户认证

安装 pam-u2f。不同发行版上的包名称可能不同。

输入以下命令注册 U2F 设备:

1
sudo pamu2fcfg -u <username> >> /etc/u2f_mappings

其中,<username> 是您的用户名(用于登录的用户名,而非 root)。程序会要求您按下 U2F 设备上的按钮。

编辑 PAM 文件。PAM 文件可能是 /etc/pam.d/system-auth/etc/pam.d/common-auth

注意
请在更改 PAM 文件前,请开启一个具有 root 权限的 shell,否则若配置失败,您将无法登录。此外建议您在更改 PAM 文件前,复制一个 .bak 文件备份。如果配置失败且您已重启,可以在 GRUB 中加入内核参数 init=/bin/bash 来登入 root shell,并使用 mount -o remount,rw / 挂载根分区为可写,从而恢复 .bak 文件。

以 openSUSE Tumbleweed 为例,编辑 /etc/pam.d/common-auth

1
2
3
4
5
#%PAM-1.0
auth required pam_env.so
auth optional pam_kwallet5.so
auth sufficient pam_u2f.so authfile=/etc/u2f_mappings cue
auth required pam_unix.so try_first_pass

增加 pam_u2f.so 行。其中,cue 代表显示要求用户按下 U2F 设备上按钮提的提示(即在输入密码的界面显示「Please touch the device」)。

注意
PAM 文件的第一行必须为 #%PAM-1.0。该行不是注释,缺少该行将导致您无法登录系统。

Password-less 登录

如果您想只用 U2F 设备登录,且不允许密码登录,你可以使用 passwd -d 删除密码,并确保 PAM 文件中的 pam_unix.so 扩展不包含 null_ok 选项。

桌面环境注意事项

许多桌面环境(包括 KDE)并未适配无密码登录的情况,因此依然显示密码输入框。您无需输入任何密码,仅需按下 Enter 键,并触碰您的 U2F 设备按钮即可。

配置 U2F sudo

在配置 U2F sudo 前,请确保您的 sudo 使用的是用户的密码,而不是 root 的密码。需要注意的是,SUSE 系列的 Linux 在 sudo 时,默认要求的是 root 的密码(小插曲:此设计被 Linus Torvalds 吐槽过,他的女儿曾使用 openSUSE 发行版,在安装打印机驱动时,打电话向 Linus 询问 root 密码)。

以 openSUSE Tumbleweed 为例,编辑 /etc/sudoers

1
2
3
4
5
# Defaults    targetpw ## 注释此行
# ALL ALL=(ALL) ALL ## 同时注释此行,否则任何人都能获得 root 权限

root ALL=(ALL) ALL ## 取消注释此行
%wheel ALL=(ALL) ALL ## 取消注释此行

将您需要赋予 sudo 权限的用户添加到 sudo 组中。

注意
无论你的 sudo 使用的是 root 密码还是用户密码,请务必将您当前的用户添加到配置的允许 sudo 组中。一些发行版的默认 sudo 组是 wheel,一些则是 sudo。请在编辑完成后试着使用 sudo 并确保能够成功获取 root 权限再关闭 root shell,否则可能失去 root 权限。对于桌面环境,你应该要重启桌面环境(X 或 Wayland)。

编辑 PAM 文件 /etc/pam.d/sudo,在 include common-auth 之前添加 pam_u2f.so 行:

1
2
3
4
#%PAM-1.0
# ...lines..
auth sufficient pam_u2f.so authfile=/etc/u2f_mappings cue # 此行为您需要添加的
auth include common-auth # 此行为文件中已有的

openSUSE 上的特殊配置

openSUSE 上没有 /etc/pam.d/sudo,因此您需要创建该文件如下:

1
2
3
4
5
6
7
8
%PAM-1.0
auth requisite pam_nologin.so
auth sufficient pam_u2f.so authfile=/etc/u2f_mappings cue
auth requisite pam_nologin.so
auth include common-account
account include common-password
session required pam_loginuid.so
session include common-session

配置 U2F 无密码 sudo

注意
该配置有风险,请在配置前备份相关配置文件并确保有一个 root shell。

pam_u2f.so 行中的 sufficient 改为 required 即可。

修复 Polkit

Polkit 依然会使用 root 密码进行认证,因此您需要重新配置。

创建 /etc/polkit-1/rules.d/50-sudo.rules

1
2
3
polkit.addAdminRule(function(action, subject) {
return ["unix-group:wheel"];
});

请将 wheel 替换为您的 sudo 组。一些发行版的默认 sudo 组是 wheel,一些则是 sudo

References

  1. Trusted Platform Module - Arch Wiki
  2. Arch Boot Process - Arch Wiki
  3. dm-crypt/Encrypting an Entire System - Arch Wiki
  4. LUKS2, TPM2 and FIDO2 - openSUSE Wiki
  5. pam-u2f - Yubico Developers