Pre-Calculate UKI TPM Measurements with systemd-measure
systemd-measure calculates the TPM PCR values a Unified Kernel Image should produce, before you find out the hard way at boot. This guide covers inspecting PCR 11, calculating measurements, and signing the result for later use. The examples target systemd 255.4-1ubuntu8.17, installed here.
The route
Jump straight to the step you need, or tick off Done means at the end.
Allow about 20 minutes for a calculation-only workflow. You need a UKI's component files, or a currently booted system with TPM2 access. Signing additionally needs an available TPM2 device, an RSA private key, and careful handling of the resulting policy. Do not enrol a key into a LUKS volume until you have tested recovery with another decryption method.
1. Confirm the installed interface
Check the binary and package before relying on examples. These are ordinary read-only commands:
$ /usr/lib/systemd/systemd-measure --version
systemd 255 (255.4-1ubuntu8.17)
$ dpkg-query -W -f='${Package} ${Version}\n' systemd
systemd 255.4-1ubuntu8.17
The documented interface is experimental and may change. The command has three verbs: status, calculate, and sign. status is also the default when no verb is supplied.
Checkpoint
Make sure you are running the installed path above, not a similarly named helper from another systemd installation.
2. Compare the current PCR values
Start with the local state. This reads TPM measurements and does not alter them:
$ /usr/lib/systemd/systemd-measure status --no-pager
11:sha256=...
12:sha256=...
13:sha256=...
Your hashes will differ. The default output covers PCR 11, 12, and 13 using the available default banks. To request one bank explicitly, repeat --bank when needed:
$ /usr/lib/systemd/systemd-measure status --bank=sha256 --json=pretty --no-pager
If this fails, check that the machine has a suitable TPM2 device. Enumerate devices without changing anything:
$ /usr/lib/systemd/systemd-measure --tpm2-device=list status --no-pager
A TPM2 device is required for status, and also for sign, even though a signature is portable and is not bound to the TPM's current state.
3. Calculate expected measurements from UKI components
For a reproducible calculation, pass the ELF kernel and whichever UKI sections are present. Only --linux is mandatory; the other paths are optional and each option may be used once:
$ /usr/lib/systemd/systemd-measure calculate \
--linux=/path/to/vmlinux \
--osrel=/path/to/os-release.txt \
--cmdline=/path/to/cmdline.txt \
--initrd=/path/to/initrd.cpio \
--splash=/path/to/splash.bmp \
--dtb=/path/to/devicetree.dtb \
--bank=sha256 \
--json=pretty
Expected output is a JSON representation of PCR 11 measurements for the selected bank. With the default non-JSON format, the shape is similar to 11:sha256=<hex-digest>. Do not copy the digest from this example into a policy: it depends on the exact bytes of every supplied component and on the selected boot phases.
--linuxis the ELF kernel a UKI wraps, not necessarily the final PE/EFI file.--osrel,--cmdline,--initrd,--splash,--dtb,--uname,--sbat, and--pcrpkeyeach correspond to a UKI section.
For the currently booted measurement instead, use --current in place of the component paths:
$ /usr/lib/systemd/systemd-measure calculate --current --bank=sha256
11:sha256=<current-pcr-11-digest>
This is a snapshot of current PCR 11 state, not a reconstruction of a future kernel. Treat it as an expectation only when the boot inputs and phase sequence are controlled.
4. Make boot-phase expectations explicit
By default, calculations cover four useful phases: entering the initrd, leaving it, completing sysinit, and reaching system readiness. To request a particular phase, pass --phase. The value is a colon-separated path:
$ /usr/lib/systemd/systemd-measure calculate \
--linux=/path/to/vmlinux \
--initrd=/path/to/initrd.cpio \
--phase=enter-initrd \
--phase=enter-initrd:leave-initrd \
--bank=sha256
Repeat the option for multiple outputs. A signature restricted to enter-initrd can be useful for a secret needed only while the initrd is running, but it will not be interchangeable with a signature covering later runtime. Verify the phase policy with the service that consumes it before deploying it.
5. Sign the calculated values
Warning
Signing is security-sensitive. Keep the private key readable only by the intended operator, and redirect output to a new file rather than overwriting an existing policy.
$ umask 077
$ /usr/lib/systemd/systemd-measure sign \
--linux=/path/to/vmlinux \
--osrel=/path/to/os-release.txt \
--cmdline=/path/to/cmdline.txt \
--initrd=/path/to/initrd.cpio \
--pcrpkey=/path/to/tpm2-pcr-public.pem \
--bank=sha256 \
--private-key=/path/to/tpm2-pcr-private.pem \
--public-key=/path/to/tpm2-pcr-public.pem \
--json=pretty \
> /path/to/tpm2-pcr-signature.json
$ jq empty /path/to/tpm2-pcr-signature.json
The output is a JSON signature object. --private-key and --public-key are the RSA pair used to sign and verify the result. --pcrpkey is different: it selects the public key embedded in the UKI's .pcrpkey section. They are often the same PEM public key, but they serve different roles. If --public-key is omitted, systemd-measure derives it from the private key.
Unlike calculation, signing requires a suitable TPM2 device. Check it with --tpm2-device=list first, or select one explicitly with --tpm2-device=/dev/tpmrm0. Do not use --tpm2-device=auto unless exactly one suitable device is expected.
6. Combine signatures without overwriting the source
To add a second key or phase policy, use --append. The existing JSON file is read but not modified:
$ /usr/lib/systemd/systemd-measure sign \
--append=/path/to/tpm2-pcr-signature.json \
--linux=/path/to/vmlinux \
--initrd=/path/to/initrd.cpio \
--bank=sha256 \
--private-key=/path/to/initrd-private.pem \
--public-key=/path/to/initrd-public.pem \
--phase=enter-initrd \
--json=pretty \
> /path/to/tpm2-pcr-signature-combined.json
$ jq empty /path/to/tpm2-pcr-signature-combined.json
Recovery
Added signatures are deduplicated. Keep the original until the combined object has been checked and tested. Recovery is straightforward: remove the newly created combined file and continue using the unchanged original. This does not revoke a key already enrolled elsewhere.
Common traps
- Do not confuse
--pcrpkeywith--public-key. One describes UKI data and the other verifies the generated signature. - Do not assume a matching PCR value after changing the kernel, initrd, command line, os-release data, boot phase, or PCR bank.
- Do not use a successful calculation as proof that a LUKS volume can recover. Enrolment with
systemd-cryptenrollchanges disk metadata and needs a tested recovery path. - Use elevated privileges only when your key files, TPM device permissions, or later enrolment workflow require them. The read-only inspection and calculation examples normally do not.
Done means
- You confirmed the installed systemd-measure version and binary.
- You inspected current PCR values and checked TPM2 device availability.
- You calculated PCR 11 from the exact UKI components and selected banks.
- You recorded boot phases when a policy is phase-sensitive.
- You kept private keys protected and verified generated JSON before using it.
- You have not enrolled a policy or changed a LUKS volume without a separate recovery test.