From: Lennart Poettering Date: Fri, 31 Oct 2025 17:16:07 +0000 (+0100) Subject: doc: document NvPCRs briefly X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=refs%2Fpull%2F39463%2Fhead;p=thirdparty%2Fsystemd.git doc: document NvPCRs briefly --- diff --git a/docs/TPM2_PCR_MEASUREMENTS.md b/docs/TPM2_PCR_MEASUREMENTS.md index c6164b76d2c..abd280c0048 100644 --- a/docs/TPM2_PCR_MEASUREMENTS.md +++ b/docs/TPM2_PCR_MEASUREMENTS.md @@ -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`