1
0
mirror of https://github.com/systemd/systemd synced 2025-11-16 23:34:46 +01:00

Compare commits

...

27 Commits

Author SHA1 Message Date
Zbigniew Jędrzejewski-Szmek
9218e4eacc mkosi: update fedora commit reference to 8e2833a5b64f7e2ce62ea0a2d0ec9e393e718dfa
* 8e2833a5b6 Automatically figure out the name of the top-level tar dir
* dffbf2beba Make sure fallback source is listed first
* 1d3b892105 Enable sysupdate and sysupdated
2025-11-03 13:31:22 +01:00
jouyouyun
10cdb58690 backlight: fix typo 2025-11-03 11:07:46 +00:00
jouyouyun
49ca2d8778 bootctl: fix typo 2025-11-03 11:30:42 +01:00
Lennart Poettering
f8fcf7de7c kernel-install: move staging area to /var/tmp/
The generated files (UKIs...) can potentially be huge, hence create them
in /var/tmp/, rather than /tmp/.
2025-11-03 09:01:44 +01:00
Lennart Poettering
8f62d20b73
Add support for nvindex-based additional PCRs for TPM2, aka "NvPCRs" (#39463)
This is based on the code from #33276, but is cleaned up, and goes for a
modified approach:

the original PR allocated nvindexes fully dynamically, and that created
big headaches, because the assignments needed to be propagated into the
early boot process, and that meant stuffing them as sidecards to the
boot UKIs.

The TCG then offered us a fixed nvindex range assigned to us, and
happily said yes to that, but since then the discussion stalled, we
couldn't get any answer from TCG on this anymore.

This code uses the range that was hinted to us to use, but not
officially assigned to us by default, but makes it build time
configurable so that downstreams can change this.

(This does *not* make it runtime configurable, because that's really
hard, because of the early boot issue again).

This PR comes with a CI test and full docs. And I think this is really a
version should that be merged.

Fixes: https://github.com/systemd/systemd/issues/29877
2025-11-03 09:00:41 +01:00
Lennart Poettering
330e66f15e doc: document NvPCRs briefly 2025-11-02 21:26:13 +01:00
Lennart Poettering
34c687f2b3 ci: add some test for the new nvpcr infra 2025-11-02 21:26:13 +01:00
Lennart Poettering
a9d02df0c7 ci: never go to raw tpm device, always go via resource manager 2025-11-02 21:14:35 +01:00
Lennart Poettering
d73d369133 analyze: add new verb for determining NvPCR values 2025-11-02 21:14:35 +01:00
Lennart Poettering
e5a2e78665 cryptsetup: automatically measure used keyslot and mechanism (i.e. fido2, tpm2, pkcs11) to an NvPCR
Fixes: #29877
2025-11-02 21:14:35 +01:00
Lennart Poettering
8a6e77f1a8 tpm2-setup: measure "anchor" extension early at boot into nvpcrs 2025-11-02 21:14:35 +01:00
Lennart Poettering
2da86d62ff tpm2-setup: split out SRK setup into a function of its own 2025-11-02 21:14:35 +01:00
Lennart Poettering
0196abbd10 pcrextend: automatically measure SMBIOS product ID at boot
Now that PCRs are not that expensive anymore, let's use them to measure
the SMBIOS product ID to one.
2025-11-02 21:14:35 +01:00
Lennart Poettering
2b90bf1730 pcrextend: make use new nvindex-based PCRs 2025-11-02 21:14:35 +01:00
Lennart Poettering
f5f26332fa catalog: improve PCR extended catalog entry 2025-11-02 21:14:35 +01:00
Lennart Poettering
b0c5c6aad8 tpm2-util: add infra for allocating nvindex-based PCRs (aka "NvPCRs")
We'd like to measure various additional things into PCRs, but all
available ones to the OS are already used for various purposes. Hence,
let's introduce a new concept of "NV Index based PCRs", i.e. let's use
TPM2 nv indexes of type TPM2_NT_EXTEND that mostly behave like real
PCRs, but which we can allocate relatively freely from the nv index
space. Let's call these "fake" PCRs "NvPCRs".

My original intention was to get a fixed NV index range assigned from
the TCG, either for Linux or for systemd as a project, but this stalled
with no further updates from the TCG for more than a year and a half
now. I was told an NV index range to use though, even if it never was
officially assigned, hence this PR uses this by default. But the range
is configurable at build time, on purpose, so that downstreams have some
flexibility to change this if they want. To abstract the actual nvindex
number away we introduce 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 nvindex (like any nvindex) 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 nvindex 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.
2025-11-02 21:14:35 +01:00
Lennart Poettering
b7e072f3fa tpm2-util: move parse for tpm2 hash algorithms up 2025-11-02 21:14:35 +01:00
Lennart Poettering
260b9e8489 tpm2-util: make tpm2_undefine_policy_nv_index() generic
We can use this to remove any kind of nvindex, hence give it a generic
name.

Also instead of passing "NONE" as session if none is specified, pass
PASSWORD instead, so that the function actually becomes useful if no
session is specified (the only user so far, pcrlock always provides a
session, hence this is no change in behaviour).
2025-11-02 21:14:35 +01:00
Lennart Poettering
d412b1629c tpm2-util: rename tpm2_extend_bytes()→tpm2_pcr_extend_bytes() and make it take struct iovec
We soon want to add the ability to extend into nvindexes in addition to
PCRs, hence rename the function to make clear it is about pcr extension.

While we are at it, switch things over to "struct iovec" as we generally
try to do it now in tpm2-util.[ch] these days.
2025-11-02 21:14:35 +01:00
Lennart Poettering
b95912446e pcrlock: make sure we can parse nv_index measurement records in TCG CEL
Make the systemd-pcrlock tool compatible with TCG CEL records that
encode measurements into nvindexes rather than PCRs.

This doesn't add code for actually predicting them, but just makes sure
we can parse them correctly and display them reasonably.
2025-11-02 21:14:35 +01:00
Lennart Poettering
3e9ff7c0d8 creds-util: initialize default PCR mask in encrypt_credential_and_warn()
If UINT32_MAX is passed in the PCR masks pick some reasonable defaults
in encrypt_credential_and_warn().

These defaults copy what "systemd-creds encrypt" uses. By adding these
defaults to the internal functions any user of them can take benefit of
them.
2025-11-02 21:14:35 +01:00
Lennart Poettering
ffe958b98f creds-util: add helper for querying system credential dirs
The dirs are constant string, but let's make them overridable via env
vars for debugging purposes.
2025-11-02 21:14:35 +01:00
Lennart Poettering
c30e3d7290 creds-util: add automatic mode for tpm2 based creds
This reworkds TPM2 based creds a bit. Instead of mapping the key type
"tpm2" directly to a TPM2 key without PK, let's map it to an "automatic"
key type that either picks PK or doesn't, depending on what's available.
That should make things easier to grok for people, as the nitty gritty
details of PK or not PK are made autmatic. Moreover it gives us more
leverage to change the TPM2 enrollment types later (for example, we
definitely want to start pinning SRK, and hook up pcrlock too, for
creds, which we currently don't).

This hence adds a new _CRED_AUTO_TPM2
pseudo-type we automatically maps to CRED_AES256_GCM_BY_TPM2_HMAC_WITH_PK
or CRED_AES256_GCM_BY_TPM2_HMAC depending if PK as available. Similar,
_CRED_AUTO_HOST_AND_TPM2 is added, which does the same for the
host/nonhost cred type.

This does not introduce any new type on the wire, it just changes how we
select the right key type.

To make the code more readable this also adds some categorization macros
for the keys, instead of repeating the list of key types at multiple
places.
2025-11-02 21:14:35 +01:00
Lennart Poettering
d2d1fc59b4 conf-files: optionally truncate suffix from discovered files 2025-11-02 21:14:35 +01:00
Lennart Poettering
86279dc970 format-table: add field type that outputs hex values prefixed with 0x 2025-11-02 21:14:35 +01:00
Lennart Poettering
7ecc69c33b fileio: add new flag READ_FULL_FILE_VERIFY_REGULAR() that checks if file we operate on is regular 2025-11-02 21:14:35 +01:00
Lennart Poettering
1acec1c890 fileio: make filename/path argument optional in xfopenat_full(), read_full_file_full() 2025-11-02 21:14:35 +01:00
49 changed files with 2329 additions and 298 deletions

View File

@ -39,7 +39,7 @@ jobs:
trigger: pull_request
fmf_url: https://src.fedoraproject.org/rpms/systemd
# This is automatically updated by tools/fetch-distro.py --update fedora
fmf_ref: ea1d871ecd6c2fe063523840c1e4cf9bcf200e32
fmf_ref: 8e2833a5b64f7e2ce62ea0a2d0ec9e393e718dfa
targets:
- fedora-rawhide-x86_64
# testing-farm in the Fedora repository is explicitly configured to use testing-farm bare metal runners as

View File

@ -734,10 +734,27 @@ Support: %SUPPORT_URL%
The Trusted Platform Module's (TPM) Platform Configuration Register (PCR)
@PCR@, on banks @BANKS@, has been extended with the string '@MEASURING@'.
Whenever the system transitions to a new runtime phase, the specified PCR is
extended with a different string, to ensure that security policies for
TPM-bound secrets and other resources are limited to specific phases of the
runtime.
System state, configuration and properties are cryptographically measured into
the security chip in an irreversible way to provide local and remote
attestation of system state and identity.
For example, whenever the system transitions to a new runtime phase, the
PCRs ares extended with a different string, to ensure that security
policies for TPM-bound secrets and other resources are limited to specific
phases of the runtime.
-- 4c2e46d266a747c6ac1460aa54484fa7
Subject: TPM NvPCR Extended
Defined-By: systemd
Support: %SUPPORT_URL%
The Trusted Platform Module's (TPM) additional Platform Configuration Register
(PCR) stored in non-volatile indexes (NV Indexes) @NVPCR@, has been extended
with the string '@MEASURING@'.
System state, configuration and properties are cryptographically measured into
the security chip in an irreversible way to provide local and remote
attestation of system state and identity.
-- f9b0be465ad540d0850ad32172d57c21
Subject: Memory Trimmed

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`

View File

@ -941,6 +941,20 @@
<xi:include href="version-info.xml" xpointer="v253"/></listitem>
</varlistentry>
<varlistentry>
<term><option>tpm2-measure-keyslot-nvpcr=</option></term>
<listitem><para>Controls whether to measure information about the used LUKS unlock keyslot to a TPM2
non-volatile index (nvindex in PCR mode). If set to to an empty string (which is the default) no TPM2
nvindex extension is done, otherwise keyslot information is measured to an nvindex of the specified
name, which is allocated if needed. It is recommended to set this to <literal>cryptsetup</literal> to
enable this logic. The slot index and the used unlock mechanism (i.e. <literal>tpm2</literal>,
<literal>fido2</literal>, <literal>pkcs11</literal>) is measured along with the activated volume name
and its UUID.</para>
<xi:include href="version-info.xml" xpointer="v259"/></listitem>
</varlistentry>
<varlistentry>
<term><option>token-timeout=</option></term>

View File

@ -201,6 +201,12 @@
<arg choice="plain">pcrs</arg>
<arg choice="opt" rep="repeat"><replaceable>PCR</replaceable></arg>
</cmdsynopsis>
<cmdsynopsis>
<command>systemd-analyze</command>
<arg choice="opt" rep="repeat">OPTIONS</arg>
<arg choice="plain">nvpcrs</arg>
<arg choice="opt" rep="repeat"><replaceable>NVPCR</replaceable></arg>
</cmdsynopsis>
<cmdsynopsis>
<command>systemd-analyze</command>
<arg choice="opt" rep="repeat">OPTIONS</arg>
@ -1059,6 +1065,24 @@ NR NAME SHA256
</example>
</refsect2>
<refsect2>
<title><command>systemd-analyze nvpcrs <optional><replaceable>NVPCR</replaceable></optional></command></title>
<para>This command shows the known TPM2 NvPCRs (additional PCRs stored in TPM2 NV indexes) along with
their identifying names and current values.</para>
<example>
<title>Example Output</title>
<programlisting>$ systemd-analyze nvpcrs
NAME NVINDEX VALUE
cryptsetup 0x1d10201 f400543943cc7215557ce672872ace5382e6d53177cc459078ba9277e41588d9
hardware 0x1d10200 e155474936d7d74c893e6ece1099a2311d572cf23becea159dabf282db754284</programlisting>
</example>
<xi:include href="version-info.xml" xpointer="v259"/>
</refsect2>
<refsect2>
<title><command>systemd-analyze srk <optional>&gt;<replaceable>FILE</replaceable></optional></command></title>

View File

@ -327,15 +327,15 @@
<listitem><para>When specified with the <command>encrypt</command> command controls the
encryption/signature key to use. Takes one of <literal>host</literal>, <literal>tpm2</literal>,
<literal>host+tpm2</literal>, <literal>null</literal>, <literal>auto</literal>,
<literal>auto-initrd</literal>. See above for details on the three key types. If set to
<literal>auto-initrd</literal>. See above for details on the key types. If set to
<literal>auto</literal> (which is the default) the TPM2 key is used if a TPM2 device is found and not
running in a container. The host key is used if <filename>/var/lib/systemd/</filename> is on
persistent media. This means on typical systems the encryption is by default bound to both the TPM2
chip and the OS installation, and both need to be available to decrypt the credential again. If
<literal>auto</literal> is selected but neither TPM2 is available (or running in container) nor
<filename>/var/lib/systemd/</filename> is on persistent media, encryption will fail. If set to
<literal>null</literal> a fixed zero length key is used (thus, in this mode no confidentiality
nor authenticity are provided!). This logic is useful to cover for systems that lack a TPM2 chip but
<literal>null</literal> a fixed zero length key is used (thus, in this mode no confidentiality nor
authenticity are provided!). This logic is useful to cover for systems that lack a TPM2 chip but
where credentials shall be generated. Note that decryption of such credentials is refused on systems
that have a TPM2 chip and where UEFI SecureBoot is enabled (this is done so that such a locked down
system cannot be tricked into loading a credential generated this way that lacks authentication

View File

@ -21,10 +21,11 @@
<refname>systemd-pcrphase-sysinit.service</refname>
<refname>systemd-pcrphase-initrd.service</refname>
<refname>systemd-pcrmachine.service</refname>
<refname>systemd-pcrproduct.service</refname>
<refname>systemd-pcrfs-root.service</refname>
<refname>systemd-pcrfs@.service</refname>
<refname>systemd-pcrextend</refname>
<refpurpose>Measure boot phase into TPM2 PCR 11, machine ID and file system identity into PCR 15</refpurpose>
<refpurpose>Measure boot phases, machine ID, product UUID and file system identity into TPM PCRs and NvPCRs</refpurpose>
</refnamediv>
<refsynopsisdiv>
@ -49,6 +50,10 @@
(see <citerefentry><refentrytitle>machine-id</refentrytitle><manvolnum>5</manvolnum></citerefentry>) into
PCR 15.</para>
<para><filename>systemd-pcrproduct.service</filename> is a system service that measures the firmware
product UUID (as provided by one of SMBIOS, Devicetree, …) into a NvPCR named
<literal>hardware</literal>.</para>
<para><filename>systemd-pcrfs-root.service</filename> and <filename>systemd-pcrfs@.service</filename> are
services that measure file system identity information (i.e. mount point, file system type, label and
UUID, partition label and UUID) into PCR 15. <filename>systemd-pcrfs-root.service</filename> does so for
@ -138,7 +143,8 @@
<title>Options</title>
<para>The <filename>/usr/lib/systemd/system-pcrextend</filename> executable may also be invoked from the
command line, where it expects the word to extend into PCR 11, as well as the following switches:</para>
command line, where it expects the word to extend into a PCR or NvPCR, as well as the following
switches:</para>
<variablelist>
<varlistentry>
@ -155,11 +161,24 @@
<term><option>--pcr=</option></term>
<listitem><para>Takes the index of the PCR to extend. If <option>--machine-id</option> or
<option>--file-system=</option> are specified defaults to 15, otherwise defaults to 11.</para>
<option>--file-system=</option> are specified defaults to 15, otherwise (and unless
<option>--product-id</option> is specified) defaults to 11. May not be combined with
<option>--nvpcr=</option>.</para>
<xi:include href="version-info.xml" xpointer="v255"/></listitem>
</varlistentry>
<varlistentry>
<term><option>--nvpcr=</option></term>
<listitem><para>Takes a name of an NvPCR to extend. NvPCRs are additional PCRs implemented via TPM NV
indexes. The name should be a short string such as <literal>hardware</literal> or
<literal>disk-encryption</literal>. If <option>--product-id</option> is specified defaults
<literal>hardware</literal>. May not be combined with <option>--pcr=</option>.</para>
<xi:include href="version-info.xml" xpointer="v259"/></listitem>
</varlistentry>
<varlistentry>
<term><option>--tpm2-device=<replaceable>PATH</replaceable></option></term>
@ -182,6 +201,17 @@
<xi:include href="version-info.xml" xpointer="v253"/></listitem>
</varlistentry>
<varlistentry>
<term><option>--early</option></term>
<listitem><para>Selects early-boot mode. Specifically this means that the NvPCR anchor secret is not
attempted to be written into <filename>/var/lib/</filename> and the boot loader partition in addition
to <filename>/run/</filename>. (Unlike the latter the former are generally not mounted and writable
during early boot or in the initrd.)</para>
<xi:include href="version-info.xml" xpointer="v259"/></listitem>
</varlistentry>
<varlistentry>
<term><option>--machine-id</option></term>
@ -191,6 +221,15 @@
<xi:include href="version-info.xml" xpointer="v253"/></listitem>
</varlistentry>
<varlistentry>
<term><option>--product-id</option></term>
<listitem><para>Instead of measuring a word specified on the command line into PCR 11, measure the
firmware's product UUID into an NvPCR named <literal>hardware</literal>.</para>
<xi:include href="version-info.xml" xpointer="v259"/></listitem>
</varlistentry>
<varlistentry>
<term><option>--file-system=</option></term>
@ -230,6 +269,13 @@
<xi:include href="version-info.xml" xpointer="v252"/></listitem>
</varlistentry>
<varlistentry>
<term><filename>/usr/lib/nvpcr/*.nvpcr</filename></term>
<listitem><para>Definition files for NvPCRs.</para>
<xi:include href="version-info.xml" xpointer="v259"/></listitem>
</varlistentry>
</variablelist>
</refsect1>

View File

@ -20,7 +20,7 @@
<refname>systemd-tpm2-setup.service</refname>
<refname>systemd-tpm2-setup-early.service</refname>
<refname>systemd-tpm2-setup</refname>
<refpurpose>Set up the TPM2 Storage Root Key (SRK) at boot</refpurpose>
<refpurpose>Set up the TPM2 Storage Root Key (SRK) and initialize NvPCRs at boot</refpurpose>
</refnamediv>
<refsynopsisdiv>
@ -33,7 +33,8 @@
<para><filename>systemd-tpm2-setup.service</filename> and
<filename>systemd-tpm2-setup-early.service</filename> are services that generate the Storage Root Key
(SRK) if it has not been generated yet, and stores it in the TPM.</para>
(SRK) if it has not been generated yet, and stores it in the TPM. If NvPCRs (additional PCR registers in
TPM NV Indexes) are defined, these are initialized with the anchoring secret.</para>
<para>The services will store the public key of the SRK key pair in a PEM file in
<filename>/run/systemd/tpm2-srk-public-key.pem</filename> and
@ -70,6 +71,28 @@
<xi:include href="version-info.xml" xpointer="v255"/></listitem>
</varlistentry>
<varlistentry>
<term><filename>/usr/lib/nvpcr/*.nvpcr</filename></term>
<listitem><para>Definition files for NvPCRs.</para>
<xi:include href="version-info.xml" xpointer="v259"/></listitem>
</varlistentry>
<varlistentry>
<term><filename>/run/credentials/@encrypted/nvpcr-anchor.*</filename></term>
<listitem><para>Encrypted NvPCR anchor secret, received into the system via system credentials.</para>
<xi:include href="version-info.xml" xpointer="v259"/></listitem>
</varlistentry>
<varlistentry>
<term><filename>$BOOT/loader/credentials/nvpcr-anchor.*.cred</filename></term>
<listitem><para>Encrypted NvPCR anchor secret, to be picked up by the kernel loader stub,
i.e. <citerefentry><refentrytitle>systemd-stub</refentrytitle><manvolnum>7</manvolnum></citerefentry>.</para>
<xi:include href="version-info.xml" xpointer="v259"/></listitem>
</varlistentry>
</variablelist>
</refsect1>

View File

@ -1343,6 +1343,7 @@ tpm2 = dependency('tss2-esys tss2-rc tss2-mu tss2-tcti-device',
tpm2_cflags = tpm2.partial_dependency(includes: true, compile_args: true)
conf.set10('HAVE_TPM2', tpm2.found())
conf.set10('HAVE_TSS2_ESYS3', tpm2.found() and tpm2.version().version_compare('>= 3.0.0'))
conf.set('TPM2_NVPCR_BASE', get_option('tpm2-nvpcr-base'))
libdw = dependency('libdw',
required : get_option('elfutils'))
@ -3028,6 +3029,7 @@ summary({
'default user $PATH' : default_user_path != '' ? default_user_path : '(same as system services)',
'systemd service watchdog' : service_watchdog == '' ? 'disabled' : service_watchdog,
'time epoch' : f'@time_epoch@ (@alt_time_epoch@)',
'TPM2 nvpcr base' : run_command(sh, '-c', 'printf 0x%x @0@'.format(get_option('tpm2-nvpcr-base')), check : true).stdout()
})
# TODO:

View File

@ -449,6 +449,8 @@ option('libfido2', type : 'feature', deprecated : { 'true' : 'enabled', 'false'
description : 'FIDO2 support')
option('tpm2', type : 'feature', deprecated : { 'true' : 'enabled', 'false' : 'disabled' },
description : 'TPM2 support')
option('tpm2-nvpcr-base', type : 'integer', value: 0x01d10200,
description : 'Base for TPM2 nvindex based PCRs')
option('elfutils', type : 'feature', deprecated : { 'true' : 'enabled', 'false' : 'disabled' },
description : 'elfutils support')
option('zlib', type : 'feature', deprecated : { 'true' : 'enabled', 'false' : 'disabled' },

View File

@ -4,5 +4,5 @@
Environment=
GIT_URL=https://src.fedoraproject.org/rpms/systemd.git
GIT_BRANCH=rawhide
GIT_COMMIT=ea1d871ecd6c2fe063523840c1e4cf9bcf200e32
GIT_COMMIT=8e2833a5b64f7e2ce62ea0a2d0ec9e393e718dfa
PKG_SUBDIR=fedora

View File

@ -68,7 +68,7 @@ _systemd_analyze() {
)
local -A VERBS=(
[STANDALONE]='time blame unit-files unit-paths exit-status compare-versions calendar timestamp timespan pcrs srk has-tpm2 smbios11 chid'
[STANDALONE]='time blame unit-files unit-paths exit-status compare-versions calendar timestamp timespan pcrs nvpcrs srk has-tpm2 smbios11 chid'
[CRITICAL_CHAIN]='critical-chain'
[DOT]='dot'
[DUMP]='dump'

View File

@ -0,0 +1,110 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#include "alloc-util.h"
#include "analyze-nvpcrs.h"
#include "analyze.h"
#include "conf-files.h"
#include "constants.h"
#include "format-table.h"
#include "hexdecoct.h"
#include "string-util.h"
#include "strv.h"
#include "tpm2-util.h"
#if HAVE_TPM2
static int add_nvpcr_to_table(Tpm2Context **c, Table *t, const char *name) {
int r;
_cleanup_free_ char *h = NULL;
uint32_t nv_index = 0;
if (c) {
if (!*c) {
r = tpm2_context_new_or_warn(/* device= */ NULL, c);
if (r < 0)
return r;
}
_cleanup_(iovec_done) struct iovec digest = {};
r = tpm2_nvpcr_read(*c, /* session= */ NULL, name, &digest, &nv_index);
if (r < 0)
return log_error_errno(r, "Failed to read NvPCR '%s': %m", name);
h = hexmem(digest.iov_base, digest.iov_len);
if (!h)
return log_oom();
} else {
r = tpm2_nvpcr_get_index(name, &nv_index);
if (r < 0)
return log_error_errno(r, "Failed to get NV index of NvPCR '%s': %m", name);
}
r = table_add_many(
t,
TABLE_STRING, name,
TABLE_UINT32_HEX_0x, nv_index,
TABLE_STRING, h);
if (r < 0)
return table_log_add_error(r);
return 0;
}
#endif
int verb_nvpcrs(int argc, char *argv[], void *userdata) {
#if HAVE_TPM2
_cleanup_(tpm2_context_unrefp) Tpm2Context *c = NULL;
_cleanup_(table_unrefp) Table *table = NULL;
int r;
bool have_tpm2 = tpm2_is_fully_supported();
if (!have_tpm2)
log_notice("System lacks full TPM2 support, not showing NvPCR state.");
table = table_new("name", "nvindex", "value");
if (!table)
return log_oom();
(void) table_set_align_percent(table, table_get_cell(table, 0, 1), 100);
(void) table_set_ersatz_string(table, TABLE_ERSATZ_DASH);
(void) table_set_sort(table, (size_t) 0);
if (!have_tpm2)
(void) table_hide_column_from_display(table, (size_t) 2);
if (strv_isempty(strv_skip(argv, 1))) {
_cleanup_strv_free_ char **l = NULL;
r = conf_files_list_nulstr(
&l,
".nvpcr",
/* root= */ NULL,
CONF_FILES_REGULAR|CONF_FILES_BASENAME|CONF_FILES_FILTER_MASKED|CONF_FILES_TRUNCATE_SUFFIX,
CONF_PATHS_NULSTR("nvpcr"));
if (r < 0)
return log_error_errno(r, "Failed to find .nvpcr files: %m");
STRV_FOREACH(i, l) {
r = add_nvpcr_to_table(have_tpm2 ? &c : NULL, table, *i);
if (r < 0)
return r;
}
} else
for (int i = 1; i < argc; i++) {
r = add_nvpcr_to_table(have_tpm2 ? &c : NULL, table, argv[i]);
if (r < 0)
return r;
}
if (table_isempty(table) && FLAGS_SET(arg_json_format_flags, SD_JSON_FORMAT_OFF))
log_notice("No NvPCRs defined.");
else {
r = table_print_with_pager(table, arg_json_format_flags, arg_pager_flags, /* show_header= */ true);
if (r < 0)
return log_error_errno(r, "Failed to output table: %m");
}
return EXIT_SUCCESS;
#else
return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "TPM2 support not enabled at build time.");
#endif
}

View File

@ -0,0 +1,4 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#pragma once
int verb_nvpcrs(int argc, char *argv[], void *userdata);

View File

@ -33,6 +33,7 @@
#include "analyze-inspect-elf.h"
#include "analyze-log-control.h"
#include "analyze-malloc.h"
#include "analyze-nvpcrs.h"
#include "analyze-pcrs.h"
#include "analyze-plot.h"
#include "analyze-security.h"
@ -258,6 +259,7 @@ static int help(int argc, char *argv[], void *userdata) {
"\n%3$sTPM Operations:%4$s\n"
" has-tpm2 Report whether TPM2 support is available\n"
" pcrs [PCR...] Show TPM2 PCRs and their names\n"
" nvpcrs [NVPCR...] Show additional TPM2 PCRs stored in NV indexes\n"
" srk [>FILE] Write TPM2 SRK (to FILE)\n"
"\n%3$sOptions:%4$s\n"
" --recursive-errors=MODE Control which units are verified\n"
@ -713,6 +715,10 @@ done:
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
"Option --offline= requires one or more units to perform a security review.");
if (arg_json_format_flags != SD_JSON_FORMAT_OFF && !STRPTR_IN_SET(argv[optind], "security", "inspect-elf", "dlopen-metadata", "plot", "fdstore", "pcrs", "nvpcrs", "architectures", "capability", "exit-status"))
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
"Option --json= is only supported for security, inspect-elf, dlopen-metadata, plot, fdstore, pcrs, nvpcrs, architectures, capability, exit-status right now.");
if (arg_threshold != 100 && !streq_ptr(argv[optind], "security"))
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
"Option --threshold= is only supported for security right now.");
@ -806,6 +812,7 @@ static int run(int argc, char *argv[]) {
{ "image-policy", 2, 2, 0, verb_image_policy },
{ "has-tpm2", VERB_ANY, 1, 0, verb_has_tpm2 },
{ "pcrs", VERB_ANY, VERB_ANY, 0, verb_pcrs },
{ "nvpcrs", VERB_ANY, VERB_ANY, 0, verb_nvpcrs },
{ "srk", VERB_ANY, 1, 0, verb_srk },
{ "architectures", VERB_ANY, VERB_ANY, 0, verb_architectures },
{ "smbios11", VERB_ANY, 1, 0, verb_smbios11 },

View File

@ -21,6 +21,7 @@ systemd_analyze_sources = files(
'analyze-inspect-elf.c',
'analyze-log-control.c',
'analyze-malloc.c',
'analyze-nvpcrs.c',
'analyze-pcrs.c',
'analyze-plot.c',
'analyze-security.c',

View File

@ -203,7 +203,7 @@ static int validate_device(sd_device *device) {
r = device_in_subsystem(device, "leds");
if (r < 0)
return log_device_debug_errno(device, r, "Failed to check if device is in backlight subsystem: %m");
return log_device_debug_errno(device, r, "Failed to check if device is in leds subsystem: %m");
if (r > 0)
return true; /* We assume LED device is always valid. */

View File

@ -416,7 +416,13 @@ static int dump_files(Hashmap *fh, const char *root, ConfFile ***ret_files, size
return 0;
}
static int copy_and_sort_files_from_hashmap(Hashmap *fh, const char *root, ConfFilesFlags flags, char ***ret) {
static int copy_and_sort_files_from_hashmap(
Hashmap *fh,
const char *suffix,
const char *root,
ConfFilesFlags flags,
char ***ret) {
_cleanup_strv_free_ char **results = NULL;
_cleanup_free_ ConfFile **files = NULL;
size_t n_files = 0, n_results = 0;
@ -432,19 +438,44 @@ static int copy_and_sort_files_from_hashmap(Hashmap *fh, const char *root, ConfF
FOREACH_ARRAY(i, files, n_files) {
ConfFile *c = *i;
const char *add = NULL;
if (FLAGS_SET(flags, CONF_FILES_BASENAME))
r = strv_extend_with_size(&results, &n_results, c->name);
add = c->name;
else if (root) {
char *p;
_cleanup_free_ char *p = NULL;
r = chaseat_prefix_root(c->result, root, &p);
if (r < 0)
return log_debug_errno(r, "Failed to prefix '%s' with root '%s': %m", c->result, root);
r = strv_consume_with_size(&results, &n_results, TAKE_PTR(p));
if (FLAGS_SET(flags, CONF_FILES_TRUNCATE_SUFFIX) && suffix) {
char *e = endswith(p, suffix);
if (!e)
continue;
*e = 0;
}
if (strv_consume_with_size(&results, &n_results, TAKE_PTR(p)) < 0)
return log_oom_debug();
continue;
} else
r = strv_extend_with_size(&results, &n_results, c->result);
add = c->result;
if (FLAGS_SET(flags, CONF_FILES_TRUNCATE_SUFFIX)) {
const char *e = endswith(add, suffix);
if (!e)
continue;
_cleanup_free_ char *n = strndup(add, e - add);
if (!n)
return log_oom_debug();
r = strv_consume_with_size(&results, &n_results, TAKE_PTR(n));
} else
r = strv_extend_with_size(&results, &n_results, add);
if (r < 0)
return log_oom_debug();
}
@ -566,7 +597,7 @@ int conf_files_list_strv(
if (r < 0)
return r;
return copy_and_sort_files_from_hashmap(fh, empty_to_root(root_abs), flags, ret);
return copy_and_sort_files_from_hashmap(fh, suffix, empty_to_root(root_abs), flags, ret);
}
int conf_files_list_strv_full(
@ -619,7 +650,7 @@ int conf_files_list_strv_at(
if (r < 0)
return r;
return copy_and_sort_files_from_hashmap(fh, /* root = */ NULL, flags, ret);
return copy_and_sort_files_from_hashmap(fh, suffix, /* root = */ NULL, flags, ret);
}
int conf_files_list_strv_at_full(
@ -747,7 +778,7 @@ int conf_files_list_with_replacement(
return log_debug_errno(r, "Failed to prefix '%s' with root '%s': %m", c->result, empty_to_root(root_abs));
}
r = copy_and_sort_files_from_hashmap(fh, empty_to_root(root_abs), flags, ret_files);
r = copy_and_sort_files_from_hashmap(fh, ".conf", empty_to_root(root_abs), flags, ret_files);
if (r < 0)
return r;

View File

@ -6,13 +6,14 @@
#include "basic-forward.h"
typedef enum ConfFilesFlags {
CONF_FILES_EXECUTABLE = 1 << 0,
CONF_FILES_REGULAR = 1 << 1,
CONF_FILES_DIRECTORY = 1 << 2,
CONF_FILES_BASENAME = 1 << 3,
CONF_FILES_FILTER_MASKED_BY_SYMLINK = 1 << 4,
CONF_FILES_FILTER_MASKED_BY_EMPTY = 1 << 5,
CONF_FILES_EXECUTABLE = 1 << 0, /* inode must be marked executable */
CONF_FILES_REGULAR = 1 << 1, /* inode must be regular file */
CONF_FILES_DIRECTORY = 1 << 2, /* inode must be directory */
CONF_FILES_BASENAME = 1 << 3, /* only return basename of file, not full path */
CONF_FILES_FILTER_MASKED_BY_SYMLINK = 1 << 4, /* implement /dev/null symlink based masking */
CONF_FILES_FILTER_MASKED_BY_EMPTY = 1 << 5, /* implement masking by empty file */
CONF_FILES_FILTER_MASKED = CONF_FILES_FILTER_MASKED_BY_SYMLINK | CONF_FILES_FILTER_MASKED_BY_EMPTY,
CONF_FILES_TRUNCATE_SUFFIX = 1 << 6, /* truncate specified suffix from return filename or path */
} ConfFilesFlags;
typedef struct ConfFile {

View File

@ -664,6 +664,12 @@ int read_full_stream_full(
if (fstat(fd, &st) < 0)
return -errno;
if (FLAGS_SET(flags, READ_FULL_FILE_VERIFY_REGULAR)) {
r = stat_verify_regular(&st);
if (r < 0)
return r;
}
if (S_ISREG(st.st_mode)) {
/* Try to start with the right file size if we shall read the file in full. Note
@ -685,7 +691,8 @@ int read_full_stream_full(
if (flags & READ_FULL_FILE_WARN_WORLD_READABLE)
(void) warn_file_is_world_accessible(filename, &st, NULL, 0);
}
}
} else if (FLAGS_SET(flags, READ_FULL_FILE_VERIFY_REGULAR))
return -EBADFD;
/* If we don't know how much to read, figure it out now. If we shall read a part of the file, then
* allocate the requested size. If we shall load the full file start with LINE_MAX. Note that if
@ -827,7 +834,6 @@ int read_full_file_full(
XfopenFlags xflags = XFOPEN_UNLOCKED;
int r;
assert(filename);
assert(ret_contents);
if (FLAGS_SET(flags, READ_FULL_FILE_CONNECT_SOCKET) && /* If this is enabled, let's try to connect to it */
@ -1002,11 +1008,10 @@ static int xfopenat_regular(int dir_fd, const char *path, const char *mode, int
/* A combination of fopen() with openat() */
assert(dir_fd >= 0 || dir_fd == AT_FDCWD);
assert(path);
assert(mode);
assert(ret);
if (dir_fd == AT_FDCWD && open_flags == 0)
if (dir_fd == AT_FDCWD && open_flags == 0 && path)
f = fopen(path, mode);
else {
_cleanup_close_ int fd = -EBADF;
@ -1016,9 +1021,18 @@ static int xfopenat_regular(int dir_fd, const char *path, const char *mode, int
if (mode_flags < 0)
return mode_flags;
fd = openat(dir_fd, path, mode_flags | open_flags);
if (fd < 0)
return -errno;
if (path) {
fd = openat(dir_fd, path, mode_flags | open_flags);
if (fd < 0)
return -errno;
} else {
if (dir_fd == AT_FDCWD)
return -EBADF;
fd = fd_reopen(dir_fd, mode_flags | open_flags);
if (fd < 0)
return fd;
}
f = take_fdopen(&fd, mode);
}
@ -1035,7 +1049,6 @@ static int xfopenat_unix_socket(int dir_fd, const char *path, const char *bind_n
int r;
assert(dir_fd >= 0 || dir_fd == AT_FDCWD);
assert(path);
assert(ret);
sk = socket(AF_UNIX, SOCK_STREAM|SOCK_CLOEXEC, 0);
@ -1084,7 +1097,6 @@ int xfopenat_full(
int r;
assert(dir_fd >= 0 || dir_fd == AT_FDCWD);
assert(path);
assert(mode);
assert(ret);

View File

@ -30,6 +30,7 @@ typedef enum {
READ_FULL_FILE_WARN_WORLD_READABLE = 1 << 3, /* if regular file, log at LOG_WARNING level if access mode above 0700 */
READ_FULL_FILE_CONNECT_SOCKET = 1 << 4, /* if socket inode, connect to it and read off it */
READ_FULL_FILE_FAIL_WHEN_LARGER = 1 << 5, /* fail loading if file is larger than specified size */
READ_FULL_FILE_VERIFY_REGULAR = 1 << 6, /* before reading, verify this is a regular file */
} ReadFullFileFlags;
int fdopen_unlocked(int fd, const char *options, FILE **ret);

View File

@ -572,7 +572,7 @@ int verb_status(int argc, char *argv[], void *userdata) {
* to _either_ of them, print a warning. */
if (!sd_id128_is_null(esp_uuid) && !sd_id128_equal(stub_partition_uuid, esp_uuid) &&
!sd_id128_is_null(xbootldr_uuid) && !sd_id128_equal(stub_partition_uuid, xbootldr_uuid))
printf("WARNING: The stub loader reports a different UUID than the detected ESP and XBOOTDLR partitions "
printf("WARNING: The stub loader reports a different UUID than the detected ESP and XBOOTLDR partitions "
"("SD_ID128_UUID_FORMAT_STR" vs. "SD_ID128_UUID_FORMAT_STR"/"SD_ID128_UUID_FORMAT_STR")!\n",
SD_ID128_FORMAT_VAL(stub_partition_uuid),
SD_ID128_FORMAT_VAL(esp_uuid),

View File

@ -148,17 +148,14 @@ static int open_credential_directory(
if (arg_system)
/* PID 1 ensures that system credentials are always accessible under the same fixed path. It
* will create symlinks if necessary to guarantee that. */
p = encrypted ?
ENCRYPTED_SYSTEM_CREDENTIALS_DIRECTORY :
SYSTEM_CREDENTIALS_DIRECTORY;
else {
r = (encrypted ? get_encrypted_system_credentials_dir : get_system_credentials_dir)(&p);
else
/* Otherwise take the dirs from the env vars we got passed */
r = (encrypted ? get_encrypted_credentials_dir : get_credentials_dir)(&p);
if (r == -ENXIO) /* No environment variable? */
goto not_found;
if (r < 0)
return log_error_errno(r, "Failed to get credentials directory: %m");
}
if (r == -ENXIO) /* No environment variable? */
goto not_found;
if (r < 0)
return log_error_errno(r, "Failed to get credentials directory: %m");
d = opendir(p);
if (!d) {
@ -958,7 +955,6 @@ static int parse_argv(int argc, char *argv[]) {
if (streq(optarg, "help")) {
if (arg_legend)
puts("Supported key types:");
return DUMP_STRING_TABLE(cred_key_type, CredKeyType, _CRED_KEY_TYPE_MAX);
}
@ -978,7 +974,7 @@ static int parse_argv(int argc, char *argv[]) {
break;
case 'T':
arg_with_key = CRED_AES256_GCM_BY_TPM2_HMAC;
arg_with_key = _CRED_AUTO_TPM2;
break;
case ARG_TPM2_DEVICE:

View File

@ -123,6 +123,7 @@ static char *arg_tpm2_pcrlock = NULL;
static usec_t arg_token_timeout_usec = 30*USEC_PER_SEC;
static unsigned arg_tpm2_measure_pcr = UINT_MAX; /* This and the following field is about measuring the unlocked volume key to the local TPM */
static char **arg_tpm2_measure_banks = NULL;
static char *arg_tpm2_measure_keyslot_nvpcr = NULL;
static char *arg_link_keyring = NULL;
static char *arg_link_key_type = NULL;
static char *arg_link_key_description = NULL;
@ -138,6 +139,7 @@ STATIC_DESTRUCTOR_REGISTER(arg_fido2_rp_id, freep);
STATIC_DESTRUCTOR_REGISTER(arg_tpm2_device, freep);
STATIC_DESTRUCTOR_REGISTER(arg_tpm2_signature, freep);
STATIC_DESTRUCTOR_REGISTER(arg_tpm2_measure_banks, strv_freep);
STATIC_DESTRUCTOR_REGISTER(arg_tpm2_measure_keyslot_nvpcr, freep);
STATIC_DESTRUCTOR_REGISTER(arg_tpm2_pcrlock, freep);
STATIC_DESTRUCTOR_REGISTER(arg_link_keyring, freep);
STATIC_DESTRUCTOR_REGISTER(arg_link_key_type, freep);
@ -555,6 +557,21 @@ static int parse_one_option(const char *option) {
log_error("Build lacks OpenSSL support, cannot measure to PCR banks, ignoring: %s", option);
#endif
} else if ((val = startswith(option, "tpm2-measure-keyslot-nvpcr="))) {
if (isempty(val)) {
arg_tpm2_measure_keyslot_nvpcr = mfree(arg_tpm2_measure_keyslot_nvpcr);
return 0;
}
if (!tpm2_nvpcr_name_is_valid(val)) {
log_warning("Invalid NvPCR name, ignoring: %s", option);
return 0;
}
if (free_and_strdup(&arg_tpm2_measure_keyslot_nvpcr, val) < 0)
return log_oom();
} else if ((val = startswith(option, "try-empty-password="))) {
r = parse_boolean(val);
@ -1012,7 +1029,7 @@ static int measure_volume_key(
if (r < 0)
return r;
if (r == 0) {
log_debug("Kernel stub did not measure kernel image into the expected PCR, skipping userspace measurement, too.");
log_debug("Kernel stub did not measure kernel image into the expected PCR, skipping userspace volume key measurement, too.");
return 0;
}
@ -1047,7 +1064,7 @@ static int measure_volume_key(
if (!s)
return log_oom();
r = tpm2_extend_bytes(c, l ?: arg_tpm2_measure_banks, arg_tpm2_measure_pcr, s, SIZE_MAX, volume_key, volume_key_size, TPM2_EVENT_VOLUME_KEY, s);
r = tpm2_pcr_extend_bytes(c, l ?: arg_tpm2_measure_banks, arg_tpm2_measure_pcr, &IOVEC_MAKE_STRING(s), &IOVEC_MAKE(volume_key, volume_key_size), TPM2_EVENT_VOLUME_KEY, s);
if (r < 0)
return log_error_errno(r, "Could not extend PCR: %m");
@ -1060,7 +1077,80 @@ static int measure_volume_key(
return 0;
#else
return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "TPM2 support disabled, not measuring.");
return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "TPM2 support disabled, not measuring volume key.");
#endif
}
static int measure_keyslot(
struct crypt_device *cd,
const char *name,
const char *mechanism,
int keyslot) {
int r;
assert(cd);
assert(name);
if (!arg_tpm2_measure_keyslot_nvpcr) {
log_debug("Not measuring unlock keyslot, deactivated.");
return 0;
}
r = efi_measured_uki(LOG_WARNING);
if (r < 0)
return r;
if (r == 0) {
log_debug("Kernel stub did not measure kernel image into the expected PCR, skipping userspace key slot measurement, too.");
return 0;
}
#if HAVE_TPM2
_cleanup_(tpm2_context_unrefp) Tpm2Context *c = NULL;
r = tpm2_context_new_or_warn(arg_tpm2_device, &c);
if (r < 0)
return r;
_cleanup_free_ char *escaped = NULL;
escaped = xescape(name, ":"); /* avoid ambiguity around ":" once we join things below */
if (!escaped)
return log_oom();
_cleanup_free_ char *k = NULL;
if (keyslot >= 0 && asprintf(&k, "%i", keyslot) < 0)
return log_oom();
_cleanup_free_ char *s = NULL;
s = strjoin("cryptsetup-keyslot:", escaped, ":", strempty(crypt_get_uuid(cd)), ":", strempty(mechanism), ":", strempty(k));
if (!s)
return log_oom();
r = tpm2_nvpcr_extend_bytes(c, /* session= */ NULL, arg_tpm2_measure_keyslot_nvpcr, &IOVEC_MAKE_STRING(s), /* secret= */ NULL, TPM2_EVENT_KEYSLOT, s);
if (r == -ENETDOWN) {
/* NvPCR is not initialized yet. Do so now. */
_cleanup_(iovec_done_erase) struct iovec anchor_secret = {};
r = tpm2_nvpcr_acquire_anchor_secret(&anchor_secret, /* sync_secondary= */ false);
if (r < 0)
return r;
r = tpm2_nvpcr_initialize(c, /* session= */ NULL, arg_tpm2_measure_keyslot_nvpcr, &anchor_secret);
if (r < 0)
return log_error_errno(r, "Failed to extend NvPCR index '%s' with anchor secret: %m", name);
r = tpm2_nvpcr_extend_bytes(c, /* session= */ NULL, arg_tpm2_measure_keyslot_nvpcr, &IOVEC_MAKE_STRING(s), /* secret= */ NULL, TPM2_EVENT_KEYSLOT, s);
}
if (r < 0)
return log_error_errno(r, "Could not extend NvPCR: %m");
log_struct(LOG_INFO,
"MESSAGE_ID=" SD_MESSAGE_TPM_NVPCR_EXTEND_STR,
LOG_MESSAGE("Successfully extended NvPCR index '%s' with '%s'.", arg_tpm2_measure_keyslot_nvpcr, s),
"MEASURING=%s", s,
"NVPCR=%s", arg_tpm2_measure_keyslot_nvpcr);
return 0;
#else
return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "TPM2 support disabled, not measuring keyslot.");
#endif
}
@ -1074,6 +1164,8 @@ static int log_external_activation(int r, const char *volume) {
static int measured_crypt_activate_by_volume_key(
struct crypt_device *cd,
const char *name,
const char *mechanism,
int keyslot,
const void *volume_key,
size_t volume_key_size,
uint32_t flags) {
@ -1091,18 +1183,19 @@ static int measured_crypt_activate_by_volume_key(
if (r < 0)
return r;
if (volume_key_size == 0) {
if (volume_key_size > 0)
(void) measure_volume_key(cd, name, volume_key, volume_key_size); /* OK if fails */
else
log_debug("Not measuring volume key, none specified.");
return r;
}
(void) measure_volume_key(cd, name, volume_key, volume_key_size); /* OK if fails */
(void) measure_keyslot(cd, name, mechanism, keyslot); /* ditto */
return r;
}
static int measured_crypt_activate_by_passphrase(
struct crypt_device *cd,
const char *name,
const char *mechanism,
int keyslot,
const char *passphrase,
size_t passphrase_size,
@ -1136,17 +1229,21 @@ static int measured_crypt_activate_by_passphrase(
if (!vk)
return -ENOMEM;
r = crypt_volume_key_get(cd, keyslot, vk, &vks, passphrase, passphrase_size);
if (r < 0)
return r;
keyslot = crypt_volume_key_get(cd, keyslot, vk, &vks, passphrase, passphrase_size);
if (keyslot < 0)
return keyslot;
return measured_crypt_activate_by_volume_key(cd, name, vk, vks, flags);
return measured_crypt_activate_by_volume_key(cd, mechanism, name, keyslot, vk, vks, flags);
shortcut:
r = crypt_activate_by_passphrase(cd, name, keyslot, passphrase, passphrase_size, flags);
if (r == -EEXIST) /* volume is already active */
return log_external_activation(r, name);
return r;
keyslot = crypt_activate_by_passphrase(cd, name, keyslot, passphrase, passphrase_size, flags);
if (keyslot == -EEXIST) /* volume is already active */
return log_external_activation(keyslot, name);
if (keyslot < 0)
return keyslot;
(void) measure_keyslot(cd, name, mechanism, keyslot);
return keyslot;
}
static int attach_tcrypt(
@ -1226,7 +1323,14 @@ static int attach_tcrypt(
return log_error_errno(r, "Failed to load tcrypt superblock on device %s: %m", crypt_get_device_name(cd));
}
r = measured_crypt_activate_by_volume_key(cd, name, NULL, 0, flags);
r = measured_crypt_activate_by_volume_key(
cd,
name,
/* mechanism= */ NULL,
/* keyslot= */ -1,
/* volume_key= */ NULL,
/* volume_key_size= */ 0,
flags);
if (r < 0)
return log_error_errno(r, "Failed to activate tcrypt device %s: %m", crypt_get_device_name(cd));
@ -1351,6 +1455,8 @@ static bool use_token_plugins(void) {
* plugins, if measurement has been requested. */
if (arg_tpm2_measure_pcr != UINT_MAX)
return false;
if (arg_tpm2_measure_keyslot_nvpcr)
return false;
#endif
/* Disable tokens if we're in FIDO2 mode with manual parameters. */
@ -1586,7 +1692,14 @@ static int attach_luks_or_plain_or_bitlk_by_fido2(
}
if (pass_volume_key)
r = measured_crypt_activate_by_volume_key(cd, name, decrypted_key, decrypted_key_size, flags);
r = measured_crypt_activate_by_volume_key(
cd,
name,
"fido2",
/* keyslot= */ -1,
decrypted_key,
decrypted_key_size,
flags);
else {
_cleanup_(erase_and_freep) char *base64_encoded = NULL;
ssize_t base64_encoded_size;
@ -1597,7 +1710,14 @@ static int attach_luks_or_plain_or_bitlk_by_fido2(
if (base64_encoded_size < 0)
return log_oom();
r = measured_crypt_activate_by_passphrase(cd, name, keyslot, base64_encoded, base64_encoded_size, flags);
r = measured_crypt_activate_by_passphrase(
cd,
name,
"fido2",
keyslot,
base64_encoded,
base64_encoded_size,
flags);
}
if (r == -EPERM) {
log_error_errno(r, "Failed to activate with FIDO2 decrypted key. (Key incorrect?)");
@ -1743,7 +1863,14 @@ static int attach_luks_or_plain_or_bitlk_by_pkcs11(
assert(decrypted_key);
if (pass_volume_key)
r = measured_crypt_activate_by_volume_key(cd, name, decrypted_key, decrypted_key_size, flags);
r = measured_crypt_activate_by_volume_key(
cd,
name,
"pkcs11",
/* keyslot= */ -1,
decrypted_key,
decrypted_key_size,
flags);
else {
_cleanup_(erase_and_freep) char *base64_encoded = NULL;
ssize_t base64_encoded_size;
@ -1760,7 +1887,14 @@ static int attach_luks_or_plain_or_bitlk_by_pkcs11(
if (base64_encoded_size < 0)
return log_oom();
r = measured_crypt_activate_by_passphrase(cd, name, keyslot, base64_encoded, base64_encoded_size, flags);
r = measured_crypt_activate_by_passphrase(
cd,
name,
"pkcs11",
keyslot,
base64_encoded,
base64_encoded_size,
flags);
}
if (r == -EPERM) {
log_error_errno(r, "Failed to activate with PKCS#11 decrypted key. (Key incorrect?)");
@ -2048,7 +2182,14 @@ static int attach_luks_or_plain_or_bitlk_by_tpm2(
}
if (pass_volume_key)
r = measured_crypt_activate_by_volume_key(cd, name, decrypted_key.iov_base, decrypted_key.iov_len, flags);
r = measured_crypt_activate_by_volume_key(
cd,
name,
"tpm2",
/* keyslot= */ -1,
decrypted_key.iov_base,
decrypted_key.iov_len,
flags);
else {
_cleanup_(erase_and_freep) char *base64_encoded = NULL;
ssize_t base64_encoded_size;
@ -2059,7 +2200,14 @@ static int attach_luks_or_plain_or_bitlk_by_tpm2(
if (base64_encoded_size < 0)
return log_oom();
r = measured_crypt_activate_by_passphrase(cd, name, keyslot, base64_encoded, base64_encoded_size, flags);
r = measured_crypt_activate_by_passphrase(
cd,
name,
"tpm2",
keyslot,
base64_encoded,
base64_encoded_size,
flags);
}
if (r == -EPERM) {
log_error_errno(r, "Failed to activate with TPM2 decrypted key. (Key incorrect?)");
@ -2085,9 +2233,9 @@ static int attach_luks_or_plain_or_bitlk_by_key_data(
assert(key_data);
if (pass_volume_key)
r = measured_crypt_activate_by_volume_key(cd, name, key_data->iov_base, key_data->iov_len, flags);
r = measured_crypt_activate_by_volume_key(cd, name, /* mechanism= */ NULL, /* keyslot= */ -1, key_data->iov_base, key_data->iov_len, flags);
else
r = measured_crypt_activate_by_passphrase(cd, name, arg_key_slot, key_data->iov_base, key_data->iov_len, flags);
r = measured_crypt_activate_by_passphrase(cd, name, /* mechanism= */ NULL, arg_key_slot, key_data->iov_base, key_data->iov_len, flags);
if (r == -EPERM) {
log_error_errno(r, "Failed to activate. (Key incorrect?)");
return -EAGAIN; /* Log actual error, but return EAGAIN */
@ -2138,9 +2286,9 @@ static int attach_luks_or_plain_or_bitlk_by_key_file(
return log_error_errno(r, "Failed to read key file '%s': %m", key_file);
if (pass_volume_key)
r = measured_crypt_activate_by_volume_key(cd, name, kfdata, kfsize, flags);
r = measured_crypt_activate_by_volume_key(cd, name, /* mechanism= */ NULL, /* keyslot= */ -1, kfdata, kfsize, flags);
else
r = measured_crypt_activate_by_passphrase(cd, name, arg_key_slot, kfdata, kfsize, flags);
r = measured_crypt_activate_by_passphrase(cd, name, /* mechanism= */ NULL, arg_key_slot, kfdata, kfsize, flags);
if (r == -EPERM) {
log_error_errno(r, "Failed to activate with key file '%s'. (Key data incorrect?)", key_file);
return -EAGAIN; /* Log actual error, but return EAGAIN */
@ -2166,9 +2314,9 @@ static int attach_luks_or_plain_or_bitlk_by_passphrase(
r = -EINVAL;
STRV_FOREACH(p, passwords) {
if (pass_volume_key)
r = measured_crypt_activate_by_volume_key(cd, name, *p, arg_key_size, flags);
r = measured_crypt_activate_by_volume_key(cd, name, /* mechanism= */ NULL, /* keyslot= */ -1, *p, arg_key_size, flags);
else
r = measured_crypt_activate_by_passphrase(cd, name, arg_key_slot, *p, strlen(*p), flags);
r = measured_crypt_activate_by_passphrase(cd, name, /* mechanism= */ NULL, arg_key_slot, *p, strlen(*p), flags);
if (r >= 0)
break;
}

View File

@ -127,7 +127,7 @@ static int add_cryptsetup(
* assignment, under the assumption that people who are fine to use sd-stub with its PCR
* assignments are also OK with our PCR 15 use here. */
if (r > 0)
if (!strextend_with_separator(&options, ",", "tpm2-measure-pcr=yes"))
if (!strextend_with_separator(&options, ",", "tpm2-measure-pcr=yes,tpm2-measure-slot-nvpcr=cryptsetup"))
return log_oom();
if (r == 0)
log_debug("Will not measure volume key of volume '%s', not booted via systemd-stub with measurements enabled.", id);

View File

@ -810,7 +810,6 @@ static int context_ensure_layout(Context *c) {
}
static int context_set_up_staging_area(Context *c) {
static const char *template = "/tmp/kernel-install.staging.XXXXXX";
int r;
assert(c);
@ -818,12 +817,19 @@ static int context_set_up_staging_area(Context *c) {
if (c->staging_area)
return 0;
if (c->action == ACTION_INSPECT) {
const char *d;
r = var_tmp_dir(&d);
if (r < 0)
return log_error_errno(r, "Failed to determine temporary directory location: %m");
_cleanup_free_ char *template = path_join(d, "kernel-install.staging.XXXXXX");
if (!template)
return log_oom();
if (c->action == ACTION_INSPECT)
/* This is only used for display. The directory will not be created. */
c->staging_area = strdup(template);
if (!c->staging_area)
return log_oom();
} else {
c->staging_area = TAKE_PTR(template);
else {
r = mkdtemp_malloc(template, &c->staging_area);
if (r < 0)
return log_error_errno(r, "Failed to create staging area: %m");

View File

@ -318,7 +318,7 @@ diff -u <(echo "$output") - >&2 <<EOF
"KERNEL_INSTALL_LAYOUT=other",
"KERNEL_INSTALL_INITRD_GENERATOR=none",
"KERNEL_INSTALL_UKI_GENERATOR=",
"KERNEL_INSTALL_STAGING_AREA=/tmp/kernel-install.staging.XXXXXX"
"KERNEL_INSTALL_STAGING_AREA=/var/tmp/kernel-install.staging.XXXXXX"
]
}
EOF

View File

@ -27,12 +27,16 @@ static char *arg_tpm2_device = NULL;
static char **arg_banks = NULL;
static char *arg_file_system = NULL;
static bool arg_machine_id = false;
static bool arg_product_id = false;
static unsigned arg_pcr_index = UINT_MAX;
static char *arg_nvpcr_name = NULL;
static bool arg_varlink = false;
static bool arg_early = false;
STATIC_DESTRUCTOR_REGISTER(arg_banks, strv_freep);
STATIC_DESTRUCTOR_REGISTER(arg_tpm2_device, freep);
STATIC_DESTRUCTOR_REGISTER(arg_file_system, freep);
STATIC_DESTRUCTOR_REGISTER(arg_nvpcr_name, freep);
#define EXTENSION_STRING_SAFE_LIMIT 1024
@ -47,16 +51,20 @@ static int help(int argc, char *argv[], void *userdata) {
printf("%1$s [OPTIONS...] WORD\n"
"%1$s [OPTIONS...] --file-system=PATH\n"
"%1$s [OPTIONS...] --machine-id\n"
"%1$s [OPTIONS...] --product-id\n"
"\n%5$sExtend a TPM2 PCR with boot phase, machine ID, or file system ID.%6$s\n"
"\n%3$sOptions:%4$s\n"
" -h --help Show this help\n"
" --version Print version\n"
" --bank=DIGEST Select TPM PCR bank (SHA1, SHA256)\n"
" --pcr=INDEX Select TPM PCR index (0…23)\n"
" --nvpcr=NAME Select TPM PCR mode nvindex name\n"
" --tpm2-device=PATH Use specified TPM2 device\n"
" --graceful Exit gracefully if no TPM2 device is found\n"
" --file-system=PATH Measure UUID/labels of file system into PCR 15\n"
" --machine-id Measure machine ID into PCR 15\n"
" --product-id Measure SMBIOS product ID into NvPCR 'hardware'\n"
" --early Run in early boot mode, without access to /var/\n"
"\nSee the %2$s for details.\n",
program_invocation_short_name,
link,
@ -73,10 +81,13 @@ static int parse_argv(int argc, char *argv[]) {
ARG_VERSION = 0x100,
ARG_BANK,
ARG_PCR,
ARG_NVPCR,
ARG_TPM2_DEVICE,
ARG_GRACEFUL,
ARG_FILE_SYSTEM,
ARG_MACHINE_ID,
ARG_PRODUCT_ID,
ARG_EARLY,
};
static const struct option options[] = {
@ -84,10 +95,13 @@ static int parse_argv(int argc, char *argv[]) {
{ "version", no_argument, NULL, ARG_VERSION },
{ "bank", required_argument, NULL, ARG_BANK },
{ "pcr", required_argument, NULL, ARG_PCR },
{ "nvpcr", required_argument, NULL, ARG_NVPCR },
{ "tpm2-device", required_argument, NULL, ARG_TPM2_DEVICE },
{ "graceful", no_argument, NULL, ARG_GRACEFUL },
{ "file-system", required_argument, NULL, ARG_FILE_SYSTEM },
{ "machine-id", no_argument, NULL, ARG_MACHINE_ID },
{ "product-id", no_argument, NULL, ARG_PRODUCT_ID },
{ "early", no_argument, NULL, ARG_EARLY },
{}
};
@ -127,6 +141,15 @@ static int parse_argv(int argc, char *argv[]) {
arg_pcr_index = r;
break;
case ARG_NVPCR:
if (!tpm2_nvpcr_name_is_valid(optarg))
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "NvPCR name is not valid: %s", optarg);
r = free_and_strdup_warn(&arg_nvpcr_name, optarg);
if (r < 0)
return r;
break;
case ARG_TPM2_DEVICE: {
_cleanup_free_ char *device = NULL;
@ -158,6 +181,14 @@ static int parse_argv(int argc, char *argv[]) {
arg_machine_id = true;
break;
case ARG_PRODUCT_ID:
arg_product_id = true;
break;
case ARG_EARLY:
arg_early = true;
break;
case '?':
return -EINVAL;
@ -165,18 +196,27 @@ static int parse_argv(int argc, char *argv[]) {
assert_not_reached();
}
if (arg_file_system && arg_machine_id)
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "--file-system= and --machine-id may not be combined.");
if (!!arg_file_system + arg_machine_id + arg_product_id > 1)
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "--file-system=, --machine-id, --product-id may not be combined.");
if (arg_pcr_index != UINT_MAX && arg_nvpcr_name)
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "--pcr= and --nvpcr= may not be combined.");
r = sd_varlink_invocation(SD_VARLINK_ALLOW_ACCEPT);
if (r < 0)
return log_error_errno(r, "Failed to check if invoked in Varlink mode: %m");
if (r > 0)
arg_varlink = true;
else if (arg_pcr_index == UINT_MAX)
arg_pcr_index = (arg_file_system || arg_machine_id) ?
TPM2_PCR_SYSTEM_IDENTITY : /* → PCR 15 */
TPM2_PCR_KERNEL_BOOT; /* → PCR 11 */
else if (arg_pcr_index == UINT_MAX && !arg_nvpcr_name) {
arg_pcr_index =
(arg_file_system || arg_machine_id) ? TPM2_PCR_SYSTEM_IDENTITY : /* → PCR 15 */
!arg_product_id ? TPM2_PCR_KERNEL_BOOT : /* → PCR 11 */
UINT_MAX;
r = free_and_strdup_warn(&arg_nvpcr_name, arg_product_id ? "hardware" : NULL);
if (r < 0)
return r;
}
return 1;
}
@ -198,7 +238,34 @@ static int determine_banks(Tpm2Context *c, unsigned target_pcr_nr) {
return 0;
}
static int extend_now(unsigned pcr, const void *data, size_t size, Tpm2UserspaceEventType event) {
static int escape_and_truncate_data(const void *data, size_t size, char **ret) {
_cleanup_free_ char *safe = NULL;
assert(data || size == 0);
if (size > EXTENSION_STRING_SAFE_LIMIT) {
safe = cescape_length(data, EXTENSION_STRING_SAFE_LIMIT);
if (!safe)
return -ENOMEM;
if (!strextend(&safe, "..."))
return -ENOMEM;
} else {
safe = cescape_length(data, size);
if (!safe)
return -ENOMEM;
}
*ret = TAKE_PTR(safe);
return 0;
}
static int extend_pcr_now(
unsigned pcr,
const void *data,
size_t size,
Tpm2UserspaceEventType event) {
_cleanup_(tpm2_context_unrefp) Tpm2Context *c = NULL;
int r;
@ -218,22 +285,12 @@ static int extend_now(unsigned pcr, const void *data, size_t size, Tpm2Userspace
return log_oom();
_cleanup_free_ char *safe = NULL;
if (size > EXTENSION_STRING_SAFE_LIMIT) {
safe = cescape_length(data, EXTENSION_STRING_SAFE_LIMIT);
if (!safe)
return log_oom();
if (!strextend(&safe, "..."))
return log_oom();
} else {
safe = cescape_length(data, size);
if (!safe)
return log_oom();
}
if (escape_and_truncate_data(data, size, &safe) < 0)
return log_oom();
log_debug("Measuring '%s' into PCR index %u, banks %s.", safe, pcr, joined_banks);
r = tpm2_extend_bytes(c, arg_banks, pcr, data, size, /* secret= */ NULL, /* secret_size= */ 0, event, safe);
r = tpm2_pcr_extend_bytes(c, arg_banks, pcr, &IOVEC_MAKE(data, size), /* secret= */ NULL, event, safe);
if (r < 0)
return log_error_errno(r, "Could not extend PCR: %m");
@ -247,8 +304,55 @@ static int extend_now(unsigned pcr, const void *data, size_t size, Tpm2Userspace
return 0;
}
static int extend_nvpcr_now(
const char *name,
const void *data,
size_t size,
Tpm2UserspaceEventType event) {
_cleanup_(tpm2_context_unrefp) Tpm2Context *c = NULL;
int r;
r = tpm2_context_new_or_warn(arg_tpm2_device, &c);
if (r < 0)
return r;
_cleanup_free_ char *safe = NULL;
if (escape_and_truncate_data(data, size, &safe) < 0)
return log_oom();
log_debug("Measuring '%s' into NvPCR index '%s'.", safe, name);
r = tpm2_nvpcr_extend_bytes(c, /* session= */ NULL, name, &IOVEC_MAKE(data, size), /* secret= */ NULL, event, safe);
if (r == -ENETDOWN) {
/* NvPCR is not initialized yet. Let's do this now. */
_cleanup_(iovec_done_erase) struct iovec anchor_secret = {};
r = tpm2_nvpcr_acquire_anchor_secret(&anchor_secret, /* sync_secondary= */ !arg_early);
if (r < 0)
return r;
r = tpm2_nvpcr_initialize(c, /* session= */ NULL, name, &anchor_secret);
if (r < 0)
return log_error_errno(r, "Failed to extend NvPCR index '%s' with anchor secret: %m", name);
r = tpm2_nvpcr_extend_bytes(c, /* session= */ NULL, name, &IOVEC_MAKE(data, size), /* secret= */ NULL, event, safe);
}
if (r < 0)
return log_error_errno(r, "Could not extend NvPCR: %m");
log_struct(LOG_INFO,
"MESSAGE_ID=" SD_MESSAGE_TPM_NVPCR_EXTEND_STR,
LOG_MESSAGE("Extended NvPCR index '%s' with '%s'.", name, safe),
"MEASURING=%s", safe,
"NVPCR=%u", name);
return 0;
}
typedef struct MethodExtendParameters {
unsigned pcr;
const char *nvpcr;
const char *text;
struct iovec data;
} MethodExtendParameters;
@ -262,9 +366,10 @@ static void method_extend_parameters_done(MethodExtendParameters *p) {
static int vl_method_extend(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata) {
static const sd_json_dispatch_field dispatch_table[] = {
{ "pcr", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint, offsetof(MethodExtendParameters, pcr), SD_JSON_MANDATORY },
{ "text", SD_JSON_VARIANT_STRING, sd_json_dispatch_const_string, offsetof(MethodExtendParameters, text), 0 },
{ "data", SD_JSON_VARIANT_STRING, json_dispatch_unbase64_iovec, offsetof(MethodExtendParameters, data), 0 },
{ "pcr", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint, offsetof(MethodExtendParameters, pcr), 0 },
{ "nvpcr", SD_JSON_VARIANT_STRING, sd_json_dispatch_const_string, offsetof(MethodExtendParameters, nvpcr), 0 },
{ "text", SD_JSON_VARIANT_STRING, sd_json_dispatch_const_string, offsetof(MethodExtendParameters, text), 0 },
{ "data", SD_JSON_VARIANT_STRING, json_dispatch_unbase64_iovec, offsetof(MethodExtendParameters, data), 0 },
{}
};
_cleanup_(method_extend_parameters_done) MethodExtendParameters p = {
@ -278,19 +383,38 @@ static int vl_method_extend(sd_varlink *link, sd_json_variant *parameters, sd_va
if (r != 0)
return r;
if (!TPM2_PCR_INDEX_VALID(p.pcr))
if (p.nvpcr) {
/* Specifying both nvpcr name and pcr doesn't make sense */
if (p.pcr != UINT_MAX)
return sd_varlink_error_invalid_parameter_name(link, "nvpcr");
if (!tpm2_nvpcr_name_is_valid(p.nvpcr))
return sd_varlink_error_invalid_parameter_name(link, "nvpcr");
} else if (!TPM2_PCR_INDEX_VALID(p.pcr))
return sd_varlink_error_invalid_parameter_name(link, "pcr");
struct iovec *extend_iovec, text_iovec;
if (p.text) {
/* Specifying both the text string and the binary data is not allowed */
if (p.data.iov_base)
return sd_varlink_error_invalid_parameter_name(link, "data");
r = extend_now(p.pcr, p.text, strlen(p.text), _TPM2_USERSPACE_EVENT_TYPE_INVALID);
text_iovec = IOVEC_MAKE_STRING(p.text);
extend_iovec = &text_iovec;
} else if (p.data.iov_base)
r = extend_now(p.pcr, p.data.iov_base, p.data.iov_len, _TPM2_USERSPACE_EVENT_TYPE_INVALID);
extend_iovec = &p.data;
else
return sd_varlink_error_invalid_parameter_name(link, "text");
if (p.nvpcr) {
r = extend_nvpcr_now(p.nvpcr, extend_iovec->iov_base, extend_iovec->iov_len, _TPM2_USERSPACE_EVENT_TYPE_INVALID);
if (r == -ENOENT)
return sd_varlink_error(link, "io.systemd.PCRExtend.NoSuchNvPCR", NULL);
} else
r = extend_pcr_now(p.pcr, extend_iovec->iov_base, extend_iovec->iov_len, _TPM2_USERSPACE_EVENT_TYPE_INVALID);
if (r < 0)
return r;
@ -355,6 +479,16 @@ static int run(int argc, char *argv[]) {
event = TPM2_EVENT_MACHINE_ID;
} else if (arg_product_id) {
if (optind != argc)
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Expected no argument.");
r = pcrextend_product_id_word(&word);
if (r < 0)
return r;
event = TPM2_EVENT_PRODUCT_ID;
} else {
if (optind+1 != argc)
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Expected a single argument.");
@ -386,9 +520,12 @@ static int run(int argc, char *argv[]) {
return EXIT_SUCCESS;
}
r = extend_now(arg_pcr_index, word, strlen(word), event);
if (arg_nvpcr_name)
r = extend_nvpcr_now(arg_nvpcr_name, word, strlen(word), event);
else
r = extend_pcr_now(arg_pcr_index, word, strlen(word), event);
if (r < 0)
return log_error_errno(r, "Failed to create TPM2 context: %m");
return r;
return EXIT_SUCCESS;
}

View File

@ -155,7 +155,9 @@ typedef enum EventPayloadValid {
struct EventLogRecord {
EventLog *event_log;
uint32_t pcr;
uint32_t pcr; /* Either 'pcr' or 'nv_index' are set, but not both. Other one is UINT32_MAX. */
uint32_t nv_index;
const char *source;
char *description;
@ -184,6 +186,8 @@ struct EventLogRecord {
#define EVENT_LOG_RECORD_IS_FIRMWARE(record) ((record)->firmware_event_type != UINT32_MAX)
#define EVENT_LOG_RECORD_IS_USERSPACE(record) ((record)->userspace_event_type >= 0)
#define EVENT_LOG_RECORD_IS_PCR(record) ((record)->pcr != UINT32_MAX)
#define EVENT_LOG_RECORD_IS_NV_INDEX(record) ((record)->nv_index != UINT32_MAX)
struct EventLogRegisterBank {
TPM2B_DIGEST observed;
@ -339,6 +343,8 @@ static EventLogRecord* event_log_record_new(EventLog *el) {
*record = (EventLogRecord) {
.event_log = el,
.pcr = UINT32_MAX,
.nv_index = UINT32_MAX,
.firmware_event_type = UINT32_MAX,
.userspace_event_type = _TPM2_USERSPACE_EVENT_TYPE_INVALID,
.event_payload_valid = _EVENT_PAYLOAD_VALID_INVALID,
@ -1003,9 +1009,6 @@ static int event_log_load_firmware(EventLog *el) {
}
static int event_log_record_parse_json(EventLogRecord *record, sd_json_variant *j) {
const char *rectype = NULL;
sd_json_variant *x, *k;
uint64_t u;
int r;
assert(record);
@ -1014,23 +1017,44 @@ static int event_log_record_parse_json(EventLogRecord *record, sd_json_variant *
if (!sd_json_variant_is_object(j))
return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), "record object is not an object.");
x = sd_json_variant_by_key(j, "pcr");
if (!x)
return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), "'pcr' field missing from TPM measurement log file entry.");
if (!sd_json_variant_is_unsigned(x))
return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), "'pcr' field is not an integer.");
sd_json_variant *jp = sd_json_variant_by_key(j, "pcr");
sd_json_variant *jn = sd_json_variant_by_key(j, "nv_index");
if (jp) {
uint64_t u;
if (jn)
return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), "Both 'pcr' and 'nv_index' field found in TPM measurement log file entry.");
if (!sd_json_variant_is_unsigned(jp))
return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), "'pcr' field is not an integer.");
u = sd_json_variant_unsigned(x);
if (u >= TPM2_PCRS_MAX)
return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), "'pcr' field is out of range.");
record->pcr = sd_json_variant_unsigned(x);
u = sd_json_variant_unsigned(jp);
if (u >= TPM2_PCRS_MAX)
return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), "'pcr' field is out of range.");
x = sd_json_variant_by_key(j, "digests");
record->pcr = u;
record->nv_index = UINT32_MAX;
} else {
uint64_t u;
if (!jn)
return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), "Neither 'pcr' nor 'nv_index' field found in TPM measurement log file entry.");
if (!sd_json_variant_is_unsigned(jn))
return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), "'nv_index' field is not an integer.");
u = sd_json_variant_unsigned(jn);
if (u >= UINT32_MAX)
return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), "'nv_index' field is out of range.");
record->nv_index = u;
record->pcr = UINT32_MAX;
}
sd_json_variant *x = sd_json_variant_by_key(j, "digests");
if (!x)
return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), "'digests' field missing from TPM measurement log file entry.");
if (!sd_json_variant_is_array(x))
return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), "'digests' field is not an array.");
sd_json_variant *k;
JSON_VARIANT_ARRAY_FOREACH(k, x) {
_cleanup_free_ void *hash = NULL;
size_t hash_size;
@ -1067,6 +1091,7 @@ static int event_log_record_parse_json(EventLogRecord *record, sd_json_variant *
return log_error_errno(r, "Failed to add bank to event log record: %m");
}
const char *rectype = NULL;
x = sd_json_variant_by_key(j, "content_type");
if (!x)
log_debug("'content_type' missing from TPM measurement log file entry, ignoring.");
@ -1324,6 +1349,10 @@ static int event_log_calculate_pcrs(EventLog *el) {
}
FOREACH_ARRAY(rr, el->records, el->n_records) {
if (!EVENT_LOG_RECORD_IS_PCR(*rr))
continue;
EventLogRegister *reg = el->registers + (*rr)->pcr;
for (size_t i = 0; i < el->n_algorithms; i++) {
@ -1626,6 +1655,9 @@ static int event_log_record_equal(const EventLogRecord *a, const EventLogRecord
if (a->pcr != b->pcr)
return false;
if (a->nv_index != b->nv_index)
return false;
x = event_log_record_find_bank(a, a->event_log->primary_algorithm);
y = event_log_record_find_bank(b, b->event_log->primary_algorithm);
if (!x || !y)
@ -1848,6 +1880,9 @@ static int event_log_validate_fully_recognized(EventLog *el) {
FOREACH_ARRAY(rr, el->records, el->n_records) {
EventLogRecord *rec = *rr;
if (!EVENT_LOG_RECORD_IS_PCR(rec))
continue;
if (rec->pcr != pcr)
continue;
@ -1916,8 +1951,13 @@ static uint32_t event_log_component_variant_pcrs(EventLogComponentVariant *i) {
/* returns mask of PCRs touched by this variant */
FOREACH_ARRAY(rr, i->records, i->n_records)
FOREACH_ARRAY(rr, i->records, i->n_records) {
if (!EVENT_LOG_RECORD_IS_PCR(*rr))
continue;
mask |= UINT32_C(1) << (*rr)->pcr;
}
return mask;
}
@ -2123,11 +2163,19 @@ static int show_log_table(EventLog *el, sd_json_variant **ret_variant) {
FOREACH_ARRAY(rr, el->records, el->n_records) {
EventLogRecord *record = *rr;
r = table_add_many(table,
TABLE_UINT32, record->pcr,
TABLE_STRING, glyph(GLYPH_FULL_BLOCK),
TABLE_SET_COLOR, color_for_pcr(el, record->pcr),
TABLE_STRING, tpm2_pcr_index_to_string(record->pcr));
if (EVENT_LOG_RECORD_IS_PCR(record))
r = table_add_many(table,
TABLE_UINT32, record->pcr,
TABLE_STRING, glyph(GLYPH_FULL_BLOCK),
TABLE_SET_COLOR, color_for_pcr(el, record->pcr),
TABLE_STRING, tpm2_pcr_index_to_string(record->pcr));
else if EVENT_LOG_RECORD_IS_NV_INDEX(record)
r = table_add_many(table,
TABLE_UINT32_HEX_0x, record->nv_index,
TABLE_EMPTY,
TABLE_EMPTY);
else
assert_not_reached();
if (r < 0)
return table_log_add_error(r);
@ -2538,7 +2586,8 @@ static int event_log_record_to_cel(EventLogRecord *record, uint64_t *recnum, sd_
r = sd_json_buildo(
ret,
SD_JSON_BUILD_PAIR_UNSIGNED("pcr", record->pcr),
SD_JSON_BUILD_PAIR_CONDITION(EVENT_LOG_RECORD_IS_PCR(record), "pcr", SD_JSON_BUILD_UNSIGNED(record->pcr)),
SD_JSON_BUILD_PAIR_CONDITION(EVENT_LOG_RECORD_IS_NV_INDEX(record), "nv_index", SD_JSON_BUILD_UNSIGNED(record->nv_index)),
SD_JSON_BUILD_PAIR_UNSIGNED("recnum", ++(*recnum)),
SD_JSON_BUILD_PAIR_VARIANT("digests", ja),
SD_JSON_BUILD_PAIR_CONDITION(!!ct, "content_type", SD_JSON_BUILD_STRING(ct)),
@ -4004,6 +4053,9 @@ static int event_log_component_variant_calculate(
FOREACH_ARRAY(rr, variant->records, variant->n_records) {
EventLogRecord *rec = *rr;
if (!EVENT_LOG_RECORD_IS_PCR(rec))
continue;
if (rec->pcr != pcr)
continue;
@ -4900,7 +4952,7 @@ static int undefine_policy_nv_index(
if (r < 0)
return r;
r = tpm2_undefine_policy_nv_index(
r = tpm2_undefine_nv_index(
tc,
encryption_session,
nv_index,

View File

@ -41,6 +41,7 @@
#include "stat-util.h"
#include "string-util.h"
#include "tmpfile-util.h"
#include "tpm2-pcr.h"
#include "tpm2-util.h"
#include "user-util.h"
@ -125,6 +126,30 @@ int open_credentials_dir(void) {
return RET_NERRNO(open(d, O_CLOEXEC|O_DIRECTORY));
}
int get_system_credentials_dir(const char **ret) {
int r;
/* Note that for system credentials the environment variable we honour is just for debugging purpose
* (unlike for the per-service credential path env var where it's key part of the protocol). */
r = get_credentials_dir_internal("SYSTEMD_SYSTEM_CREDENTIALS_DIRECTORY", ret);
if (r >= 0 || r != -ENXIO)
return r;
*ret = SYSTEM_CREDENTIALS_DIRECTORY;
return 0;
}
int get_encrypted_system_credentials_dir(const char **ret) {
int r;
r = get_credentials_dir_internal("SYSTEMD_ENCRYPTED_SYSTEM_CREDENTIALS_DIRECTORY", ret);
if (r >= 0 || r != -ENXIO)
return r;
*ret = ENCRYPTED_SYSTEM_CREDENTIALS_DIRECTORY;
return 0;
}
int read_credential(const char *name, void **ret, size_t *ret_size) {
_cleanup_free_ char *fn = NULL;
const char *d;
@ -815,19 +840,7 @@ int encrypt_credential_and_warn(
/* Only one of these two flags may be set at the same time */
assert(!FLAGS_SET(flags, CREDENTIAL_ALLOW_NULL) || !FLAGS_SET(flags, CREDENTIAL_REFUSE_NULL));
if (!sd_id128_in_set(with_key,
_CRED_AUTO,
_CRED_AUTO_INITRD,
_CRED_AUTO_SCOPED,
CRED_AES256_GCM_BY_HOST,
CRED_AES256_GCM_BY_HOST_SCOPED,
CRED_AES256_GCM_BY_TPM2_HMAC,
CRED_AES256_GCM_BY_TPM2_HMAC_WITH_PK,
CRED_AES256_GCM_BY_HOST_AND_TPM2_HMAC,
CRED_AES256_GCM_BY_HOST_AND_TPM2_HMAC_SCOPED,
CRED_AES256_GCM_BY_HOST_AND_TPM2_HMAC_WITH_PK,
CRED_AES256_GCM_BY_HOST_AND_TPM2_HMAC_WITH_PK_SCOPED,
CRED_AES256_GCM_BY_NULL))
if (!CRED_KEY_IS_VALID(with_key) && !CRED_KEY_IS_AUTO(with_key))
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid key type: " SD_ID128_FORMAT_STR, SD_ID128_FORMAT_VAL(with_key));
if (name && !credential_name_valid(name))
@ -847,41 +860,34 @@ int encrypt_credential_and_warn(
log_debug("Including not-after timestamp '%s' in encrypted credential.", format_timestamp(buf, sizeof(buf), not_after));
}
if (sd_id128_in_set(with_key,
_CRED_AUTO_SCOPED,
CRED_AES256_GCM_BY_HOST_SCOPED,
CRED_AES256_GCM_BY_HOST_AND_TPM2_HMAC_SCOPED,
CRED_AES256_GCM_BY_HOST_AND_TPM2_HMAC_WITH_PK_SCOPED)) {
if (CRED_KEY_IS_SCOPED(with_key)) {
if (!uid_is_valid(uid))
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
"Scoped credential key type "SD_ID128_FORMAT_STR" selected, but no UID specified.", SD_ID128_FORMAT_VAL(with_key));
} else
uid = UID_INVALID;
if (sd_id128_in_set(with_key,
_CRED_AUTO,
_CRED_AUTO_SCOPED,
CRED_AES256_GCM_BY_HOST,
CRED_AES256_GCM_BY_HOST_SCOPED,
CRED_AES256_GCM_BY_HOST_AND_TPM2_HMAC,
CRED_AES256_GCM_BY_HOST_AND_TPM2_HMAC_SCOPED,
CRED_AES256_GCM_BY_HOST_AND_TPM2_HMAC_WITH_PK,
CRED_AES256_GCM_BY_HOST_AND_TPM2_HMAC_WITH_PK_SCOPED)) {
if (CRED_KEY_WANTS_HOST(with_key) || CRED_KEY_REQUIRES_HOST(with_key)) {
r = get_credential_host_secret(
CREDENTIAL_SECRET_GENERATE|
CREDENTIAL_SECRET_WARN_NOT_ENCRYPTED|
(sd_id128_in_set(with_key, _CRED_AUTO, _CRED_AUTO_SCOPED) ? CREDENTIAL_SECRET_FAIL_ON_TEMPORARY_FS : 0),
(CRED_KEY_WANTS_HOST(with_key) ? CREDENTIAL_SECRET_FAIL_ON_TEMPORARY_FS : 0),
&host_key);
if (r == -ENOMEDIUM && sd_id128_in_set(with_key, _CRED_AUTO, _CRED_AUTO_SCOPED))
if (r == -ENOMEDIUM && CRED_KEY_WANTS_HOST(with_key))
log_debug_errno(r, "Credential host secret location on temporary file system, not using.");
else if (r < 0)
return log_error_errno(r, "Failed to determine local credential host secret: %m");
}
if (tpm2_hash_pcr_mask == UINT32_MAX)
tpm2_hash_pcr_mask = 0;
if (tpm2_pubkey_pcr_mask == UINT32_MAX)
tpm2_pubkey_pcr_mask = UINT32_C(1) << TPM2_PCR_KERNEL_BOOT;
#if HAVE_TPM2
bool try_tpm2;
if (sd_id128_in_set(with_key, _CRED_AUTO, _CRED_AUTO_INITRD, _CRED_AUTO_SCOPED)) {
if (CRED_KEY_WANTS_TPM2(with_key)) {
/* If automatic mode is selected lets see if a TPM2 it is present. If we are running in a
* container tpm2_support will detect this, and will return a different flag combination of
* TPM2_SUPPORT_FULL, effectively skipping the use of TPM2 when inside one. */
@ -890,28 +896,16 @@ int encrypt_credential_and_warn(
if (!try_tpm2)
log_debug("System lacks TPM2 support or running in a container, not attempting to use TPM2.");
} else
try_tpm2 = sd_id128_in_set(with_key,
CRED_AES256_GCM_BY_TPM2_HMAC,
CRED_AES256_GCM_BY_TPM2_HMAC_WITH_PK,
CRED_AES256_GCM_BY_HOST_AND_TPM2_HMAC,
CRED_AES256_GCM_BY_HOST_AND_TPM2_HMAC_SCOPED,
CRED_AES256_GCM_BY_HOST_AND_TPM2_HMAC_WITH_PK,
CRED_AES256_GCM_BY_HOST_AND_TPM2_HMAC_WITH_PK_SCOPED);
try_tpm2 = CRED_KEY_REQUIRES_TPM2(with_key);
if (try_tpm2) {
if (sd_id128_in_set(with_key,
_CRED_AUTO,
_CRED_AUTO_INITRD,
_CRED_AUTO_SCOPED,
CRED_AES256_GCM_BY_TPM2_HMAC_WITH_PK,
CRED_AES256_GCM_BY_HOST_AND_TPM2_HMAC_WITH_PK,
CRED_AES256_GCM_BY_HOST_AND_TPM2_HMAC_WITH_PK_SCOPED)) {
if (CRED_KEY_WANTS_TPM2_PK(with_key) || CRED_KEY_REQUIRES_TPM2_PK(with_key)) {
/* Load public key for PCR policies, if one is specified, or explicitly requested */
r = tpm2_load_pcr_public_key(tpm2_pubkey_path, &pubkey.iov_base, &pubkey.iov_len);
if (r < 0) {
if (tpm2_pubkey_path || r != -ENOENT || !sd_id128_in_set(with_key, _CRED_AUTO, _CRED_AUTO_INITRD, _CRED_AUTO_SCOPED))
if (r != -ENOENT || tpm2_pubkey_path || CRED_KEY_REQUIRES_TPM2_PK(with_key))
return log_error_errno(r, "Failed to read TPM PCR public key: %m");
log_debug_errno(r, "Failed to read TPM2 PCR public key, proceeding without: %m");
@ -991,7 +985,7 @@ int encrypt_credential_and_warn(
}
#endif
if (sd_id128_in_set(with_key, _CRED_AUTO, _CRED_AUTO_INITRD, _CRED_AUTO_SCOPED)) {
if (CRED_KEY_IS_AUTO(with_key)) {
/* Let's settle the key type in auto mode now. */
if (iovec_is_set(&host_key) && iovec_is_set(&tpm2_key))
@ -1200,7 +1194,6 @@ int decrypt_credential_and_warn(
struct encrypted_credential_header *h;
struct metadata_credential_header *m;
uint8_t md[SHA256_DIGEST_LENGTH];
bool with_tpm2, with_tpm2_pk, with_host_key, with_null, with_scope;
const EVP_CIPHER *cc;
size_t p, hs;
int r, added;
@ -1230,16 +1223,10 @@ int decrypt_credential_and_warn(
if (input->iov_len < sizeof(h->id))
return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), "Encrypted file too short.");
with_host_key = sd_id128_in_set(h->id, CRED_AES256_GCM_BY_HOST, CRED_AES256_GCM_BY_HOST_SCOPED, CRED_AES256_GCM_BY_HOST_AND_TPM2_HMAC, CRED_AES256_GCM_BY_HOST_AND_TPM2_HMAC_SCOPED, CRED_AES256_GCM_BY_HOST_AND_TPM2_HMAC_WITH_PK, CRED_AES256_GCM_BY_HOST_AND_TPM2_HMAC_WITH_PK_SCOPED);
with_tpm2_pk = sd_id128_in_set(h->id, CRED_AES256_GCM_BY_TPM2_HMAC_WITH_PK, CRED_AES256_GCM_BY_HOST_AND_TPM2_HMAC_WITH_PK, CRED_AES256_GCM_BY_HOST_AND_TPM2_HMAC_WITH_PK_SCOPED);
with_tpm2 = sd_id128_in_set(h->id, CRED_AES256_GCM_BY_TPM2_HMAC, CRED_AES256_GCM_BY_HOST_AND_TPM2_HMAC, CRED_AES256_GCM_BY_HOST_AND_TPM2_HMAC_SCOPED) || with_tpm2_pk;
with_null = sd_id128_equal(h->id, CRED_AES256_GCM_BY_NULL);
with_scope = sd_id128_in_set(h->id, CRED_AES256_GCM_BY_HOST_SCOPED, CRED_AES256_GCM_BY_HOST_AND_TPM2_HMAC_SCOPED, CRED_AES256_GCM_BY_HOST_AND_TPM2_HMAC_WITH_PK_SCOPED);
if (!with_host_key && !with_tpm2 && !with_null)
if (!CRED_KEY_IS_VALID(h->id))
return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "Unknown encryption format, or corrupted data.");
if (with_tpm2_pk) {
if (CRED_KEY_REQUIRES_TPM2_PK(h->id)) {
r = tpm2_load_pcr_signature(tpm2_signature_path, &signature_json);
if (r == -ENOENT)
return log_error_errno(SYNTHETIC_ERRNO(EHOSTDOWN), "Couldn't find PCR signature file: %m");
@ -1247,7 +1234,7 @@ int decrypt_credential_and_warn(
return log_error_errno(r, "Failed to load PCR signature: %m");
}
if (with_null) {
if (sd_id128_equal(h->id, CRED_AES256_GCM_BY_NULL)) {
if (FLAGS_SET(flags, CREDENTIAL_REFUSE_NULL))
return log_error_errno(SYNTHETIC_ERRNO(EHWPOISON),
"Credential uses null key, but that's not allowed, refusing.");
@ -1273,7 +1260,7 @@ int decrypt_credential_and_warn(
}
}
if (with_scope) {
if (CRED_KEY_IS_SCOPED(h->id)) {
if (!uid_is_valid(uid))
return log_error_errno(SYNTHETIC_ERRNO(EMEDIUMTYPE), "Encrypted file is scoped to a user, but no user selected.");
} else {
@ -1302,16 +1289,16 @@ int decrypt_credential_and_warn(
* lower limit only) */
if (input->iov_len <
ALIGN8(offsetof(struct encrypted_credential_header, iv) + le32toh(h->iv_size)) +
ALIGN8(with_tpm2 ? offsetof(struct tpm2_credential_header, policy_hash_and_blob) : 0) +
ALIGN8(with_tpm2_pk ? offsetof(struct tpm2_public_key_credential_header, data) : 0) +
ALIGN8(with_scope ? sizeof(struct scoped_credential_header) : 0) +
ALIGN8(CRED_KEY_REQUIRES_TPM2(h->id) ? offsetof(struct tpm2_credential_header, policy_hash_and_blob) : 0) +
ALIGN8(CRED_KEY_REQUIRES_TPM2_PK(h->id) ? offsetof(struct tpm2_public_key_credential_header, data) : 0) +
ALIGN8(CRED_KEY_IS_SCOPED(h->id) ? sizeof(struct scoped_credential_header) : 0) +
ALIGN8(offsetof(struct metadata_credential_header, name)) +
le32toh(h->tag_size))
return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), "Encrypted file too short.");
p = ALIGN8(offsetof(struct encrypted_credential_header, iv) + le32toh(h->iv_size));
if (with_tpm2) {
if (CRED_KEY_REQUIRES_TPM2(h->id)) {
#if HAVE_TPM2
struct tpm2_credential_header* t = (struct tpm2_credential_header*) ((uint8_t*) input->iov_base + p);
struct tpm2_public_key_credential_header *z = NULL;
@ -1332,8 +1319,8 @@ int decrypt_credential_and_warn(
if (input->iov_len <
p +
ALIGN8(offsetof(struct tpm2_credential_header, policy_hash_and_blob) + le32toh(t->blob_size) + le32toh(t->policy_hash_size)) +
ALIGN8(with_tpm2_pk ? offsetof(struct tpm2_public_key_credential_header, data) : 0) +
ALIGN8(with_scope ? sizeof(struct scoped_credential_header) : 0) +
ALIGN8(CRED_KEY_REQUIRES_TPM2_PK(h->id) ? offsetof(struct tpm2_public_key_credential_header, data) : 0) +
ALIGN8(CRED_KEY_IS_SCOPED(h->id) ? sizeof(struct scoped_credential_header) : 0) +
ALIGN8(offsetof(struct metadata_credential_header, name)) +
le32toh(h->tag_size))
return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), "Encrypted file too short.");
@ -1342,7 +1329,7 @@ int decrypt_credential_and_warn(
le32toh(t->blob_size) +
le32toh(t->policy_hash_size));
if (with_tpm2_pk) {
if (CRED_KEY_REQUIRES_TPM2_PK(h->id)) {
z = (struct tpm2_public_key_credential_header*) ((uint8_t*) input->iov_base + p);
if (!TPM2_PCR_MASK_VALID(le64toh(z->pcr_mask)) || le64toh(z->pcr_mask) == 0)
@ -1353,7 +1340,7 @@ int decrypt_credential_and_warn(
if (input->iov_len <
p +
ALIGN8(offsetof(struct tpm2_public_key_credential_header, data) + le32toh(z->size)) +
ALIGN8(with_scope ? sizeof(struct scoped_credential_header) : 0) +
ALIGN8(CRED_KEY_IS_SCOPED(h->id) ? sizeof(struct scoped_credential_header) : 0) +
ALIGN8(offsetof(struct metadata_credential_header, name)) +
le32toh(h->tag_size))
return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), "Encrypted file too short.");
@ -1395,7 +1382,7 @@ int decrypt_credential_and_warn(
#endif
}
if (with_scope) {
if (CRED_KEY_IS_SCOPED(h->id)) {
struct scoped_credential_header* sh = (struct scoped_credential_header*) ((uint8_t*) input->iov_base + p);
if (le64toh(sh->flags) != SCOPE_HASH_DATA_BASE_FLAGS)
@ -1411,18 +1398,18 @@ int decrypt_credential_and_warn(
p += sizeof(struct scoped_credential_header);
}
if (with_host_key) {
if (CRED_KEY_REQUIRES_HOST(h->id)) {
r = get_credential_host_secret(/* flags= */ 0, &host_key);
if (r < 0)
return log_error_errno(r, "Failed to determine local credential key: %m");
}
if (with_null && !FLAGS_SET(flags, CREDENTIAL_ALLOW_NULL))
if (sd_id128_equal(h->id, CRED_AES256_GCM_BY_NULL) && !FLAGS_SET(flags, CREDENTIAL_ALLOW_NULL))
log_warning("Warning: using a null key for decryption and authentication. Confidentiality or authenticity are not provided.");
sha256_hash_host_and_tpm2_key(&host_key, &tpm2_key, md);
if (with_scope) {
if (CRED_KEY_IS_SCOPED(h->id)) {
r = mangle_uid_into_key(uid, md);
if (r < 0)
return r;

View File

@ -33,6 +33,10 @@ int open_credentials_dir(void);
#define SYSTEM_CREDENTIALS_DIRECTORY "/run/credentials/@system"
#define ENCRYPTED_SYSTEM_CREDENTIALS_DIRECTORY "/run/credentials/@encrypted"
/* Where system creds have been passed */
int get_system_credentials_dir(const char **ret);
int get_encrypted_system_credentials_dir(const char **ret);
int read_credential(const char *name, void **ret, size_t *ret_size); /* use in services! */
int read_credential_with_decryption(const char *name, void **ret, size_t *ret_size); /* use in generators + pid1! */
@ -79,15 +83,90 @@ typedef enum CredentialFlags {
SD_ID128_MAKE(ad,bc,4c,a3,ef,b6,42,01,ba,88,1b,6f,2e,40,95,ea)
#define CRED_AES256_GCM_BY_NULL SD_ID128_MAKE(05,84,69,da,f6,f5,43,24,80,05,49,da,0f,8e,a2,fb)
/* Two special IDs to pick a general automatic mode (i.e. tpm2+host if TPM2 exists, only host otherwise) or
* an initrd-specific automatic mode (i.e. tpm2 if firmware can do it, otherwise fixed zero-length key, and
* never involve host keys). These IDs will never be stored on disk, but are useful only internally while
* figuring out what precisely to write to disk. To mark that these aren't a "real" type, we'll prefix them
* with an underscore. */
/* Five special IDs to pick a general automatic mode. These IDs will never be stored on disk, but are useful
* only internally while figuring out what precisely to write to disk. To mark that these aren't a "real"
* type, we'll prefix them with an underscore. */
/* Use TPM2 if available + host if available and on physical media. If neither are available, fail. */
#define _CRED_AUTO SD_ID128_MAKE(a2,19,cb,07,85,b2,4c,04,b1,6d,18,ca,b9,d2,ee,01)
/* Use best TPM2, and do not use host, and fail if no TPM */
#define _CRED_AUTO_TPM2 SD_ID128_MAKE(45,f3,a6,7e,0c,12,42,56,a4,ee,75,eb,44,c6,5a,6f)
/* Use TPM2 *and* host, and fail if one of the two isn't available. */
#define _CRED_AUTO_HOST_AND_TPM2 SD_ID128_MAKE(da,f6,7a,60,d3,eb,47,b3,a9,be,2f,d5,fe,c2,15,22)
/* Like _CRED_AUTO_TPM2, but uses "null" if not TPM is around */
#define _CRED_AUTO_INITRD SD_ID128_MAKE(02,dc,8e,de,3a,02,43,ab,a9,ec,54,9c,05,e6,a0,71)
/* Like _CRED_AUTO, but with per-UID scoping */
#define _CRED_AUTO_SCOPED SD_ID128_MAKE(23,88,96,85,6f,74,48,8a,9c,78,6f,6a,b0,e7,3b,6a)
#define CRED_KEY_IS_VALID(key) \
sd_id128_in_set((key), \
CRED_AES256_GCM_BY_HOST, \
CRED_AES256_GCM_BY_HOST_SCOPED, \
CRED_AES256_GCM_BY_TPM2_HMAC, \
CRED_AES256_GCM_BY_TPM2_HMAC_WITH_PK, \
CRED_AES256_GCM_BY_HOST_AND_TPM2_HMAC, \
CRED_AES256_GCM_BY_HOST_AND_TPM2_HMAC_SCOPED, \
CRED_AES256_GCM_BY_HOST_AND_TPM2_HMAC_WITH_PK, \
CRED_AES256_GCM_BY_HOST_AND_TPM2_HMAC_WITH_PK_SCOPED, \
CRED_AES256_GCM_BY_NULL)
#define CRED_KEY_IS_AUTO(key) \
sd_id128_in_set((key), \
_CRED_AUTO, \
_CRED_AUTO_TPM2, \
_CRED_AUTO_HOST_AND_TPM2, \
_CRED_AUTO_INITRD, \
_CRED_AUTO_SCOPED)
#define CRED_KEY_IS_SCOPED(key) \
sd_id128_in_set((key), \
_CRED_AUTO_SCOPED, \
CRED_AES256_GCM_BY_HOST_SCOPED, \
CRED_AES256_GCM_BY_HOST_AND_TPM2_HMAC_SCOPED, \
CRED_AES256_GCM_BY_HOST_AND_TPM2_HMAC_WITH_PK_SCOPED)
#define CRED_KEY_REQUIRES_HOST(key) \
sd_id128_in_set((key), \
_CRED_AUTO_HOST_AND_TPM2, \
CRED_AES256_GCM_BY_HOST, \
CRED_AES256_GCM_BY_HOST_SCOPED, \
CRED_AES256_GCM_BY_HOST_AND_TPM2_HMAC, \
CRED_AES256_GCM_BY_HOST_AND_TPM2_HMAC_SCOPED, \
CRED_AES256_GCM_BY_HOST_AND_TPM2_HMAC_WITH_PK, \
CRED_AES256_GCM_BY_HOST_AND_TPM2_HMAC_WITH_PK_SCOPED)
#define CRED_KEY_WANTS_HOST(key) \
sd_id128_in_set((key), \
_CRED_AUTO, \
_CRED_AUTO_SCOPED)
#define CRED_KEY_REQUIRES_TPM2(key) \
sd_id128_in_set((key), \
_CRED_AUTO_TPM2, \
_CRED_AUTO_HOST_AND_TPM2, \
CRED_AES256_GCM_BY_TPM2_HMAC, \
CRED_AES256_GCM_BY_TPM2_HMAC_WITH_PK, \
CRED_AES256_GCM_BY_HOST_AND_TPM2_HMAC, \
CRED_AES256_GCM_BY_HOST_AND_TPM2_HMAC_SCOPED, \
CRED_AES256_GCM_BY_HOST_AND_TPM2_HMAC_WITH_PK, \
CRED_AES256_GCM_BY_HOST_AND_TPM2_HMAC_WITH_PK_SCOPED)
#define CRED_KEY_WANTS_TPM2(key) \
sd_id128_in_set((key), \
_CRED_AUTO, \
_CRED_AUTO_INITRD, \
_CRED_AUTO_SCOPED)
#define CRED_KEY_REQUIRES_TPM2_PK(key) \
sd_id128_in_set((key), \
CRED_AES256_GCM_BY_TPM2_HMAC_WITH_PK, \
CRED_AES256_GCM_BY_HOST_AND_TPM2_HMAC_WITH_PK, \
CRED_AES256_GCM_BY_HOST_AND_TPM2_HMAC_WITH_PK_SCOPED)
#define CRED_KEY_WANTS_TPM2_PK(key) \
sd_id128_in_set((key), \
_CRED_AUTO, \
_CRED_AUTO_TPM2, \
_CRED_AUTO_HOST_AND_TPM2, \
_CRED_AUTO_INITRD, \
_CRED_AUTO_SCOPED)
int encrypt_credential_and_warn(sd_id128_t with_key, const char *name, usec_t timestamp, usec_t not_after, const char *tpm2_device, uint32_t tpm2_hash_pcr_mask, const char *tpm2_pubkey_path, uint32_t tpm2_pubkey_pcr_mask, uid_t uid, const struct iovec *input, CredentialFlags flags, struct iovec *ret);
int decrypt_credential_and_warn(const char *validate_name, usec_t validate_timestamp, const char *tpm2_device, const char *tpm2_signature_path, uid_t uid, const struct iovec *input, CredentialFlags flags, struct iovec *ret);

View File

@ -313,12 +313,14 @@ static size_t table_data_size(TableDataType type, const void *data) {
case TABLE_INT64:
case TABLE_UINT64:
case TABLE_UINT64_HEX:
case TABLE_UINT64_HEX_0x:
case TABLE_BPS:
return sizeof(uint64_t);
case TABLE_INT32:
case TABLE_UINT32:
case TABLE_UINT32_HEX:
case TABLE_UINT32_HEX_0x:
return sizeof(uint32_t);
case TABLE_INT16:
@ -1028,12 +1030,14 @@ int table_add_many_internal(Table *t, TableDataType first_type, ...) {
case TABLE_UINT32:
case TABLE_UINT32_HEX:
case TABLE_UINT32_HEX_0x:
buffer.uint32 = va_arg(ap, uint32_t);
data = &buffer.uint32;
break;
case TABLE_UINT64:
case TABLE_UINT64_HEX:
case TABLE_UINT64_HEX_0x:
buffer.uint64 = va_arg(ap, uint64_t);
data = &buffer.uint64;
break;
@ -1455,10 +1459,12 @@ static int cell_data_compare(TableData *a, size_t index_a, TableData *b, size_t
case TABLE_UINT32:
case TABLE_UINT32_HEX:
case TABLE_UINT32_HEX_0x:
return CMP(a->uint32, b->uint32);
case TABLE_UINT64:
case TABLE_UINT64_HEX:
case TABLE_UINT64_HEX_0x:
return CMP(a->uint64, b->uint64);
case TABLE_PERCENT:
@ -1859,6 +1865,18 @@ static const char *table_data_format(Table *t, TableData *d, bool avoid_uppercas
break;
}
case TABLE_UINT32_HEX_0x: {
_cleanup_free_ char *p = NULL;
p = new(char, 2 + 8 + 1);
if (!p)
return NULL;
sprintf(p, "0x%" PRIx32, d->uint32);
d->formatted = TAKE_PTR(p);
break;
}
case TABLE_UINT64: {
_cleanup_free_ char *p = NULL;
@ -1883,6 +1901,18 @@ static const char *table_data_format(Table *t, TableData *d, bool avoid_uppercas
break;
}
case TABLE_UINT64_HEX_0x: {
_cleanup_free_ char *p = NULL;
p = new(char, 2 + 16 + 1);
if (!p)
return NULL;
sprintf(p, "0x%" PRIx64, d->uint64);
d->formatted = TAKE_PTR(p);
break;
}
case TABLE_PERCENT: {
_cleanup_free_ char *p = NULL;
@ -2822,10 +2852,12 @@ static int table_data_to_json(TableData *d, sd_json_variant **ret) {
case TABLE_UINT32:
case TABLE_UINT32_HEX:
case TABLE_UINT32_HEX_0x:
return sd_json_variant_new_unsigned(ret, d->uint32);
case TABLE_UINT64:
case TABLE_UINT64_HEX:
case TABLE_UINT64_HEX_0x:
return sd_json_variant_new_unsigned(ret, d->uint64);
case TABLE_PERCENT:

View File

@ -40,8 +40,10 @@ typedef enum TableDataType {
TABLE_UINT16,
TABLE_UINT32,
TABLE_UINT32_HEX,
TABLE_UINT32_HEX_0x,
TABLE_UINT64,
TABLE_UINT64_HEX,
TABLE_UINT64_HEX_0x,
TABLE_PERCENT,
TABLE_IFINDEX,
TABLE_IN_ADDR, /* Takes a union in_addr_union (or a struct in_addr) */

View File

@ -10,6 +10,7 @@
#include "errno-util.h"
#include "escape.h"
#include "fd-util.h"
#include "id128-util.h"
#include "log.h"
#include "mountpoint-util.h"
#include "pcrextend-util.h"
@ -158,3 +159,24 @@ int pcrextend_machine_id_word(char **ret) {
*ret = TAKE_PTR(word);
return 0;
}
int pcrextend_product_id_word(char **ret) {
_cleanup_free_ char *word = NULL;
sd_id128_t pid;
int r;
assert(ret);
r = id128_get_product(&pid);
if (IN_SET(r, -ENOENT, -EADDRNOTAVAIL)) /* No product UUID field, or an all-zero or all-0xFF UUID */
word = strdup("product-id:missing");
else if (r < 0)
return log_error_errno(r, "Failed to acquire product ID: %m");
else
word = strjoin("product-id:", SD_ID128_TO_STRING(pid));
if (!word)
return log_oom();
*ret = TAKE_PTR(word);
return 0;
}

View File

@ -3,3 +3,4 @@
int pcrextend_file_system_word(const char *path, char **ret, char **ret_normalized_path);
int pcrextend_machine_id_word(char **ret);
int pcrextend_product_id_word(char **ret);

File diff suppressed because it is too large Load Diff

View File

@ -142,6 +142,8 @@ typedef enum Tpm2UserspaceEventType {
TPM2_EVENT_FILESYSTEM,
TPM2_EVENT_VOLUME_KEY,
TPM2_EVENT_MACHINE_ID,
TPM2_EVENT_PRODUCT_ID,
TPM2_EVENT_KEYSLOT,
_TPM2_USERSPACE_EVENT_TYPE_MAX,
_TPM2_USERSPACE_EVENT_TYPE_INVALID = -EINVAL,
} Tpm2UserspaceEventType;
@ -149,7 +151,12 @@ typedef enum Tpm2UserspaceEventType {
const char* tpm2_userspace_event_type_to_string(Tpm2UserspaceEventType type) _const_;
Tpm2UserspaceEventType tpm2_userspace_event_type_from_string(const char *s) _pure_;
int tpm2_extend_bytes(Tpm2Context *c, char **banks, unsigned pcr_index, const void *data, size_t data_size, const void *secret, size_t secret_size, Tpm2UserspaceEventType event, const char *description);
int tpm2_pcr_extend_bytes(Tpm2Context *c, char **banks, unsigned pcr_index, const struct iovec *data, const struct iovec *secret, Tpm2UserspaceEventType event, const char *description);
int tpm2_nvpcr_get_index(const char *name, uint32_t *ret);
int tpm2_nvpcr_extend_bytes(Tpm2Context *c, const Tpm2Handle *session, const char *name, const struct iovec *data, const struct iovec *secret, Tpm2UserspaceEventType event_type, const char *description);
int tpm2_nvpcr_acquire_anchor_secret(struct iovec *ret, bool sync_secondary);
int tpm2_nvpcr_initialize(Tpm2Context *c, const Tpm2Handle *session, const char *name, const struct iovec *anchor_secret);
int tpm2_nvpcr_read(Tpm2Context *c, const Tpm2Handle *session, const char *name, struct iovec *ret, uint32_t *ret_nv_index);
uint32_t tpm2_tpms_pcr_selection_to_mask(const TPMS_PCR_SELECTION *s);
void tpm2_tpms_pcr_selection_from_mask(uint32_t mask, TPMI_ALG_HASH hash, TPMS_PCR_SELECTION *ret);
@ -296,7 +303,10 @@ int tpm2_tpm2b_public_to_fingerprint(const TPM2B_PUBLIC *public, void **ret_fing
int tpm2_define_policy_nv_index(Tpm2Context *c, const Tpm2Handle *session, TPM2_HANDLE requested_nv_index, const TPM2B_DIGEST *write_policy, TPM2_HANDLE *ret_nv_index, Tpm2Handle **ret_nv_handle, TPM2B_NV_PUBLIC *ret_nv_public);
int tpm2_write_policy_nv_index(Tpm2Context *c, const Tpm2Handle *policy_session, TPM2_HANDLE nv_index, const Tpm2Handle *nv_handle, const TPM2B_DIGEST *policy_digest);
int tpm2_undefine_policy_nv_index(Tpm2Context *c, const Tpm2Handle *session, TPM2_HANDLE nv_index, const Tpm2Handle *nv_handle);
int tpm2_define_nvpcr_nv_index(Tpm2Context *c, const Tpm2Handle *session, TPM2_HANDLE nv_index, TPMI_ALG_HASH algorithm, Tpm2Handle **ret_nv_handle);
int tpm2_extend_nvpcr_nv_index(Tpm2Context *c, TPM2_HANDLE nv_index, const Tpm2Handle *nv_handle, const struct iovec *digest);
int tpm2_undefine_nv_index(Tpm2Context *c, const Tpm2Handle *session, TPM2_HANDLE nv_index, const Tpm2Handle *nv_handle);
int tpm2_read_nv_index(Tpm2Context *c, const Tpm2Handle *session, TPM2_HANDLE nv_index, const Tpm2Handle *nv_handle, struct iovec *ret_value);
int tpm2_seal_data(Tpm2Context *c, const struct iovec *data, const Tpm2Handle *primary_handle, const Tpm2Handle *encryption_session, const TPM2B_DIGEST *policy, struct iovec *ret_public, struct iovec *ret_private);
int tpm2_unseal_data(Tpm2Context *c, const struct iovec *public, const struct iovec *private, const Tpm2Handle *primary_handle, const Tpm2Handle *policy_session, const Tpm2Handle *encryption_session, struct iovec *ret_data);
@ -510,3 +520,5 @@ const char* tpm2_pcr_index_to_string(int pcr) _const_;
assert_cc(TPM2_NV_INDEX_UNASSIGNED_FIRST >= TPM2_NV_INDEX_FIRST);
assert_cc(TPM2_NV_INDEX_UNASSIGNED_LAST <= TPM2_NV_INDEX_LAST);
#endif
bool tpm2_nvpcr_name_is_valid(const char *name);

View File

@ -6,14 +6,18 @@ static SD_VARLINK_DEFINE_METHOD(
Extend,
SD_VARLINK_FIELD_COMMENT("PCR number to extend, in range of 0…23"),
SD_VARLINK_DEFINE_INPUT(pcr, SD_VARLINK_INT, 0),
SD_VARLINK_DEFINE_INPUT(nvpcr, SD_VARLINK_INT, SD_VARLINK_NULLABLE),
SD_VARLINK_FIELD_COMMENT("Text string to measure. (Specify either this, or the 'data' field below, not both)"),
SD_VARLINK_DEFINE_INPUT(text, SD_VARLINK_STRING, SD_VARLINK_NULLABLE),
SD_VARLINK_FIELD_COMMENT("Binary data to measure, encoded in Base64. (Specify either this, or the 'text' field above, not both)"),
SD_VARLINK_DEFINE_INPUT(data, SD_VARLINK_STRING, SD_VARLINK_NULLABLE));
static SD_VARLINK_DEFINE_ERROR(NoSuchNvPCR);
SD_VARLINK_DEFINE_INTERFACE(
io_systemd_PCRExtend,
"io.systemd.PCRExtend",
SD_VARLINK_INTERFACE_COMMENT("TPM PCR Extension APIs"),
SD_VARLINK_SYMBOL_COMMENT("Measure some text or binary data into a PCR"),
&vl_method_Extend);
&vl_method_Extend,
&vl_error_NoSuchNvPCR);

View File

@ -272,6 +272,8 @@ _SD_BEGIN_DECLARATIONS;
#define SD_MESSAGE_TPM_PCR_EXTEND SD_ID128_MAKE(3f,7d,5e,f3,e5,4f,43,02,b4,f0,b1,43,bb,27,0c,ab)
#define SD_MESSAGE_TPM_PCR_EXTEND_STR SD_ID128_MAKE_STR(3f,7d,5e,f3,e5,4f,43,02,b4,f0,b1,43,bb,27,0c,ab)
#define SD_MESSAGE_TPM_NVPCR_EXTEND SD_ID128_MAKE(4c,2e,46,d2,66,a7,47,c6,ac,14,60,aa,54,48,4f,a7)
#define SD_MESSAGE_TPM_NVPCR_EXTEND_STR SD_ID128_MAKE_STR(4c,2e,46,d2,66,a7,47,c6,ac,14,60,aa,54,48,4f,a7)
#define SD_MESSAGE_MEMORY_TRIM SD_ID128_MAKE(f9,b0,be,46,5a,d5,40,d0,85,0a,d3,21,72,d5,7c,21)
#define SD_MESSAGE_MEMORY_TRIM_STR SD_ID128_MAKE_STR(f9,b0,be,46,5a,d5,40,d0,85,0a,d3,21,72,d5,7c,21)

View File

@ -25,6 +25,24 @@ executables += [
generator_template + {
'name' : 'systemd-tpm2-generator',
'sources' : files('tpm2-generator.c'),
'conditions' : [
'ENABLE_BOOTLOADER',
'HAVE_OPENSSL',
'HAVE_TPM2',
],
},
]
if conf.get('ENABLE_BOOTLOADER') == 1 and conf.get('HAVE_OPENSSL') == 1 and conf.get('HAVE_TPM2') == 1
nvpcrs = [ 'cryptsetup',
'hardware' ]
foreach n : nvpcrs
custom_target(
input : 'nvpcr/' + n + '.nvpcr.in',
output : n + '.nvpcr',
command : [jinja2_cmdline, '@INPUT@', '@OUTPUT@'],
install : true,
install_dir : prefixdir / 'lib/nvpcr')
endforeach
endif

View File

@ -0,0 +1,5 @@
{
"name" : "cryptsetup",
"algorithm" : "sha256",
"nvIndex" : {{TPM2_NVPCR_BASE + 1}}
}

View File

@ -0,0 +1,5 @@
{
"name" : "hardware",
"algorithm" : "sha256",
"nvIndex" : {{TPM2_NVPCR_BASE + 0}}
}

View File

@ -7,6 +7,10 @@
#include "alloc-util.h"
#include "build.h"
#include "conf-files.h"
#include "constants.h"
#include "creds-util.h"
#include "errno-util.h"
#include "fd-util.h"
#include "fileio.h"
#include "fs-util.h"
@ -16,7 +20,12 @@
#include "mkdir.h"
#include "parse-util.h"
#include "pretty-print.h"
#include "recurse-dir.h"
#include "set.h"
#include "string-util.h"
#include "strv.h"
#include "terminal-util.h"
#include "time-util.h"
#include "tmpfile-util.h"
#include "tpm2-util.h"
@ -41,7 +50,7 @@ static int help(int argc, char *argv[], void *userdata) {
return log_oom();
printf("%1$s [OPTIONS...]\n"
"\n%5$sSet up the TPM2 Storage Root Key (SRK).%6$s\n"
"\n%5$sSet up the TPM2 Storage Root Key (SRK), and initialize NvPCRs.%6$s\n"
"\n%3$sOptions:%4$s\n"
" -h --help Show this help\n"
" --version Show package version\n"
@ -252,22 +261,9 @@ static int load_public_key_tpm2(struct public_key_data *ret) {
return 0;
}
static int run(int argc, char *argv[]) {
static int setup_srk(void) {
int r;
log_setup();
r = parse_argv(argc, argv);
if (r <= 0)
return r;
if (arg_graceful && !tpm2_is_fully_supported()) {
log_notice("No complete TPM2 support detected, exiting gracefully.");
return EXIT_SUCCESS;
}
umask(0022);
_cleanup_(public_key_data_done) struct public_key_data runtime_key = {}, persistent_key = {}, tpm2_key = {};
r = load_public_key_disk(TPM2_SRK_PEM_RUNTIME_PATH, &runtime_key);
@ -390,4 +386,128 @@ static int run(int argc, char *argv[]) {
return 0;
}
typedef struct SetupNvPCRContext {
Tpm2Context *tpm2_context;
struct iovec anchor_secret;
size_t n_already, n_anchored;
Set *done;
} SetupNvPCRContext;
static void setup_nvpcr_context_done(SetupNvPCRContext *c) {
assert(c);
iovec_done_erase(&c->anchor_secret);
c->tpm2_context = tpm2_context_unref(c->tpm2_context);
c->done = set_free(c->done);
}
static int setup_nvpcr_one(
SetupNvPCRContext *c,
const char *name) {
int r;
assert(c);
assert(name);
if (set_contains(c->done, name))
return 0;
if (!c->tpm2_context) {
r = tpm2_context_new_or_warn(arg_tpm2_device, &c->tpm2_context);
if (r < 0)
return r;
}
r = tpm2_nvpcr_initialize(c->tpm2_context, /* session= */ NULL, name, &c->anchor_secret);
if (r == -EUNATCH) {
assert(!iovec_is_set(&c->anchor_secret));
/* If we get EUNATCH this means we actually need to initialize this NvPCR
* now, and haven't provided the anchor secret yet. Hence acquire it now. */
r = tpm2_nvpcr_acquire_anchor_secret(&c->anchor_secret, /* sync_secondary= */ !arg_early);
if (r < 0)
return log_error_errno(r, "Failed to acquire anchor secret: %m");
r = tpm2_nvpcr_initialize(c->tpm2_context, /* session= */ NULL, name, &c->anchor_secret);
}
if (r < 0)
return log_error_errno(r, "Failed to extend NvPCR index with anchor secret: %m");
if (r > 0)
c->n_anchored++;
else
c->n_already++;
if (set_put_strdup(&c->done, name) < 0)
return log_oom();
return 0;
}
static int setup_nvpcr(void) {
_cleanup_(setup_nvpcr_context_done) SetupNvPCRContext c = {};
int r = 0;
_cleanup_strv_free_ char **l = NULL;
r = conf_files_list_nulstr(
&l,
".nvpcr",
/* root= */ NULL,
CONF_FILES_REGULAR|CONF_FILES_BASENAME|CONF_FILES_FILTER_MASKED|CONF_FILES_TRUNCATE_SUFFIX,
CONF_PATHS_NULSTR("nvpcr"));
if (r < 0)
return log_error_errno(r, "Failed to find .nvpcr files: %m");
STRV_FOREACH(i, l) {
r = setup_nvpcr_one(&c, *i);
if (r < 0)
return r;
}
if (c.n_already > 0 && c.n_anchored == 0 && !arg_early) {
/* If we didn't anchor anything right now, but we anchored something earlier, then it might
* have happened in the initrd, and thus the anchor ID was not commited to /var/ or the ESP
* yet. Hence, let's explicitly do so now, to catch up. */
r = tpm2_nvpcr_acquire_anchor_secret(/* ret= */ NULL, /* sync_secondary= */ true);
if (r < 0)
return r;
}
if (c.n_anchored > 0) {
if (c.n_already == 0)
log_info("%zu NvPCRs initialized.", c.n_anchored);
else
log_info("%zu NvPCRs initialized. (%zu NvPCRs were already initialized.)", c.n_anchored, c.n_already);
} else if (c.n_already > 0)
log_info("%zu NvPCRs already initialized.", c.n_already);
else
log_debug("No NvPCRs defined, nothing initialized.");
return r;
}
static int run(int argc, char *argv[]) {
int r;
log_setup();
r = parse_argv(argc, argv);
if (r <= 0)
return r;
if (arg_graceful && !tpm2_is_fully_supported()) {
log_notice("No complete TPM2 support detected, exiting gracefully.");
return EXIT_SUCCESS;
}
umask(0022);
r = setup_srk();
RET_GATHER(r, setup_nvpcr());
return r;
}
DEFINE_MAIN_FUNCTION_WITH_POSITIVE_FAILURE(run);

View File

@ -1093,6 +1093,11 @@ systemd-analyze image-policy 'home=encrypted:usr=verity' 2>&1 | grep -q -e '^usr
systemd-analyze pcrs
systemd-analyze pcrs --json=pretty
systemd-analyze pcrs 14 7 0 ima
if systemd-analyze has-tpm2 -q ; then
systemd-analyze nvpcrs
systemd-analyze nvpcrs --json=pretty
systemd-analyze nvpcrs hardware cryptsetup
fi
systemd-analyze architectures
systemd-analyze architectures --json=pretty

View File

@ -30,6 +30,7 @@ export SYSTEMD_FORCE_MEASURE=1
"$SD_PCREXTEND" --version
"$SD_PCREXTEND" foo
"$SD_PCREXTEND" --machine-id
"$SD_PCREXTEND" --product-id
"$SD_PCREXTEND" --tpm2-device=list
"$SD_PCREXTEND" --tpm2-device=auto foo
"$SD_PCREXTEND" --tpm2-device=/dev/tpm0 foo
@ -40,6 +41,7 @@ export SYSTEMD_FORCE_MEASURE=1
"$SD_PCREXTEND" --file-system=/
"$SD_PCREXTEND" --file-system=/tmp --file-system=/
"$SD_PCREXTEND" --file-system=/tmp --file-system=/ --pcr=15 --pcr=11
"$SD_PCREXTEND" --nvpcr=hardware foo
if tpm_has_pcr sha1 11; then
"$SD_PCREXTEND" --bank=sha1 --pcr=11 foo
@ -55,6 +57,7 @@ fi
(! "$SD_PCREXTEND" --pcr=-1 foo)
(! "$SD_PCREXTEND" --pcr=1024 foo)
(! "$SD_PCREXTEND" --foo=bar)
(! "$SD_PCREXTEND" --nvpcr=idontexist foo)
unset SYSTEMD_FORCE_MEASURE
@ -122,3 +125,28 @@ diff /tmp/newpcr15 \
<(cat /tmp/oldpcr15 <(echo -n "file-system:$FS_WORD" | openssl dgst -binary -sha256) | openssl dgst -binary -sha256)
rm -f /tmp/oldpcr{11,15} /tmp/newpcr{11,15}
mkdir -p /run/nvpcr
cat >/run/nvpcr/test.nvpcr <<EOF
{"name":"test","algorithm":"sha256","nvIndex":30474762}
EOF
/usr/lib/systemd/systemd-tpm2-setup
test -f /run/systemd/nvpcr/test.anchor
/usr/lib/systemd/systemd-pcrextend --nvpcr=test schrumpel
# To calculate the current value we need the anchor measurement
DIGEST_BASE="$(cat /run/systemd/nvpcr/test.anchor)"
DIGEST_MEASURED="$(echo -n "schrumpel" | openssl dgst -sha256 -binary | xxd -p -c200)"
DIGEST_EXPECTED="$(echo "$DIGEST_BASE$DIGEST_MEASURED" | xxd -r -p | openssl dgst -sha256 -binary | xxd -p -c200)"
DIGEST_ACTUAL="$(systemd-analyze nvpcrs test --json=pretty | jq -r '.[] | select(.name=="test") | .value')"
test "$DIGEST_ACTUAL" = "$DIGEST_EXPECTED"
# Now "destroy" the value via another measurement
/usr/lib/systemd/systemd-pcrextend --nvpcr=test schnurz
DIGEST_ACTUAL2="$(systemd-analyze nvpcrs test --json=pretty | jq -r '.[] | select(.name=="test") | .value')"
test "$DIGEST_ACTUAL2" != "$DIGEST_EXPECTED"
# And calculate the new result
DIGEST_MEASURED2="$(echo -n "schnurz" | openssl dgst -sha256 -binary | xxd -p -c200)"
DIGEST_EXPECTED2="$(echo "$DIGEST_EXPECTED$DIGEST_MEASURED2" | xxd -r -p | openssl dgst -sha256 -binary | xxd -p -c200)"
test "$DIGEST_ACTUAL2" = "$DIGEST_EXPECTED2"

View File

@ -15,7 +15,7 @@ fi
"$SD_TPM2SETUP" --version
"$SD_TPM2SETUP" --tpm2-device=list
"$SD_TPM2SETUP" --tpm2-device=auto
"$SD_TPM2SETUP" --tpm2-device=/dev/tpm0
"$SD_TPM2SETUP" --tpm2-device=/dev/tpmrm0
"$SD_TPM2SETUP" --early=yes
"$SD_TPM2SETUP" --early=yes
"$SD_TPM2SETUP" --early=no

View File

@ -556,6 +556,11 @@ units = [
'conditions' : ['ENABLE_BOOTLOADER', 'HAVE_OPENSSL', 'HAVE_TPM2'],
'symlinks' : ['factory-reset.target.wants/'],
},
{
'file' : 'systemd-pcrproduct.service.in',
'conditions' : ['ENABLE_BOOTLOADER', 'HAVE_OPENSSL', 'HAVE_TPM2'],
'symlinks' : ['sysinit.target.wants/'],
},
{
'file' : 'systemd-pcrphase-initrd.service.in',
'conditions' : ['ENABLE_BOOTLOADER', 'HAVE_OPENSSL', 'HAVE_TPM2', 'ENABLE_INITRD'],

View File

@ -0,0 +1,23 @@
# SPDX-License-Identifier: LGPL-2.1-or-later
#
# This file is part of systemd.
#
# systemd is free software; you can redistribute it and/or modify it
# under the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation; either version 2.1 of the License, or
# (at your option) any later version.
[Unit]
Description=TPM PCR Product ID Measurement
Documentation=man:systemd-pcrproduct.service(8)
DefaultDependencies=no
Conflicts=shutdown.target
After=tpm2.target
Before=sysinit.target shutdown.target
ConditionPathExists=!/etc/initrd-release
ConditionSecurity=measured-uki
[Service]
Type=oneshot
RemainAfterExit=yes
ExecStart={{LIBEXECDIR}}/systemd-pcrextend --graceful --product-id

View File

@ -14,7 +14,7 @@ DefaultDependencies=no
Conflicts=shutdown.target
After=tpm2.target systemd-tpm2-setup-early.service systemd-remount-fs.service
Before=sysinit.target shutdown.target
RequiresMountsFor=/var/lib/systemd/tpm2-srk-public-key.pem
RequiresMountsFor=/var/lib/systemd
ConditionSecurity=measured-uki
ConditionPathExists=!/etc/initrd-release