mirror of
https://github.com/systemd/systemd
synced 2025-11-16 23:34:46 +01:00
Compare commits
27 Commits
5fa2fb65a6
...
9218e4eacc
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9218e4eacc | ||
|
|
10cdb58690 | ||
|
|
49ca2d8778 | ||
|
|
f8fcf7de7c | ||
|
|
8f62d20b73 | ||
|
|
330e66f15e | ||
|
|
34c687f2b3 | ||
|
|
a9d02df0c7 | ||
|
|
d73d369133 | ||
|
|
e5a2e78665 | ||
|
|
8a6e77f1a8 | ||
|
|
2da86d62ff | ||
|
|
0196abbd10 | ||
|
|
2b90bf1730 | ||
|
|
f5f26332fa | ||
|
|
b0c5c6aad8 | ||
|
|
b7e072f3fa | ||
|
|
260b9e8489 | ||
|
|
d412b1629c | ||
|
|
b95912446e | ||
|
|
3e9ff7c0d8 | ||
|
|
ffe958b98f | ||
|
|
c30e3d7290 | ||
|
|
d2d1fc59b4 | ||
|
|
86279dc970 | ||
|
|
7ecc69c33b | ||
|
|
1acec1c890 |
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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`
|
||||
|
||||
@ -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>
|
||||
|
||||
|
||||
@ -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>><replaceable>FILE</replaceable></optional></command></title>
|
||||
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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>
|
||||
|
||||
|
||||
@ -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>
|
||||
|
||||
|
||||
@ -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:
|
||||
|
||||
@ -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' },
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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'
|
||||
|
||||
110
src/analyze/analyze-nvpcrs.c
Normal file
110
src/analyze/analyze-nvpcrs.c
Normal 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
|
||||
}
|
||||
4
src/analyze/analyze-nvpcrs.h
Normal file
4
src/analyze/analyze-nvpcrs.h
Normal file
@ -0,0 +1,4 @@
|
||||
/* SPDX-License-Identifier: LGPL-2.1-or-later */
|
||||
#pragma once
|
||||
|
||||
int verb_nvpcrs(int argc, char *argv[], void *userdata);
|
||||
@ -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 },
|
||||
|
||||
@ -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',
|
||||
|
||||
@ -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. */
|
||||
|
||||
|
||||
@ -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;
|
||||
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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);
|
||||
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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),
|
||||
|
||||
@ -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:
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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");
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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);
|
||||
|
||||
|
||||
@ -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:
|
||||
|
||||
@ -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) */
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
@ -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
@ -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);
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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
|
||||
|
||||
5
src/tpm2-setup/nvpcr/cryptsetup.nvpcr.in
Normal file
5
src/tpm2-setup/nvpcr/cryptsetup.nvpcr.in
Normal file
@ -0,0 +1,5 @@
|
||||
{
|
||||
"name" : "cryptsetup",
|
||||
"algorithm" : "sha256",
|
||||
"nvIndex" : {{TPM2_NVPCR_BASE + 1}}
|
||||
}
|
||||
5
src/tpm2-setup/nvpcr/hardware.nvpcr.in
Normal file
5
src/tpm2-setup/nvpcr/hardware.nvpcr.in
Normal file
@ -0,0 +1,5 @@
|
||||
{
|
||||
"name" : "hardware",
|
||||
"algorithm" : "sha256",
|
||||
"nvIndex" : {{TPM2_NVPCR_BASE + 0}}
|
||||
}
|
||||
@ -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);
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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"
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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'],
|
||||
|
||||
23
units/systemd-pcrproduct.service.in
Normal file
23
units/systemd-pcrproduct.service.in
Normal 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
|
||||
@ -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
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user