summaryrefslogtreecommitdiffstatshomepage
path: root/docs/tpm-unlock.md
blob: 8815639c3b05688839145004c5c174365ec86faf (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
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
# TPM Unlock on Arch

The root volume uses a systemd TPM enrollment with a `pcrlock` policy that
protects PCR 7. Keep a working disk passphrase and the policy recovery PIN in
a secure location accessible without unlocking this laptop.

The policy, enrollment, and generated component files are host state. Do not
copy them into this repo. Deploy this configuration only after policy creation,
enrollment, and an automatic unlock test have succeeded.

## Deploy

Run on the Arch host, outside aibox:

```sh
just apply
sudo systemctl daemon-reload
sudo systemctl enable systemd-pcrlock-make-policy.service
sudo systemctl restart systemd-pcrlock-make-policy.service
sudo journalctl -b -u systemd-pcrlock-make-policy.service --no-pager
```

The journal must show PCR 7 in the protection mask, or report that the policy
has not changed. Check for errors and unmet conditions. A successful command
exit alone does not prove that a conditional service ran.

The service runs after root is unlocked. It updates the existing TPM policy
and boot credential from the approved component files. It selects only PCR 7
and requires the policy files and the mounted EFI System Partition at `/boot`.
On a host without an existing policy, the service skips execution.

Leave `systemd-pcrlock-secureboot-policy.service` and
`systemd-pcrlock-secureboot-authority.service` disabled. These services generate
component files from the current system. This setup retains the approved
components instead of automatically accepting changes to Secure Boot settings.

## Updates

Normal kernel and initramfs updates do not require LUKS re-enrollment. Keep the
existing mkinitcpio and Secure Boot signing hooks. Policy maintenance does not
build or sign UKIs.

After a systemd update, check the prediction before rebooting:

```sh
sudo /usr/lib/systemd/systemd-pcrlock --pcr=7 --location=770 predict
```

If PCR 7 is included and the prediction completes without errors, refresh the
policy and check the journal:

```sh
sudo systemctl restart systemd-pcrlock-make-policy.service
sudo journalctl -b -u systemd-pcrlock-make-policy.service --no-pager
```

Prediction uses the current boot log. It cannot guarantee that changed firmware
or boot software will produce the same measurements on the next boot. Keep the
disk passphrase available. No unattended hook relaxes protection or replaces
LUKS enrollments.

## NvPCR Measurements

This setup initializes the four systemd NvPCRs. It records hardware identity,
the logged-in user, and the LUKS root unlock method and keyslot. It also records
the root volume key in ordinary PCR 15. The verity NvPCR has no event on this
host because it does not use dm-verity. Root unlock still uses only the existing
PCR 7 `pcrlock` policy. The NvPCR indices are separate from that policy's index.

Ukify signs an initrd PCR 11 policy with reference `initrd` and embeds its public
key in each UKI. The `sbctl` mkinitcpio post hook signs the complete UKI for
Secure Boot. Keep the PCR private key outside the repo and EFI System Partition
(ESP). Do not regenerate it during updates.

### First Activation

Run these steps on Halley2. Keep the LUKS passphrase and pcrlock recovery PIN
available without unlocking this laptop. Do not reboot until the requested test
is agreed. Stop if any command fails.

1. Install the declared Arch packages. A full Arch update may rebuild UKIs. If
   it does, verify Secure Boot signing and automatic unlock on a separate,
   approved reboot before taking the recovery copy below.

   ```sh
   sudo pacman -Syu --needed systemd-ukify efibootmgr sbsigntools
   pacman -Q systemd systemd-ukify mkinitcpio
   sudo sbctl verify
   sudo /usr/lib/systemd/systemd-pcrlock --strict=yes --pcr=7 --location=770 predict
   df -h /boot
   ```

   `systemd` and `systemd-ukify` must have the same package version. Check that
   `/boot` can hold one more complete hardened UKI and temporary replacement
   files. Leave enough free space for normal kernel updates.

2. Save the current boot images and affected `/etc` files in a root-only backup.
   Do this once, before `just apply`. Do not overwrite this backup on a retry.

   ```sh
   sudo bash -euo pipefail -c '
     backup=/var/lib/dotfiles/tpm-nvpcr/backup
     test ! -e "$backup"
     install -d -m 0700 "$backup"
     cp -a /boot/EFI/Linux/arch-linux-hardened*.efi \
       /boot/EFI/Linux/arch-linux-lts*.efi "$backup/"
     tar -C / -cpf "$backup/etc.tar" \
       etc/nvpcr/cryptsetup.nvpcr etc/nvpcr/hardware.nvpcr \
       etc/nvpcr/login.nvpcr etc/nvpcr/verity.nvpcr \
       etc/systemd/system/systemd-pcrlogin@.service \
       etc/systemd/system/systemd-pcrproduct.service \
       etc/mkinitcpio.conf.d/60-no-nvpcr.conf \
       etc/mkinitcpio.conf etc/kernel/cmdline \
       etc/kernel/cmdline-linux-hardened
   '
   ```

3. Copy and check a working recovery UKI. Use `--create-only` so this entry does
   not change the normal EFI boot order. Read its entry number from `efibootmgr -v`.

   ```sh
   sudo cp -a /boot/EFI/Linux/arch-linux-hardened.efi \
     /boot/EFI/Linux/arch-linux-hardened-nvpcr-recovery.efi
   sudo sbverify --cert /var/lib/sbctl/keys/db/db.pem \
     /boot/EFI/Linux/arch-linux-hardened-nvpcr-recovery.efi
   sudo efibootmgr --create-only --disk /dev/nvme0n1 --part 1 \
     --label 'Arch NvPCR Recovery' \
     --loader '\EFI\Linux\arch-linux-hardened-nvpcr-recovery.efi'
   sudo efibootmgr -v
   ```

   On an approved reboot, select this entry once with `efibootmgr --bootnext`
   or the firmware menu. Confirm automatic unlock. Return to the normal entry.
   The recovery copy is outside all mkinitcpio presets.

4. Create a dedicated PCR signing key pair if neither file exists. Ukify refuses
   to replace existing key files. Check permissions and that the keys match.

   ```sh
   sudo ukify --pcr-private-key=/etc/systemd/tpm2-pcr-private-key.pem \
     --pcr-public-key=/etc/systemd/tpm2-pcr-public-key.pem genkey
   sudo stat -c '%U:%G:%a %n' /etc/systemd/tpm2-pcr-private-key.pem
   sudo openssl pkey -in /etc/systemd/tpm2-pcr-private-key.pem -pubout |
     sudo cmp - /etc/systemd/tpm2-pcr-public-key.pem
   ```

   The private file must be `root:root:600`. If either file already exists,
   verify the pair and do not run `genkey` again. Never copy the private key
   into the repo, an initramfs, or the ESP.

5. Apply the repo files and remove only the prior masks. The deployment checks
   the key pair, ukify version, and recovery image before it changes `/etc`.
   Chezmoi does not delete files that were removed from its source directory;
   `tpm-nvpcr-unmask` checks their exact old contents before removing them.

   ```sh
   just apply
   just tpm-nvpcr-unmask
   ```

6. Build and sign all four candidate UKIs on the root filesystem. These
   commands do not replace the active boot images.

   ```sh
   sudo install -d -m 0700 /var/lib/dotfiles/tpm-nvpcr/stage
   sudo mkinitcpio -k /boot/vmlinuz-linux-hardened \
     -U /var/lib/dotfiles/tpm-nvpcr/stage/arch-linux-hardened.efi \
     --cmdline /etc/kernel/cmdline-linux-hardened \
     --ukiconfig /etc/kernel/uki.conf
   sudo mkinitcpio -k /boot/vmlinuz-linux-hardened \
     -U /var/lib/dotfiles/tpm-nvpcr/stage/arch-linux-hardened-fallback.efi \
     --cmdline /etc/kernel/cmdline-linux-hardened \
     --ukiconfig /etc/kernel/uki.conf -S autodetect
   sudo mkinitcpio -k /boot/vmlinuz-linux-lts \
     -U /var/lib/dotfiles/tpm-nvpcr/stage/arch-linux-lts.efi \
     --cmdline /etc/kernel/cmdline --ukiconfig /etc/kernel/uki.conf
   sudo mkinitcpio -k /boot/vmlinuz-linux-lts \
     -U /var/lib/dotfiles/tpm-nvpcr/stage/arch-linux-lts-fallback.efi \
     --cmdline /etc/kernel/cmdline --ukiconfig /etc/kernel/uki.conf \
     -S autodetect
   just tpm-nvpcr-check \
     /var/lib/dotfiles/tpm-nvpcr/stage/arch-linux-hardened.efi \
     /var/lib/dotfiles/tpm-nvpcr/stage/arch-linux-hardened-fallback.efi \
     /var/lib/dotfiles/tpm-nvpcr/stage/arch-linux-lts.efi \
     /var/lib/dotfiles/tpm-nvpcr/stage/arch-linux-lts-fallback.efi
   ```

   The check reads `.pcrpkey`, `.pcrsig`, and `.cmdline` using ukify. It uses
   `lsinitcpio` to check the four required initrd units and verifies each
   Secure Boot signature with the enrolled `sbctl` certificate. Stop on any
   mismatch.

7. Copy only the checked hardened default image to a separate ESP path and
   create a one-time test entry. Leave the normal and recovery images intact.
   Read the new entry number from `efibootmgr -v`; do not assume it is `0002`.

   ```sh
   sudo install -m 0644 \
     /var/lib/dotfiles/tpm-nvpcr/stage/arch-linux-hardened.efi \
     /boot/EFI/Linux/arch-linux-hardened-nvpcr-test.efi
   just tpm-nvpcr-check /boot/EFI/Linux/arch-linux-hardened-nvpcr-test.efi
   sudo efibootmgr --create-only --disk /dev/nvme0n1 --part 1 \
     --label 'Arch NvPCR Test' \
     --loader '\EFI\Linux\arch-linux-hardened-nvpcr-test.efi'
   sudo efibootmgr -v
   ```

   Only after these commands succeed, set `BootNext` to that entry and perform
   one approved reboot. Keep the disk passphrase available. Confirm automatic
   unlock, `BootCurrent` equal to the test entry, and the checks below. The
   normal boot order remains unchanged. Do not publish the other images until
   the test passes. If the test fails, boot the normal or recovery entry and
   inspect the journal before trying again.

8. After the one-time test passes, copy each checked image to the ESP under a
   temporary name, then rename it
   into place before copying the next one. This needs space for only one extra
   image at a time. Do not reboot after a partial copy. Restore from the backup
   if a copy or verification fails.

   ```sh
   sudo bash -euo pipefail -c '
     for name in arch-linux-hardened.efi arch-linux-hardened-fallback.efi \
       arch-linux-lts.efi arch-linux-lts-fallback.efi; do
       install -m 0644 "/var/lib/dotfiles/tpm-nvpcr/stage/$name" \
         "/boot/EFI/Linux/$name.next"
       mv -f "/boot/EFI/Linux/$name.next" "/boot/EFI/Linux/$name"
     done
   '
   sudo sbctl verify
   just tpm-nvpcr-check /boot/EFI/Linux/arch-linux-hardened.efi \
     /boot/EFI/Linux/arch-linux-hardened-fallback.efi \
     /boot/EFI/Linux/arch-linux-lts.efi \
     /boot/EFI/Linux/arch-linux-lts-fallback.efi
   ```

### Boot Checks

After the one-time test boot, confirm automatic unlock and run:

```sh
just tpm-nvpcr-check /boot/EFI/Linux/arch-linux-hardened-nvpcr-test.efi
sudo env SYSTEMD_PAGER=cat /usr/lib/systemd/systemd-pcrlock \
  --strict=yes --pcr=7 --location=770 predict
sudo journalctl -b --no-pager \
  -u systemd-tpm2-setup-early.service \
  -u systemd-pcrnvdone.service \
  -u systemd-pcrproduct.service \
  -u systemd-pcrlogin@1000.service \
  -u systemd-cryptsetup@root.service \
  -u systemd-pcrlock-make-policy.service
systemctl --failed --no-pager
systemd-analyze nvpcrs
```

The early setup and NvPCR separator must succeed. The `hardware`, `login`, and
`cryptsetup` records must appear after their events; `verity` can stay at its
initial value. Check that PCR 7 is still the only disk-unlock policy PCR. After
publishing all four images, reboot into the normal hardened entry and run
`just tpm-nvpcr-check` with no image arguments. Boot-test hardened and LTS
default and fallback UKIs with explicit approval.
Then rebuild with `sudo mkinitcpio -P` and repeat a normal boot test. Keep the
recovery UKI until these checks pass.

After the final boot test, verify the disk passphrase without activating a new
mapping. Keyslot 0 is the passphrase slot on Halley2; check the LUKS header if
this test fails. Do not type the passphrase on the command line.

```sh
sudo cryptsetup open --test-passphrase --key-slot 0 /dev/nvme0n1p2
```

If the test succeeds, remove the temporary recovery and test boot entries and
UKIs. Check that `BootCurrent` is 0000 and that entries 0001 and 0002 still
refer to these exact files before running the removal commands. The normal
hardened and LTS entries must stay in `BootOrder`.

```sh
efibootmgr -v
sudo efibootmgr -b 0001 -B
sudo efibootmgr -b 0002 -B
sudo rm -- /boot/EFI/Linux/arch-linux-hardened-nvpcr-recovery.efi \
  /boot/EFI/Linux/arch-linux-hardened-nvpcr-test.efi
efibootmgr
sudo sbctl verify
just tpm-nvpcr-check
```

The recovery copy is not updated by `mkinitcpio`. Keeping it on the ESP leaves
an old signed boot path that can unlock under the current PCR 7 policy. The
NvPCRs record boot events, but the root TPM enrollment does not require their
values. Removal of these two copies does not prevent booting another old UKI
signed by a trusted Secure Boot key. Keep the disk passphrase and the root-only
backup for recovery from external media. Audit the LUKS TPM tokens separately
before claiming that boot-image measurements are required for unlock.

### Rollback

If a new image does not boot or automatic unlock fails, select the recovery
entry and use the disk passphrase if needed. On the running host, restore the
saved images and exact `/etc` files. Do not remove any TPM indices or keyslots.

```sh
sudo install -d -m 0700 /var/lib/dotfiles/tpm-nvpcr
sudo touch /var/lib/dotfiles/tpm-nvpcr/rollback
sudo bash -euo pipefail -c '
  cp -a /var/lib/dotfiles/tpm-nvpcr/backup/arch-linux-hardened*.efi \
    /var/lib/dotfiles/tpm-nvpcr/backup/arch-linux-lts*.efi /boot/EFI/Linux/
'
sudo tar -C / -xpf /var/lib/dotfiles/tpm-nvpcr/backup/etc.tar
sudo rm -f /etc/kernel/uki.conf /etc/initcpio/install/nvpcr
sudo systemctl daemon-reload
sudo sbctl verify
```

Revert this task's repo commit before another `just apply`. The rollback marker
blocks redeployment until then. Keep the key pair and root-only backup for
diagnosis; the temporary recovery UKI is only for first activation.

Sources: [systemd 262 release notes](https://github.com/systemd/systemd/releases/tag/v262),
[ukify](https://github.com/systemd/systemd/blob/v262/man/ukify.xml), and
[systemd TPM measurement guide](https://github.com/systemd/systemd/blob/v262/docs/TPM2_PCR_MEASUREMENTS.md).

## Recovery

If automatic unlock fails, use the disk passphrase. Inspect the boot journal
and `systemd-pcrlock log` before changing policy. Do not clear the TPM, remove
the policy, or remove password slots.

After confirming that a Secure Boot change was intended, regenerate its
component files:

```sh
sudo /usr/lib/systemd/systemd-pcrlock lock-secureboot-policy
sudo /usr/lib/systemd/systemd-pcrlock lock-secureboot-authority
sudo /usr/lib/systemd/systemd-pcrlock --pcr=7 --location=770 predict
```

Proceed only if PCR 7 is included. Update the existing policy with its recovery
PIN when the old policy no longer permits updates:

```sh
sudo /usr/lib/systemd/systemd-pcrlock \
  --pcr=7 --location=770 --recovery-pin=query make-policy
```

Enter the policy recovery PIN, not the disk passphrase. Do not record this
session or share the PIN. Reboot to test automatic unlock. An update to the
existing policy normally needs neither re-enrollment nor a UKI rebuild.

Reference: [systemd-pcrlock manual](https://man.archlinux.org/man/systemd-pcrlock.8.en).