]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
doc: document NvPCRs briefly 39463/head
authorLennart Poettering <lennart@poettering.net>
Fri, 31 Oct 2025 17:16:07 +0000 (18:16 +0100)
committerLennart Poettering <lennart@poettering.net>
Sun, 2 Nov 2025 20:26:13 +0000 (21:26 +0100)
docs/TPM2_PCR_MEASUREMENTS.md

index c6164b76d2cc774830fedf413daa5c2f43f5db33..abd280c004884767f609bc82df8fa35657fd3a40 100644 (file)
@@ -43,6 +43,37 @@ recognizable. Measurements currently recorded as `EV_IPL` will continue to be
 recorded as `EV_IPL`, for compatibility reasons. However, `EV_IPL` will not be
 used for new, additional measurements.
 
+## NvPCR Measurements
+
+Since the PCR number space is very small, systemd userspace supports additional
+PCRs implemented via TPM2 NV Indexes (here called *NvPCRs*, even though they
+are no less volatile than classic PCRs), using the `TPM2_NT_EXTEND` type. These
+mostly behave like real PCRs, but we can allocate them relatively freely from
+the NV index handle space.
+
+The NV index range to use for this is configurable at build time, so that
+downstreams have some flexibility to change this if they want. This uses the
+0x01d10200 NV index as base by default. To abstract the actual nvindex number
+away there's a naming concept, so that nvindexes are referenced by name string
+rather than number.
+
+NvPCRs are defined in little JSON snippets in `/usr/lib/nvpcr/*.nvpcr`, that
+match up index number and name, as well as pick a hash algorithm.
+
+There's one complication: these NV indexes (like any NV indexes) can be deleted
+by anyone with access to the TPM, and then be recreated. This could be used to
+reset the NvPCRs to zero during runtime, which defeats the whole point of
+them. Our way out: we measure a secret as first thing after creation into the
+NvPCRs. (Or actually, we measure a per-NvPCR secret we derive from a system
+secret via an HMAC of the NvPCR name and the NV index handle). This "anchoring"
+secret is stored in `/run/` + `/var/lib/` + ESP/XBOOTLDR (the latter encrypted
+as credential, locked to the TPM), to make it available at the whole runtime of
+the OS. It's only accessible to privileged processes with access to the
+TPM. Due to this, any process with access to the TPM and read access to any of
+the storage locations of the anchor secret is considered part of the TCB, as
+they are able to replay the NvPCR with their own content at will, so due care
+must be employed when designing a system that uses this feature.
+
 ## PCR Measurements Made by `systemd-boot` (UEFI)
 
 ### PCR 5, `EV_EVENT_TAG`, `loader.conf`
@@ -168,7 +199,7 @@ initrd" in UTF-16.
 → **Measured hash** covers the per-UKI sysext cpio archive (which is generated
 on-the-fly by `systemd-stub`).
 
-## PCR Measurements Made by `systemd-pcrextend` (Userspace)
+## PCR/NvPCR Measurements Made by `systemd-pcrextend` (Userspace)
 
 ### PCR 11, boot phases
 
@@ -191,6 +222,17 @@ from `/etc/machine-id`) during boot.
 formatted in hexadecimal lowercase characters (in UTF-8, without trailing NUL
 bytes).
 
+### NvPCR `hardware` (base+0), product UUID
+
+The `systemd-pcrproduct.service` service will measure the product UUID (as
+available from SMBIOS or Devicetree) of the host system, once at boot.
+
+→ **Measured hash** covers the string "product-id:" suffixed by the product
+UUID formatted in hexadecimal lowercase characters, without separators. If no
+product UUID of the local system could be determined the string
+"product-id:missing" is measured instead. Example string:
+`product-id:4691595be6a345f1833cc75fab63e475`.
+
 ### PCR 15, file system
 
 The `systemd-pcrfs-root.service` and `systemd-pcrfs@.service` services will
@@ -202,7 +244,7 @@ colon-separated strings, identifying the file system type, UUID, label as well
 as the GPT partition entry UUID, entry type UUID and entry label (in UTF-8,
 without trailing NUL bytes).
 
-## PCR Measurements Made by `systemd-cryptsetup` (Userspace)
+## PCR/NvPCR Measurements Made by `systemd-cryptsetup` (Userspace)
 
 ### PCR 15, volume key
 
@@ -214,3 +256,15 @@ system).
 → **Measured hash** covers the (binary) result of the HMAC(V,S) calculation where V
 is the LUKS volume key, and S is the string "cryptsetup:" followed by the LUKS
 volume name and the UUID of the LUKS superblock.
+
+### NvPCR `cryptsetup` (base+1), LUKS unlock mechanism/key slot
+
+The `systemd-cryptsetup@.service` service will measure information about the
+used LUKS keyslot, and in particular include the used unlock mechanism (pkcs11,
+tpm2, fido2, …) in it.
+
+→ **Measured hash** covers the string "cryptsetup-keyslot:", suffixed by the DM
+volume name, a ":" separator, the UUID of the LUKS superblock, a ":" separator,
+a brief string identifying the unlock mechanism, a ":" separator, and finally
+the LUKS slot number used. Example string:
+`cryptsetup-keyslot:root:1e023a55-60f9-4b6b-9b80-67438dc5f065:tpm2:1`