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.

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.

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

Done means