summaryrefslogtreecommitdiffstatshomepage
path: root/docs/tpm-unlock.md
blob: d60b5e769bc224ed0df93541bbdd1b631fdcd357 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
# TPM Root Unlock on Halley2

Before this migration, the root token requires the approved PCR 7 pcrlock
policy. The new enrollment also requires an RSA signed PCR 11 policy at
`enter-initrd`. The `luks-root` policy reference is for root unlock; the
existing `initrd` reference remains for NvPCR setup. The root volume is
`/dev/nvme0n1p2`. Keep the tested disk passphrase and pcrlock recovery PIN
available away from this laptop.

The signing key, pcrlock policy, and LUKS metadata are host state. Do not put
them in this repo. Each kernel update must build and Secure Boot sign a UKI
with both PCR 11 signatures. A signed policy approves older UKIs that use the
same signing key and `luks-root` reference; it does not prevent rollback to
an older approved version.

## Prepare

Run these steps on Halley2, outside aibox. Stop if a check fails. No step in
this section changes the LUKS header.

```sh
pacman -Q systemd systemd-ukify mkinitcpio
sudo cryptsetup open --test-passphrase --key-slot 0 /dev/nvme0n1p2
sudo /usr/lib/systemd/systemd-pcrlock --strict=yes --pcr=7 --location=770 predict
df -h /boot
just apply
sudo mkinitcpio -P
sudo sbctl verify
just tpm-nvpcr-check
```

`systemd` and `systemd-ukify` must have the same version. The normal
`mkinitcpio -P` build replaces all four maintained UKIs. The checker verifies
their Secure Boot signatures, initrd services, and both PCR 11 signatures
against the measured contents of each image. It does not claim that the LUKS
token has changed. Before enrollment, check that the initrd has no separate
root key file or credential that could bypass the TPM test.

Back up the header and old TPM token in the encrypted root file system. These
files must stay root owned and must not go to the ESP or the repo. Check the
current LUKS metadata before using the token and slot numbers below. On this
host the existing TPM token is 0, its keyslot is 1, and the passphrase uses
slot 0. Stop if these facts have changed.

```sh
sudo cryptsetup luksDump --dump-json-metadata /dev/nvme0n1p2 | jq '{keyslots: (.keyslots|keys), tokens: (.tokens|with_entries(.value |= {type, keyslots, "tpm2-pcrs", tpm2_pcrlock, tpm2_pubkey_pcrs, tpm2_pubkey_ref}))}'
sudo install -d -m 0700 /var/lib/dotfiles/tpm-root
sudo cryptsetup luksHeaderBackup /dev/nvme0n1p2 \
  --header-backup-file /var/lib/dotfiles/tpm-root/before.header
sudo install -m 0600 /dev/null /var/lib/dotfiles/tpm-root/old-token.json
sudo cryptsetup token export --token-id 0 \
  --json-file /var/lib/dotfiles/tpm-root/old-token.json /dev/nvme0n1p2
```

## Enroll

Enroll a new keyslot with two required TPM policy shards. Do not request a
direct PCR value or a TPM PIN. Enrollment on the running system cannot test
the `enter-initrd` signature because PCR 11 has changed since that phase.

```sh
sudo systemd-cryptenroll /dev/nvme0n1p2 \
  --unlock-tpm2-device=auto --tpm2-device=auto --tpm2-pcrs= \
  --tpm2-pcrlock=/var/lib/systemd/pcrlock.json \
  --tpm2-public-key=/etc/systemd/tpm2-pcr-public-key.pem \
  --tpm2-public-key-pcrs=11 \
  --tpm2-public-key-policyref=luks-root --tpm2-with-pin=no
sudo cryptsetup luksDump --dump-json-metadata /dev/nvme0n1p2 | jq '{keyslots: (.keyslots|keys), tokens: (.tokens|with_entries(.value |= {type, keyslots, "tpm2-pcrs", "tpm2-pcr-bank", tpm2_pcrlock, tpm2_pubkey_pcrs, tpm2_pubkey_ref}))}'
```

Confirm that the new TPM token has a different token ID and keyslot, and
requires `tpm2_pcrlock`, `tpm2_pubkey_pcrs: [11]`, and
`tpm2_pubkey_ref: "luks-root"`. Keep slot 0. If there is no new token, stop.

Remove only the old token metadata. Its keyslot remains for reversible
rollback until the boot tests pass. The new token is now the only automatic
unlock path. This command assumes the old token is still ID 0.

```sh
sudo cryptsetup token remove --token-id 0 /dev/nvme0n1p2
just tpm-unlock-check --trial
```

## Test

Boot the normal Hardened entry once. Confirm automatic unlock, then run:

```sh
efibootmgr | rg 'BootCurrent|BootOrder'
just tpm-nvpcr-check
just tpm-unlock-check --trial
systemctl --failed --no-pager
```

The image check verifies all four maintained UKIs without booting each one.
The token check verifies header metadata. The main boot proves that the
combined token can unlock the root volume.

For a refusal test, make a copy of the main UKI without the `luks-root`
approval. The helper keeps the `initrd` approval and the measured sections.
Re-sign the modified image for Secure Boot, then check the new signature. The
unsigned intermediate file remains on the encrypted root file system.

```sh
sudo python3 -m scripts.tpm_negative_uki \
  /boot/EFI/Linux/arch-linux-hardened.efi \
  /var/lib/dotfiles/tpm-root/no-root-approval.unsigned.efi
sudo sbsign --key /var/lib/sbctl/keys/db/db.key \
  --cert /var/lib/sbctl/keys/db/db.pem \
  --output /boot/EFI/Linux/arch-linux-hardened-no-root-approval.efi \
  /var/lib/dotfiles/tpm-root/no-root-approval.unsigned.efi
sudo sbverify --cert /var/lib/sbctl/keys/db/db.pem \
  /boot/EFI/Linux/arch-linux-hardened-no-root-approval.efi
sudo efibootmgr --create-only --disk /dev/nvme0n1 --part 1 \
  --label 'Arch Root Policy Refusal Test' \
  --loader '\EFI\Linux\arch-linux-hardened-no-root-approval.efi'
sudo efibootmgr
```

Read the new EFI entry number. Set it for one boot with
`sudo efibootmgr --bootnext NNNN`. The test must request the disk passphrase.
Enter it and check the boot journal for the TPM policy failure. If it unlocks
automatically, stop: another automatic path still exists. The normal boot
order must stay unchanged.

After the refusal test, remove only that test entry and UKI, using its actual
entry number. Boot the normal Hardened entry again if needed. Keep the
passphrase available.

```sh
sudo efibootmgr -b NNNN -B
sudo rm -- /boot/EFI/Linux/arch-linux-hardened-no-root-approval.efi
sudo rm -- /var/lib/dotfiles/tpm-root/no-root-approval.unsigned.efi
just tpm-nvpcr-check
just tpm-unlock-check --trial
```

## Finish

When both tests pass, remove only the old, unbound TPM keyslot. This host used
slot 1. Confirm the slot number again before running the command. Do not use a
type-wide `--wipe-slot=tpm2` operation.

```sh
sudo cryptsetup luksDump /dev/nvme0n1p2
sudo cryptsetup luksKillSlot /dev/nvme0n1p2 1
just tpm-unlock-check
sudo cryptsetup open --test-passphrase --key-slot 0 /dev/nvme0n1p2
sudo cryptsetup luksHeaderBackup /dev/nvme0n1p2 \
  --header-backup-file /var/lib/dotfiles/tpm-root/final.header
sudo mkinitcpio -P
just tpm-nvpcr-check
```

On a later approved reboot, confirm normal automatic unlock. Keep the final
header backup and the passphrase. Remove the pre-migration backup and token
export only after that boot succeeds.

## Recovery

If automatic unlock fails, use the disk passphrase. During the trial, restore
the old token if needed, after checking that token ID 0 is free. This restores
the previous, weaker automatic unlock policy. The old keyslot must still exist.

```sh
sudo cryptsetup token import --token-id 0 \
  --json-file /var/lib/dotfiles/tpm-root/old-token.json /dev/nvme0n1p2
```

If the LUKS header is damaged, use the saved header and passphrase from an
external recovery system. Header restore replaces all current token and
keyslot changes; do not use it for an ordinary policy failure. Do not clear
the TPM or its NV indices.

For an intended Secure Boot change, review the PCR 7 prediction before
updating its policy. See [systemd-pcrlock](https://man.archlinux.org/man/systemd-pcrlock.8.en),
[systemd-cryptenroll](https://man.archlinux.org/man/systemd-cryptenroll.1.en),
and [cryptsetup-token](https://man.archlinux.org/man/cryptsetup-token.8.en).