现代设备加密实践 [2/3]

使用 BitLocker / LUKS 闷声发大财

本页大概是 cheatsheet 一类的东西,留个简单记录备查不算坏。

LUKS setup (with systemd) #

以 Arch Linux 为例,其它发行版思路基本一致:使用 systemd-ukify 生成并签名 UKI

Partitioning #

fdisk
 1> fdisk /dev/nvme0n1
 2m
 3g
 4n
 5
 6
 7
 8+700M
 9n
10
11
12
13
14t
151
16L
171
18p
19w
20> mkfs.fat -F32 /dev/nvme0n1p1
1cryptsetup luksFormat /dev/nvme0n1p2
2cryptsetup open /dev/nvme0n1p2 arch_root
3cryptsetup --perf-no_read_workqueue --perf-no_write_workqueue --allow-discards --persistent refresh arch_root
4mkfs.ext4 /dev/mapper/arch_root
5mount /dev/mapper/arch_root /mnt
6mkdir -p /mnt/boot
7mount /dev/nvme0n1p1 /mnt/boot

archinstall #

Note: 完整安装需要做更多事情,比如 改密码、加用户、启用 NetworkManager

1pacstrap -K /mnt base linux linux-firmware efibootmgr systemd-ukify vim intel-ucode #amd-ucode
2genfstab -U /mnt >> /mnt/etc/fstab
3arch-chroot /mnt
4ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime && hwclock --systohc
5sed -i 's/#en_US.UTF-8/en-US.UTF-8/' /etc/locale.gen && locale-gen
6echo "LANG=en_US.UTF-8" > /etc/locale.conf
7echo "localhost" > /etc/hostname
8touch /etc/vconsole.conf
9bootctl install

postinstall #

查看 blkid 得到:

/dev/nvme0n1p2: UUID="f6aa37ef-1537-4610-ae99-95262aed4a36" TYPE="crypto_LUKS" ...
/dev/mapper/arch_root: UUID="8ac7c8ed-31ec-48ce-8b58-cd61d9b0260a" BLOCK_SIZE="4096" TYPE="ext4"

相应地,写入 /etc/crypttab.initramfs 并 chmod 为 0600

# Configuration for encrypted block devices.
# See crypttab(5) for details.

# <name>       <device>                                     <password>              <options>
arch_root UUID=f6aa37ef-1537-4610-ae99-95262aed4a36 - luks,discard#,tpm2-device=auto,tpm2-measure-pcr=yes,fido2-device=auto

写入 /etc/kernel/cmdline

root=UUID=8ac7c8ed-31ec-48ce-8b58-cd61d9b0260a rw

修改 /etc/mkinitcpio.d/linux.preset

11,12c11,12
< default_image="/boot/initramfs-linux.img"
< #default_uki="/efi/EFI/Linux/arch-linux.efi"
---
> #default_image="/boot/initramfs-linux.img"
> default_uki="/boot/EFI/Linux/arch-linux.efi"

修改 /etc/mkinitcpio.conf,增加 sd-encrypt hook 并关闭 initramfs 压缩。

--- a/mkinitcpio.conf
+++ b/mkinitcpio.conf
@@ -52,7 +52,7 @@
 #
 ##   NOTE: If you have /usr on a separate partition, you MUST include the
 #    usr and fsck hooks.
-HOOKS=(base systemd autodetect microcode modconf kms keyboard keymap sd-vconsole block filesystems fsck)
+HOOKS=(base systemd autodetect microcode modconf kms keyboard sd-vconsole sd-encrypt block filesystems fsck)

 # COMPRESSION
 # Use this to compress the initramfs image. By default, zstd compression
@@ -65,6 +65,7 @@
 #COMPRESSION="xz"
 #COMPRESSION="lzop"
 #COMPRESSION="lz4"
+COMPRESSION="cat"

 # COMPRESSION_OPTIONS
 # Additional options for the compressor

执行 mkinitcpio -P,重启进入系统

Secure Boot #

Foxboron/sbctl

TPM2 enrollment #

生成用于 PCR 签名的公私钥

ukify genkey \
    --pcr-private-key=/etc/systemd/tpm2-pcr-private-key.pem \
    --pcr-public-key=/etc/systemd/tpm2-pcr-public-key.pem

写入 /etc/kernel/uki.conf

 1[UKI]
 2PCRBanks=sha256
 3PCRPKey=/etc/systemd/tpm2-pcr-public-key.pem
 4
 5[PCRSignature:initrd]
 6Phases=enter-initrd
 7PCRPrivateKey=/etc/systemd/tpm2-pcr-private-key.pem
 8PCRPublicKey=/etc/systemd/tpm2-pcr-public-key.pem
 9
10[PCRSignature:system]
11Phases=enter-initrd:leave-initrd enter-initrd:leave-initrd:sysinit
12       enter-initrd:leave-initrd:sysinit:ready
13PCRPrivateKey=/etc/systemd/tpm2-pcr-private-key.pem
14PCRPublicKey=/etc/systemd/tpm2-pcr-public-key.pem

增加 tpm2 slot

systemd-cryptenroll \
  --wipe-slot=tpm2 \
  --tpm2-device=auto \
  --tpm2-pcrs=7+15:sha256=0000000000000000000000000000000000000000000000000000000000000000 \
# --tpm2-public-key=/etc/systemd/tpm2-pcr-public-key.pem \
# --tpm2-public-key-pcrs=11:sha256 \
  --tpm2-with-pin=yes \
  /dev/nvme0n1p2

修改 /etc/crypttab.initramfs 取消 discard 标志后的注释,执行 mkinitcpio -P

FIDO2 enrollment #

> sudo systemd-cryptenroll --fido2-device=list
PATH         MANUFACTURER PRODUCT        COMPATIBLE RK CLIENTPIN UP UV ALWAYSUV
/dev/hidraw8 canokeys.org CanoKey Pigeon ✓          ✓  ✓         ✓  ✗  ✗

Legend: RK        → Resident key
        CLIENTPIN → PIN request
        UP        → User presence
        UV        → User verification
        AlwaysUV  → User verification Required

> systemd-cryptenroll /dev/nvme0n1p2 --fido2-device=auto --fido2-credential-algorithm=eddsa

更换 passphrase #

cryptsetup luksChangeKey /dev/nvme0n1p2

Note: 由于启用了 PCR 15,--unlock-tpm2-device 不会工作,因此绝对不可以直接使用 systemd-cryptenroll --wipe-slot=password

Special: LUKS + Opal #

需要 sedutil

首先使用 sedutil-cli --PSIDrevert 指令 take ownership

并在设置 LUKS 分区时,cryptsetup luksFormat 指令携带 --hw-opal-only 参数

Special: 设置 TPM lockout #

需要 tpm2-tools,见 man page [1] [2]

Measured Boot 是如何工作的 #

观察以下指令输出:

mkinitcpio
> mkinitcpio -p linux
==> Building image from preset: /etc/mkinitcpio.d/linux.preset: 'default'
==> Using default configuration file: '/etc/mkinitcpio.conf'
  -> -k /boot/vmlinuz-linux -U /boot/EFI/Linux/arch-linux.efi
// ..snip..
==> Initcpio image generation successful
==> Creating unified kernel image: '/boot/EFI/Linux/arch-linux.efi'
  -> Using ukify to build UKI
Using config file: /etc/kernel/uki.conf
+ /usr/lib/systemd/systemd-measure sign --osrel= --cmdline= --uname= --pcrpkey=/etc/systemd/tpm2-pcr-public-key.pem --linux=/boot/vmlinuz-linux --initrd= --sbat= --bank=sha256 --private-key=/etc/systemd/tpm2-pcr-private-key.pem --public-key=/etc/systemd/tpm2-pcr-public-key.pem --phase=enter-initrd
+ /usr/lib/systemd/systemd-measure sign --osrel= --cmdline= --uname= --pcrpkey=/etc/systemd/tpm2-pcr-public-key.pem --linux=/boot/vmlinuz-linux --initrd= --sbat= --bank=sha256 --private-key=/etc/systemd/tpm2-pcr-private-key.pem --public-key=/etc/systemd/tpm2-pcr-public-key.pem --phase=enter-initrd:leave-initrd --phase=enter-initrd:leave-initrd:sysinit --phase=enter-initrd:leave-initrd:sysinit:ready
Wrote unsigned /boot/EFI/Linux/arch-linux.efi
==> Unified kernel image generation successful
==> Running post hooks
  -> Running post hook: [sbctl]
Signing /boot/EFI/Linux/arch-linux.efi
✓ Signed /boot/EFI/Linux/arch-linux.efi
==> Post processing done
ukify inspect
> ukify inspect /boot/EFI/Linux/arch-linux.efi
.sbat:
  size: 323 bytes
  sha256:
  text:
    sbat,1,SBAT Version,sbat,1,https://github.com/rhboot/shim/blob/main/SBAT.md
    systemd-stub,1,The systemd Developers,systemd,260,https://systemd.io/
    systemd-stub.arch,1,Arch Linux,systemd,260,https://archlinux.org/packages/core/x86_64/systemd/
    uki,1,UKI,uki,1,https://uapi-group.org/specifications/specs/unified_kernel_image/
.osrel:
  size: 408 bytes
  sha256: 1c6bb63999d11ef6714d9f182b3b008315677df4d0386928fb5a00918a055890
  text:
    VERSION_ID=6.19.9-arch1-1
    NAME="Arch Linux"
    PRETTY_NAME="Arch Linux"
    ID=arch
    BUILD_ID=rolling
    ANSI_COLOR="38;2;23;147;209"
    HOME_URL="https://archlinux.org/"
    DOCUMENTATION_URL="https://wiki.archlinux.org/"
    SUPPORT_URL="https://bbs.archlinux.org/"
    BUG_REPORT_URL="https://gitlab.archlinux.org/groups/archlinux/-/issues"
    PRIVACY_POLICY_URL="https://terms.archlinux.org/docs/privacy-policy/"
    LOGO=archlinux-logo
.cmdline:
  size: 202 bytes
  sha256: 4975e8e961a40b6ea2db275e364ce49363fd24e471db3d8c37f6383b3552ac25
  text:
.uname:
  size: 14 bytes
  sha256: 8a46ec6bd08e4a101ab8e7928c033f38b2c55789fbd9237b4ba474795ffc7e44
  text:
    6.19.9-arch1-1
.pcrpkey:
  size: 451 bytes
  sha256:
  text:
.linux:
  size: 16468624 bytes
  sha256:
.initrd:
  size: 155852800 bytes
  sha256:
.pcrsig:
  size: 2100 bytes
  sha256:
  text:

有如下结论:

  • mkinitcpio 首先打包 initramfs 镜像,随后调用 ukify 生成 UKI,最后 sbctl 对生成的 EFI PE 文件做安全启动签名
  • ukify 调用了 systemd-measure,计算 PCR 11 的预期值并对其签名,输出结果对应 PE sections 中的 .pcrsig
  • /run/systemd/tpm2-pcr-signature.json 结构与 .pcrsig 一致
  • /{run,etc}/systemd/tpm2-pcr-public-key.pem 内容与 .pcrpkey 相同

实际流程为:

  • sd-stub 将 .pcr{sig,pkey} 内容 注入/.extra 下,进入 initrd 后再由 sd-tmpfiles 拷到 /run/systemd (mkinitcpio !404, !414)
  • sd-measure 根据定义的 phase (enter-initrd/leave-initrd/sysinit) 推算 PCR 11 预期值,sd-pcrphase* 在指定时机 extend PCR 11,这也是为什么 注册 时 PCR 11 不直接作为 --tpm2-pcrs 参数,因为会在系统启动的各阶段被更新
  • sd-cryptenroll 生成 secret 写入 LUKS keyslot 和 TPM,使用 PCR 和 user PIN 保护
  • sd-cryptsetup 询问用户 PIN,验证 PCR 11 的度量值,满足条件再基于前述 policy 从 TPM 中取出 secret

BitLocker setup #

根据上次设置 BitLocker 的印象在虚拟机里截了几张图,不一定对,仅供参考。

在 计算机配置 > 管理模板 > Windows 组件 > BitLocker Drive Encryption 下,启用

  • Disable new DMA devices when this computer is locked

Group Policy: Disable DMA when locked

点开 OS Drives 目录,分别启用

  • Require additional authentication at startup
  • Enable use of BitLocker authentication requiring preboot keyboard input on slates
    (因为 PBA 界面没法用屏幕键盘)
  • Allow enhanced PINs for startup

其中「Require additional authentication at startup」的设置如下所示,此处以 Pre-boot PIN 为例。仅保留一项解锁方式,否则 BitLocker 的图形向导会提示组策略冲突。

Group Policy: Allowed auth methods

上述配置下默认使用 PCR 7+11 测量。禁用 Secure Boot,然后 PBA 随便输个错误密码:

BitLocker Recovery Screen with Secure Boot disabled

禁用 TPM 后直接跳恢复界面:

BitLocker Recovery Screen with TPM disabled


最后一部分大概是现代 Android 的 Keystore 实现,虽然之前也算个半吊子 dev 但是退版本太久了,计划之后抽空翻一遍源码,搞清楚再写 ^^