doc: document NvPCRs briefly

This commit is contained in:
Lennart Poettering
2025-10-31 18:16:07 +01:00
parent 34c687f2b3
commit 330e66f15e

View 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`