diff --git a/catalog/systemd.catalog.in b/catalog/systemd.catalog.in
index f7ef980962..4152112078 100644
--- a/catalog/systemd.catalog.in
+++ b/catalog/systemd.catalog.in
@@ -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
diff --git a/docs/TPM2_PCR_MEASUREMENTS.md b/docs/TPM2_PCR_MEASUREMENTS.md
index c6164b76d2..abd280c004 100644
--- a/docs/TPM2_PCR_MEASUREMENTS.md
+++ b/docs/TPM2_PCR_MEASUREMENTS.md
@@ -43,6 +43,37 @@ recognizable. Measurements currently recorded as `EV_IPL` will continue to be
recorded as `EV_IPL`, for compatibility reasons. However, `EV_IPL` will not be
used for new, additional measurements.
+## NvPCR Measurements
+
+Since the PCR number space is very small, systemd userspace supports additional
+PCRs implemented via TPM2 NV Indexes (here called *NvPCRs*, even though they
+are no less volatile than classic PCRs), using the `TPM2_NT_EXTEND` type. These
+mostly behave like real PCRs, but we can allocate them relatively freely from
+the NV index handle space.
+
+The NV index range to use for this is configurable at build time, so that
+downstreams have some flexibility to change this if they want. This uses the
+0x01d10200 NV index as base by default. To abstract the actual nvindex number
+away there's a naming concept, so that nvindexes are referenced by name string
+rather than number.
+
+NvPCRs are defined in little JSON snippets in `/usr/lib/nvpcr/*.nvpcr`, that
+match up index number and name, as well as pick a hash algorithm.
+
+There's one complication: these NV indexes (like any NV indexes) can be deleted
+by anyone with access to the TPM, and then be recreated. This could be used to
+reset the NvPCRs to zero during runtime, which defeats the whole point of
+them. Our way out: we measure a secret as first thing after creation into the
+NvPCRs. (Or actually, we measure a per-NvPCR secret we derive from a system
+secret via an HMAC of the NvPCR name and the NV index handle). This "anchoring"
+secret is stored in `/run/` + `/var/lib/` + ESP/XBOOTLDR (the latter encrypted
+as credential, locked to the TPM), to make it available at the whole runtime of
+the OS. It's only accessible to privileged processes with access to the
+TPM. Due to this, any process with access to the TPM and read access to any of
+the storage locations of the anchor secret is considered part of the TCB, as
+they are able to replay the NvPCR with their own content at will, so due care
+must be employed when designing a system that uses this feature.
+
## PCR Measurements Made by `systemd-boot` (UEFI)
### PCR 5, `EV_EVENT_TAG`, `loader.conf`
@@ -168,7 +199,7 @@ initrd" in UTF-16.
→ **Measured hash** covers the per-UKI sysext cpio archive (which is generated
on-the-fly by `systemd-stub`).
-## PCR Measurements Made by `systemd-pcrextend` (Userspace)
+## PCR/NvPCR Measurements Made by `systemd-pcrextend` (Userspace)
### PCR 11, boot phases
@@ -191,6 +222,17 @@ from `/etc/machine-id`) during boot.
formatted in hexadecimal lowercase characters (in UTF-8, without trailing NUL
bytes).
+### NvPCR `hardware` (base+0), product UUID
+
+The `systemd-pcrproduct.service` service will measure the product UUID (as
+available from SMBIOS or Devicetree) of the host system, once at boot.
+
+→ **Measured hash** covers the string "product-id:" suffixed by the product
+UUID formatted in hexadecimal lowercase characters, without separators. If no
+product UUID of the local system could be determined the string
+"product-id:missing" is measured instead. Example string:
+`product-id:4691595be6a345f1833cc75fab63e475`.
+
### PCR 15, file system
The `systemd-pcrfs-root.service` and `systemd-pcrfs@.service` services will
@@ -202,7 +244,7 @@ colon-separated strings, identifying the file system type, UUID, label as well
as the GPT partition entry UUID, entry type UUID and entry label (in UTF-8,
without trailing NUL bytes).
-## PCR Measurements Made by `systemd-cryptsetup` (Userspace)
+## PCR/NvPCR Measurements Made by `systemd-cryptsetup` (Userspace)
### PCR 15, volume key
@@ -214,3 +256,15 @@ system).
→ **Measured hash** covers the (binary) result of the HMAC(V,S) calculation where V
is the LUKS volume key, and S is the string "cryptsetup:" followed by the LUKS
volume name and the UUID of the LUKS superblock.
+
+### NvPCR `cryptsetup` (base+1), LUKS unlock mechanism/key slot
+
+The `systemd-cryptsetup@.service` service will measure information about the
+used LUKS keyslot, and in particular include the used unlock mechanism (pkcs11,
+tpm2, fido2, …) in it.
+
+→ **Measured hash** covers the string "cryptsetup-keyslot:", suffixed by the DM
+volume name, a ":" separator, the UUID of the LUKS superblock, a ":" separator,
+a brief string identifying the unlock mechanism, a ":" separator, and finally
+the LUKS slot number used. Example string:
+`cryptsetup-keyslot:root:1e023a55-60f9-4b6b-9b80-67438dc5f065:tpm2:1`
diff --git a/man/crypttab.xml b/man/crypttab.xml
index 1f77939589..a8a8242a62 100644
--- a/man/crypttab.xml
+++ b/man/crypttab.xml
@@ -941,6 +941,20 @@
+
+
+
+ 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 cryptsetup to
+ enable this logic. The slot index and the used unlock mechanism (i.e. tpm2,
+ fido2, pkcs11) is measured along with the activated volume name
+ and its UUID.
+
+
+
+
diff --git a/man/systemd-analyze.xml b/man/systemd-analyze.xml
index 04b8d8d41f..fb95a3ec53 100644
--- a/man/systemd-analyze.xml
+++ b/man/systemd-analyze.xml
@@ -201,6 +201,12 @@
pcrs
PCR
+
+ systemd-analyze
+ OPTIONS
+ nvpcrs
+ NVPCR
+
systemd-analyze
OPTIONS
@@ -1059,6 +1065,24 @@ NR NAME SHA256
+
+ systemd-analyze nvpcrs NVPCR…
+
+ This command shows the known TPM2 NvPCRs (additional PCRs stored in TPM2 NV indexes) along with
+ their identifying names and current values.
+
+
+ Example Output
+
+ $ systemd-analyze nvpcrs
+NAME NVINDEX VALUE
+cryptsetup 0x1d10201 f400543943cc7215557ce672872ace5382e6d53177cc459078ba9277e41588d9
+hardware 0x1d10200 e155474936d7d74c893e6ece1099a2311d572cf23becea159dabf282db754284
+
+
+
+
+
systemd-analyze srk >FILE
diff --git a/man/systemd-creds.xml b/man/systemd-creds.xml
index 7b9ce14e5f..60ded42193 100644
--- a/man/systemd-creds.xml
+++ b/man/systemd-creds.xml
@@ -327,15 +327,15 @@
When specified with the encrypt command controls the
encryption/signature key to use. Takes one of host, tpm2,
host+tpm2, null, auto,
- auto-initrd. See above for details on the three key types. If set to
+ auto-initrd. See above for details on the key types. If set to
auto (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 /var/lib/systemd/ 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
auto is selected but neither TPM2 is available (or running in container) nor
/var/lib/systemd/ is on persistent media, encryption will fail. If set to
- null 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
+ null 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
diff --git a/man/systemd-pcrphase.service.xml b/man/systemd-pcrphase.service.xml
index e301435f8e..b95007dfcb 100644
--- a/man/systemd-pcrphase.service.xml
+++ b/man/systemd-pcrphase.service.xml
@@ -21,10 +21,11 @@
systemd-pcrphase-sysinit.service
systemd-pcrphase-initrd.service
systemd-pcrmachine.service
+ systemd-pcrproduct.service
systemd-pcrfs-root.service
systemd-pcrfs@.service
systemd-pcrextend
- Measure boot phase into TPM2 PCR 11, machine ID and file system identity into PCR 15
+ Measure boot phases, machine ID, product UUID and file system identity into TPM PCRs and NvPCRs
@@ -49,6 +50,10 @@
(see machine-id5) into
PCR 15.
+ systemd-pcrproduct.service is a system service that measures the firmware
+ product UUID (as provided by one of SMBIOS, Devicetree, …) into a NvPCR named
+ hardware.
+
systemd-pcrfs-root.service and systemd-pcrfs@.service 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. systemd-pcrfs-root.service does so for
@@ -138,7 +143,8 @@
Options
The /usr/lib/systemd/system-pcrextend 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:
+ command line, where it expects the word to extend into a PCR or NvPCR, as well as the following
+ switches:
@@ -155,11 +161,24 @@
Takes the index of the PCR to extend. If or
- are specified defaults to 15, otherwise defaults to 11.
+ are specified defaults to 15, otherwise (and unless
+ is specified) defaults to 11. May not be combined with
+ .
+
+
+
+ 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 hardware or
+ disk-encryption. If is specified defaults
+ hardware. May not be combined with .
+
+
+
+
@@ -182,6 +201,17 @@
+
+
+
+ Selects early-boot mode. Specifically this means that the NvPCR anchor secret is not
+ attempted to be written into /var/lib/ and the boot loader partition in addition
+ to /run/. (Unlike the latter the former are generally not mounted and writable
+ during early boot or in the initrd.)
+
+
+
+
@@ -191,6 +221,15 @@
+
+
+
+ Instead of measuring a word specified on the command line into PCR 11, measure the
+ firmware's product UUID into an NvPCR named hardware.
+
+
+
+
@@ -230,6 +269,13 @@
+
+
+ /usr/lib/nvpcr/*.nvpcr
+
+ Definition files for NvPCRs.
+
+
diff --git a/man/systemd-tpm2-setup.service.xml b/man/systemd-tpm2-setup.service.xml
index 52ed6acf92..95d8da4644 100644
--- a/man/systemd-tpm2-setup.service.xml
+++ b/man/systemd-tpm2-setup.service.xml
@@ -20,7 +20,7 @@
systemd-tpm2-setup.service
systemd-tpm2-setup-early.service
systemd-tpm2-setup
- Set up the TPM2 Storage Root Key (SRK) at boot
+ Set up the TPM2 Storage Root Key (SRK) and initialize NvPCRs at boot
@@ -33,7 +33,8 @@
systemd-tpm2-setup.service and
systemd-tpm2-setup-early.service are services that generate the Storage Root Key
- (SRK) if it has not been generated yet, and stores it in the TPM.
+ (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.
The services will store the public key of the SRK key pair in a PEM file in
/run/systemd/tpm2-srk-public-key.pem and
@@ -70,6 +71,28 @@
+
+
+ /usr/lib/nvpcr/*.nvpcr
+
+ Definition files for NvPCRs.
+
+
+
+
+ /run/credentials/@encrypted/nvpcr-anchor.*
+
+ Encrypted NvPCR anchor secret, received into the system via system credentials.
+
+
+
+
+ $BOOT/loader/credentials/nvpcr-anchor.*.cred
+
+ Encrypted NvPCR anchor secret, to be picked up by the kernel loader stub,
+ i.e. systemd-stub7.
+
+
diff --git a/meson.build b/meson.build
index 24efc9f7c3..6c4b67d28d 100644
--- a/meson.build
+++ b/meson.build
@@ -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:
diff --git a/meson_options.txt b/meson_options.txt
index f7ec39322c..d44030ef8b 100644
--- a/meson_options.txt
+++ b/meson_options.txt
@@ -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' },
diff --git a/shell-completion/bash/systemd-analyze b/shell-completion/bash/systemd-analyze
index 832da33021..0ed8191c0a 100644
--- a/shell-completion/bash/systemd-analyze
+++ b/shell-completion/bash/systemd-analyze
@@ -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'
diff --git a/src/analyze/analyze-nvpcrs.c b/src/analyze/analyze-nvpcrs.c
new file mode 100644
index 0000000000..0fc7b163cf
--- /dev/null
+++ b/src/analyze/analyze-nvpcrs.c
@@ -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
+}
diff --git a/src/analyze/analyze-nvpcrs.h b/src/analyze/analyze-nvpcrs.h
new file mode 100644
index 0000000000..258005617e
--- /dev/null
+++ b/src/analyze/analyze-nvpcrs.h
@@ -0,0 +1,4 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+#pragma once
+
+int verb_nvpcrs(int argc, char *argv[], void *userdata);
diff --git a/src/analyze/analyze.c b/src/analyze/analyze.c
index 9b611daade..d3040d03e0 100644
--- a/src/analyze/analyze.c
+++ b/src/analyze/analyze.c
@@ -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 },
diff --git a/src/analyze/meson.build b/src/analyze/meson.build
index e26426fc6c..03fade3f4b 100644
--- a/src/analyze/meson.build
+++ b/src/analyze/meson.build
@@ -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',
diff --git a/src/basic/conf-files.c b/src/basic/conf-files.c
index c00325f2e4..77affe269c 100644
--- a/src/basic/conf-files.c
+++ b/src/basic/conf-files.c
@@ -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;
diff --git a/src/basic/conf-files.h b/src/basic/conf-files.h
index f6e63120e5..05a2966e21 100644
--- a/src/basic/conf-files.h
+++ b/src/basic/conf-files.h
@@ -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 {
diff --git a/src/basic/fileio.c b/src/basic/fileio.c
index ca763a315b..a493384c52 100644
--- a/src/basic/fileio.c
+++ b/src/basic/fileio.c
@@ -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);
diff --git a/src/basic/fileio.h b/src/basic/fileio.h
index 96a8773620..578c16c0ee 100644
--- a/src/basic/fileio.h
+++ b/src/basic/fileio.h
@@ -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);
diff --git a/src/creds/creds.c b/src/creds/creds.c
index 92b527270d..383ef268b8 100644
--- a/src/creds/creds.c
+++ b/src/creds/creds.c
@@ -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:
diff --git a/src/cryptsetup/cryptsetup.c b/src/cryptsetup/cryptsetup.c
index 7351e5dc2a..508ca9750d 100644
--- a/src/cryptsetup/cryptsetup.c
+++ b/src/cryptsetup/cryptsetup.c
@@ -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;
}
diff --git a/src/gpt-auto-generator/gpt-auto-generator.c b/src/gpt-auto-generator/gpt-auto-generator.c
index 8ed8f15bbd..6b5ca7ed6a 100644
--- a/src/gpt-auto-generator/gpt-auto-generator.c
+++ b/src/gpt-auto-generator/gpt-auto-generator.c
@@ -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);
diff --git a/src/pcrextend/pcrextend.c b/src/pcrextend/pcrextend.c
index 75b561da90..641b1e8cbe 100644
--- a/src/pcrextend/pcrextend.c
+++ b/src/pcrextend/pcrextend.c
@@ -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;
}
diff --git a/src/pcrlock/pcrlock.c b/src/pcrlock/pcrlock.c
index 3a294a9171..8be8c9ac3d 100644
--- a/src/pcrlock/pcrlock.c
+++ b/src/pcrlock/pcrlock.c
@@ -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,
diff --git a/src/shared/creds-util.c b/src/shared/creds-util.c
index fe3fce3576..ae448cbd59 100644
--- a/src/shared/creds-util.c
+++ b/src/shared/creds-util.c
@@ -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;
diff --git a/src/shared/creds-util.h b/src/shared/creds-util.h
index d23afa6c13..7b2fc76eb4 100644
--- a/src/shared/creds-util.h
+++ b/src/shared/creds-util.h
@@ -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);
diff --git a/src/shared/format-table.c b/src/shared/format-table.c
index c129f260e8..1c728a3d2b 100644
--- a/src/shared/format-table.c
+++ b/src/shared/format-table.c
@@ -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:
diff --git a/src/shared/format-table.h b/src/shared/format-table.h
index 2222373eb3..b0b9b2060e 100644
--- a/src/shared/format-table.h
+++ b/src/shared/format-table.h
@@ -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) */
diff --git a/src/shared/pcrextend-util.c b/src/shared/pcrextend-util.c
index e1e80a93ee..15e0da822a 100644
--- a/src/shared/pcrextend-util.c
+++ b/src/shared/pcrextend-util.c
@@ -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;
+}
diff --git a/src/shared/pcrextend-util.h b/src/shared/pcrextend-util.h
index 7dd612b8fb..88bd23c10d 100644
--- a/src/shared/pcrextend-util.h
+++ b/src/shared/pcrextend-util.h
@@ -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);
diff --git a/src/shared/tpm2-util.c b/src/shared/tpm2-util.c
index de1c56a84e..da74ca06a5 100644
--- a/src/shared/tpm2-util.c
+++ b/src/shared/tpm2-util.c
@@ -6,12 +6,15 @@
#include "alloc-util.h"
#include "ansi-color.h"
#include "bitfield.h"
+#include "bootspec.h"
+#include "boot-entry.h"
#include "constants.h"
#include "creds-util.h"
#include "cryptsetup-util.h"
#include "dirent-util.h"
#include "dlfcn-util.h"
#include "efi-api.h"
+#include "errno-util.h"
#include "extract-word.h"
#include "fd-util.h"
#include "fileio.h"
@@ -39,6 +42,7 @@
#include "sync-util.h"
#include "time-util.h"
#include "tpm2-pcr.h"
+#include "tmpfile-util.h"
#include "tpm2-util.h"
#include "virt.h"
@@ -65,6 +69,9 @@ static DLSYM_PROTOTYPE(Esys_Initialize) = NULL;
static DLSYM_PROTOTYPE(Esys_Load) = NULL;
static DLSYM_PROTOTYPE(Esys_LoadExternal) = NULL;
static DLSYM_PROTOTYPE(Esys_NV_DefineSpace) = NULL;
+static DLSYM_PROTOTYPE(Esys_NV_Extend) = NULL;
+static DLSYM_PROTOTYPE(Esys_NV_Read) = NULL;
+static DLSYM_PROTOTYPE(Esys_NV_ReadPublic) = NULL;
static DLSYM_PROTOTYPE(Esys_NV_UndefineSpace) = NULL;
static DLSYM_PROTOTYPE(Esys_NV_Write) = NULL;
static DLSYM_PROTOTYPE(Esys_PCR_Extend) = NULL;
@@ -139,6 +146,9 @@ static int dlopen_tpm2_esys(void) {
DLSYM_ARG(Esys_Load),
DLSYM_ARG(Esys_LoadExternal),
DLSYM_ARG(Esys_NV_DefineSpace),
+ DLSYM_ARG(Esys_NV_Extend),
+ DLSYM_ARG(Esys_NV_Read),
+ DLSYM_ARG(Esys_NV_ReadPublic),
DLSYM_ARG(Esys_NV_UndefineSpace),
DLSYM_ARG(Esys_NV_Write),
DLSYM_ARG(Esys_PCR_Extend),
@@ -5889,7 +5899,7 @@ int tpm2_write_policy_nv_index(
return 0;
}
-int tpm2_undefine_policy_nv_index(
+int tpm2_undefine_nv_index(
Tpm2Context *c,
const Tpm2Handle *session,
TPM2_HANDLE nv_index,
@@ -5904,14 +5914,232 @@ int tpm2_undefine_policy_nv_index(
c->esys_context,
/* authHandle= */ ESYS_TR_RH_OWNER,
/* nvIndex= */ nv_handle->esys_handle,
- /* shandle1= */ session ? session->esys_handle : ESYS_TR_NONE,
+ /* shandle1= */ session ? session->esys_handle : ESYS_TR_PASSWORD,
/* shandle2= */ ESYS_TR_NONE,
/* shandle3= */ ESYS_TR_NONE);
if (rc != TSS2_RC_SUCCESS)
return log_debug_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE),
"Failed to undefine NV index: %s", sym_Tss2_RC_Decode(rc));
- log_debug("Undefined NV index 0x%x", nv_index);
+ log_debug("Successfully undefined NV index 0x%x.", nv_index);
+ return 0;
+}
+
+int tpm2_define_nvpcr_nv_index(
+ Tpm2Context *c,
+ const Tpm2Handle *session,
+ TPM2_HANDLE nv_index,
+ TPMI_ALG_HASH algorithm,
+ Tpm2Handle **ret_nv_handle) {
+
+ _cleanup_(tpm2_handle_freep) Tpm2Handle *new_handle = NULL;
+ TSS2_RC rc;
+ int r;
+
+ assert(c);
+
+ /* Allocates an nvindex to use as a "fake" PCR. We call these "NvPCR" in our codebase */
+
+ if (algorithm == 0)
+ algorithm = TPM2_ALG_SHA256;
+
+ int digest_size = tpm2_hash_alg_to_size(algorithm);
+ if (digest_size < 0)
+ return digest_size;
+
+ if ((size_t) digest_size > sizeof_field(TPM2B_MAX_NV_BUFFER, buffer))
+ return log_debug_errno(SYNTHETIC_ERRNO(E2BIG), "Digest too large for extension.");
+
+ r = tpm2_handle_new(c, &new_handle);
+ if (r < 0)
+ return r;
+
+ new_handle->flush = false; /* This is a persistent NV index, don't flush hence */
+
+ TPM2B_NV_PUBLIC public_info = {
+ .size = sizeof_field(TPM2B_NV_PUBLIC, nvPublic),
+ .nvPublic = {
+ .nvIndex = nv_index,
+ .nameAlg = algorithm,
+ .attributes = TPMA_NV_CLEAR_STCLEAR |
+ TPMA_NV_ORDERLY |
+ TPMA_NV_OWNERWRITE |
+ TPMA_NV_AUTHWRITE |
+ TPMA_NV_OWNERREAD |
+ TPMA_NV_AUTHREAD |
+ (TPM2_NT_EXTEND << TPMA_NV_TPM2_NT_SHIFT),
+ .dataSize = digest_size,
+ },
+ };
+
+ rc = sym_Esys_NV_DefineSpace(
+ c->esys_context,
+ /* authHandle= */ ESYS_TR_RH_OWNER,
+ /* shandle1= */ session ? session->esys_handle : ESYS_TR_PASSWORD,
+ /* shandle2= */ ESYS_TR_NONE,
+ /* shandle3= */ ESYS_TR_NONE,
+ /* auth= */ NULL,
+ &public_info,
+ &new_handle->esys_handle);
+ if (rc == TPM2_RC_NV_DEFINED) {
+ log_debug("NV index 0x%" PRIu32 " already registered.", nv_index);
+
+ new_handle = tpm2_handle_free(new_handle);
+
+ r = tpm2_index_to_handle(
+ c,
+ nv_index,
+ session,
+ /* ret_public= */ NULL,
+ /* ret_name= */ NULL,
+ /* ret_qname= */ NULL,
+ &new_handle);
+ if (r < 0)
+ return log_debug_errno(r, "Failed to acquire handle to existing NV index 0x%" PRIu32 ".", nv_index);
+
+ log_debug("Successfully acquired handle to existing NV index 0x%" PRIx32 ".", nv_index);
+
+ _cleanup_(Esys_Freep) TPM2B_NV_PUBLIC *nv_public_real = NULL;
+ rc = sym_Esys_NV_ReadPublic(
+ c->esys_context,
+ /* nvIndex= */ new_handle->esys_handle,
+ /* shandle1= */ ESYS_TR_NONE,
+ /* shandle2= */ ESYS_TR_NONE,
+ /* shandle3= */ ESYS_TR_NONE,
+ &nv_public_real,
+ /* ret_nv_name= */ NULL);
+ if (rc != TSS2_RC_SUCCESS)
+ return log_debug_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE),
+ "Failed read public data of nvindex 0x%x: %s", nv_index, sym_Tss2_RC_Decode(rc));
+
+ log_debug("Read public info for nvindex 0x%x.", nv_index);
+
+ if (nv_public_real->size < endoffsetof_field(TPMS_NV_PUBLIC, attributes) + sizeof_field(TPMS_NV_PUBLIC, dataSize) ||
+ nv_public_real->nvPublic.nvIndex != public_info.nvPublic.nvIndex ||
+ nv_public_real->nvPublic.nameAlg != public_info.nvPublic.nameAlg ||
+ ((nv_public_real->nvPublic.attributes ^ public_info.nvPublic.attributes) & ~TPMA_NV_WRITTEN) != 0 ||
+ nv_public_real->nvPublic.dataSize != public_info.nvPublic.dataSize)
+ return log_debug_errno(SYNTHETIC_ERRNO(EEXIST),
+ "Public data of nvindex 0x%x does not match our expectations.", nv_index);
+
+ log_debug("Public info for nvindex 0x%x checks out, using.", nv_index);
+
+ if (ret_nv_handle)
+ *ret_nv_handle = TAKE_PTR(new_handle);
+
+ return 0;
+ }
+ if (rc != TSS2_RC_SUCCESS)
+ return log_debug_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE),
+ "Failed to allocate NV index: %s", sym_Tss2_RC_Decode(rc));
+
+ log_debug("NV Index 0x%" PRIx32 " successfully allocated.", nv_index);
+
+ if (ret_nv_handle)
+ *ret_nv_handle = TAKE_PTR(new_handle);
+
+ return 1;
+}
+
+int tpm2_extend_nvpcr_nv_index(
+ Tpm2Context *c,
+ TPM2_HANDLE nv_index,
+ const Tpm2Handle *nv_handle,
+ const struct iovec *digest) {
+
+ TPM2_RC rc;
+
+ assert(c);
+ assert(nv_index);
+ assert(nv_handle);
+ assert(iovec_is_set(digest));
+
+ if (digest->iov_len > sizeof_field(TPM2B_MAX_NV_BUFFER, buffer))
+ return log_debug_errno(SYNTHETIC_ERRNO(E2BIG), "Hash value to extend too long.");
+
+ TPM2B_MAX_NV_BUFFER buf = {
+ .size = digest->iov_len,
+ };
+ memcpy(buf.buffer, digest->iov_base, digest->iov_len);
+
+ rc = sym_Esys_NV_Extend(
+ c->esys_context,
+ /* authHandle= */ nv_handle->esys_handle,
+ /* nvIndex= */ nv_handle->esys_handle,
+ /* shandle1= */ ESYS_TR_PASSWORD,
+ /* shandle2= */ ESYS_TR_NONE,
+ /* shandle3= */ ESYS_TR_NONE,
+ &buf);
+ if (rc != TSS2_RC_SUCCESS)
+ return log_debug_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE),
+ "Failed to extend NV index: %s", sym_Tss2_RC_Decode(rc));
+
+ if (DEBUG_LOGGING) {
+ _cleanup_free_ char *h = NULL;
+ h = hexmem(digest->iov_base, digest->iov_len);
+ log_debug("Written hash %s to NV index 0x%x", strnull(h), nv_index);
+ }
+
+ return 0;
+}
+
+int tpm2_read_nv_index(
+ Tpm2Context *c,
+ const Tpm2Handle *session,
+ TPM2_HANDLE nv_index,
+ const Tpm2Handle *nv_handle,
+ struct iovec *ret_value) {
+
+ TPM2_RC rc;
+
+ assert(c);
+ assert(nv_index);
+ assert(nv_handle);
+
+ _cleanup_(Esys_Freep) TPM2B_NV_PUBLIC *nv_public = NULL;
+ rc = sym_Esys_NV_ReadPublic(
+ c->esys_context,
+ /* nvIndex= */ nv_handle->esys_handle,
+ /* shandle1= */ ESYS_TR_NONE,
+ /* shandle2= */ ESYS_TR_NONE,
+ /* shandle3= */ ESYS_TR_NONE,
+ &nv_public,
+ /* ret_nv_name= */ NULL);
+ if (rc != TSS2_RC_SUCCESS)
+ return log_debug_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE),
+ "Failed read public data of nvindex 0x%x: %s", nv_index, sym_Tss2_RC_Decode(rc));
+
+ log_debug("Read public info for nvindex 0x%x, value size is %zu", nv_index, (size_t) nv_public->nvPublic.dataSize);
+
+ _cleanup_(Esys_Freep) TPM2B_MAX_NV_BUFFER *value = NULL;
+ rc = sym_Esys_NV_Read(
+ c->esys_context,
+ /* authHandle= */ nv_handle->esys_handle,
+ /* nvIndex= */ nv_handle->esys_handle,
+ /* shandle1= */ session ? session->esys_handle : ESYS_TR_PASSWORD,
+ /* shandle2= */ ESYS_TR_NONE,
+ /* shandle3= */ ESYS_TR_NONE,
+ nv_public->nvPublic.dataSize,
+ /* offset= */ 0,
+ &value);
+ if (rc != TSS2_RC_SUCCESS)
+ return log_debug_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE),
+ "Failed read contents of nvindex 0x%x: %s", nv_index, sym_Tss2_RC_Decode(rc));
+
+ if (ret_value) {
+ assert(value);
+
+ struct iovec result = {
+ .iov_base = memdup(value->buffer, value->size),
+ .iov_len = value->size,
+ };
+
+ if (!result.iov_base)
+ return log_oom_debug();
+
+ *ret_value = TAKE_STRUCT(result);
+ }
+
return 0;
}
@@ -6161,11 +6389,42 @@ int tpm2_find_device_auto(char **ret) {
}
#if HAVE_TPM2
+const uint16_t tpm2_hash_algorithms[] = {
+ TPM2_ALG_SHA1,
+ TPM2_ALG_SHA256,
+ TPM2_ALG_SHA384,
+ TPM2_ALG_SHA512,
+ 0,
+};
+
+assert_cc(ELEMENTSOF(tpm2_hash_algorithms) == TPM2_N_HASH_ALGORITHMS + 1);
+
+static size_t tpm2_hash_algorithm_index(uint16_t algorithm) {
+ for (size_t i = 0; i < TPM2_N_HASH_ALGORITHMS; i++)
+ if (tpm2_hash_algorithms[i] == algorithm)
+ return i;
+
+ return SIZE_MAX;
+}
+
+static int json_dispatch_tpm2_algorithm(const char *name, sd_json_variant *variant, sd_json_dispatch_flags_t flags, void *userdata) {
+ uint16_t *algorithm = ASSERT_PTR(userdata);
+ int r;
+
+ r = tpm2_hash_alg_from_string(sd_json_variant_string(variant));
+ if (r < 0 || tpm2_hash_algorithm_index(r) == SIZE_MAX)
+ return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "Invalid hash algorithm: %s", sd_json_variant_string(variant));
+
+ *algorithm = r;
+ return 0;
+}
+
static const char* tpm2_userspace_event_type_table[_TPM2_USERSPACE_EVENT_TYPE_MAX] = {
[TPM2_EVENT_PHASE] = "phase",
[TPM2_EVENT_FILESYSTEM] = "filesystem",
[TPM2_EVENT_VOLUME_KEY] = "volume-key",
[TPM2_EVENT_MACHINE_ID] = "machine-id",
+ [TPM2_EVENT_PRODUCT_ID] = "product-id",
};
DEFINE_STRING_TABLE_LOOKUP(tpm2_userspace_event_type, Tpm2UserspaceEventType);
@@ -6181,7 +6440,6 @@ const char* tpm2_firmware_log_path(void) {
#if HAVE_OPENSSL
static int tpm2_userspace_log_open(void) {
_cleanup_close_ int fd = -EBADF;
- struct stat st;
const char *e;
int r;
@@ -6198,29 +6456,61 @@ static int tpm2_userspace_log_open(void) {
if (flock(fd, LOCK_EX) < 0)
return log_debug_errno(errno, "Failed to lock TPM log file '%s', ignoring: %m", e);
- if (fstat(fd, &st) < 0)
- return log_debug_errno(errno, "Failed to fstat TPM log file '%s', ignoring: %m", e);
-
- r = stat_verify_regular(&st);
+ r = fd_verify_regular(fd);
if (r < 0)
return log_debug_errno(r, "TPM log file '%s' is not regular, ignoring: %m", e);
+ return TAKE_FD(fd);
+}
+
+static int tpm2_userspace_log_dirty(int fd) {
+ struct stat st;
+
+ if (fd < 0) /* Apparently tpm2_local_log_open() failed earlier, let's not complain again */
+ return 0;
+
/* We set the sticky bit when we are about to append to the log file. We'll unset it afterwards
* again. If we manage to take a lock on a file that has it set we know we didn't write it fully and
* it is corrupted. Ideally we'd like to use user xattrs for this, but unfortunately tmpfs (which is
* our assumed backend fs) doesn't know user xattrs. */
+
+ if (fstat(fd, &st) < 0)
+ return log_debug_errno(errno, "Failed to fstat TPM log file, ignoring: %m");
+
if (st.st_mode & S_ISVTX)
- return log_debug_errno(SYNTHETIC_ERRNO(ESTALE), "TPM log file '%s' aborted, ignoring.", e);
+ return log_debug_errno(SYNTHETIC_ERRNO(ESTALE), "TPM log file aborted, ignoring.");
if (fchmod(fd, 0600 | S_ISVTX) < 0)
- return log_debug_errno(errno, "Failed to chmod() TPM log file '%s', ignoring: %m", e);
+ return log_debug_errno(errno, "Failed to chmod() TPM log file, ignoring: %m");
- return TAKE_FD(fd);
+ return 0;
+}
+
+static int tpm2_userspace_log_clean(int fd) {
+ int r;
+
+ if (fd < 0) /* Apparently tpm2_local_log_open() failed earlier, let's not complain again */
+ return 0;
+
+ if (fsync(fd) < 0)
+ return log_debug_errno(errno, "Failed to sync JSON data: %m");
+
+ /* Unset S_ISVTX again */
+ if (fchmod(fd, 0600) < 0)
+ return log_debug_errno(errno, "Failed to chmod() TPM log file, ignoring: %m");
+
+ r = fsync_full(fd);
+ if (r < 0)
+ return log_debug_errno(r, "Failed to sync JSON log: %m");
+
+ return 0;
}
static int tpm2_userspace_log(
int fd,
unsigned pcr_index,
+ uint32_t nv_index,
+ const char *nv_index_name,
const TPML_DIGEST_VALUES *values,
Tpm2UserspaceEventType event_type,
const char *description) {
@@ -6284,10 +6574,12 @@ static int tpm2_userspace_log(
r = sd_json_buildo(
&v,
- SD_JSON_BUILD_PAIR("pcr", SD_JSON_BUILD_UNSIGNED(pcr_index)),
+ SD_JSON_BUILD_PAIR_CONDITION(pcr_index != UINT_MAX, "pcr", SD_JSON_BUILD_UNSIGNED(pcr_index)),
+ SD_JSON_BUILD_PAIR_CONDITION(nv_index != UINT32_MAX, "nv_index", SD_JSON_BUILD_UNSIGNED(nv_index)),
SD_JSON_BUILD_PAIR("digests", SD_JSON_BUILD_VARIANT(array)),
SD_JSON_BUILD_PAIR("content_type", SD_JSON_BUILD_STRING("systemd")),
SD_JSON_BUILD_PAIR("content", SD_JSON_BUILD_OBJECT(
+ SD_JSON_BUILD_PAIR_CONDITION(!!nv_index_name, "nvIndexName", SD_JSON_BUILD_STRING(nv_index_name)),
SD_JSON_BUILD_PAIR_CONDITION(!!description, "string", SD_JSON_BUILD_STRING(description)),
SD_JSON_BUILD_PAIR("bootId", SD_JSON_BUILD_ID128(boot_id)),
SD_JSON_BUILD_PAIR("timestamp", SD_JSON_BUILD_UNSIGNED(now(CLOCK_BOOTTIME))),
@@ -6306,29 +6598,20 @@ static int tpm2_userspace_log(
if (r < 0)
return log_debug_errno(r, "Failed to write JSON data to log: %m");
- if (fsync(fd) < 0)
- return log_debug_errno(errno, "Failed to sync JSON data: %m");
-
- /* Unset S_ISVTX again */
- if (fchmod(fd, 0600) < 0)
- return log_debug_errno(errno, "Failed to chmod() TPM log file, ignoring: %m");
-
- r = fsync_full(fd);
+ r = tpm2_userspace_log_clean(fd);
if (r < 0)
- return log_debug_errno(r, "Failed to sync JSON log: %m");
+ return r;
return 1;
}
#endif
-int tpm2_extend_bytes(
+int tpm2_pcr_extend_bytes(
Tpm2Context *c,
char **banks,
unsigned pcr_index,
- const void *data,
- size_t data_size,
- const void *secret,
- size_t secret_size,
+ const struct iovec *data,
+ const struct iovec *secret,
Tpm2UserspaceEventType event_type,
const char *description) {
@@ -6338,17 +6621,15 @@ int tpm2_extend_bytes(
TSS2_RC rc;
assert(c);
- assert(data || data_size == 0);
- assert(secret || secret_size == 0);
-
- if (data_size == SIZE_MAX)
- data_size = strlen(data);
- if (secret_size == SIZE_MAX)
- secret_size = strlen(secret);
+ assert(iovec_is_valid(data));
+ assert(iovec_is_valid(secret));
if (pcr_index >= TPM2_PCRS_MAX)
return log_debug_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "Can't measure into unsupported PCR %u, refusing.", pcr_index);
+ if (!iovec_is_set(data))
+ data = &iovec_empty;
+
if (strv_isempty(banks))
return 0;
@@ -6376,10 +6657,10 @@ int tpm2_extend_bytes(
* secret for other purposes, maybe because it needs a shorter secret derived from it for
* some unrelated purpose, who knows). Hence we instead measure an HMAC signature of a
* private non-secret string instead. */
- if (secret_size > 0) {
- if (!HMAC(implementation, secret, secret_size, data, data_size, (unsigned char*) &values.digests[values.count].digest, NULL))
+ if (iovec_is_set(secret) > 0) {
+ if (!HMAC(implementation, secret->iov_base, secret->iov_len, data->iov_base, data->iov_len, (unsigned char*) &values.digests[values.count].digest, NULL))
return log_debug_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), "Failed to calculate HMAC of data to measure.");
- } else if (EVP_Digest(data, data_size, (unsigned char*) &values.digests[values.count].digest, NULL, implementation, NULL) != 1)
+ } else if (EVP_Digest(data->iov_base, data->iov_len, (unsigned char*) &values.digests[values.count].digest, NULL, implementation, NULL) != 1)
return log_debug_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), "Failed to hash data to measure.");
values.count++;
@@ -6389,6 +6670,7 @@ int tpm2_extend_bytes(
* and our measurement and change either */
log_fd = tpm2_userspace_log_open();
+ (void) tpm2_userspace_log_dirty(log_fd);
rc = sym_Esys_PCR_Extend(
c->esys_context,
ESYS_TR_PCR0 + pcr_index,
@@ -6404,7 +6686,14 @@ int tpm2_extend_bytes(
sym_Tss2_RC_Decode(rc));
/* Now, write what we just extended to the log, too. */
- (void) tpm2_userspace_log(log_fd, pcr_index, &values, event_type, description);
+ (void) tpm2_userspace_log(
+ log_fd,
+ pcr_index,
+ /* nv_index= */ UINT32_MAX,
+ /* nv_index_name= */ NULL,
+ &values,
+ event_type,
+ description);
return 0;
#else /* HAVE_OPENSSL */
@@ -6412,22 +6701,729 @@ int tpm2_extend_bytes(
#endif
}
-const uint16_t tpm2_hash_algorithms[] = {
- TPM2_ALG_SHA1,
- TPM2_ALG_SHA256,
- TPM2_ALG_SHA384,
- TPM2_ALG_SHA512,
- 0,
-};
+typedef struct NvPCRData {
+ char *name;
+ uint16_t algorithm;
+ uint32_t nv_index;
+} NvPCRData;
-assert_cc(ELEMENTSOF(tpm2_hash_algorithms) == TPM2_N_HASH_ALGORITHMS + 1);
+static void nvpcr_data_done(NvPCRData *d) {
+ assert(d);
-static size_t tpm2_hash_algorithm_index(uint16_t algorithm) {
- for (size_t i = 0; i < TPM2_N_HASH_ALGORITHMS; i++)
- if (tpm2_hash_algorithms[i] == algorithm)
- return i;
+ free(d->name);
+}
- return SIZE_MAX;
+static int nvpcr_data_load(const char *name, NvPCRData *ret) {
+ int r;
+
+ assert(ret);
+
+ if (!tpm2_nvpcr_name_is_valid(name))
+ return -EINVAL;
+
+ const char *fname = strjoina(name, ".nvpcr");
+
+ _cleanup_free_ char *path = NULL;
+ _cleanup_fclose_ FILE *f = NULL;
+ r = search_and_fopen_nulstr(fname, "re", /* root= */ NULL, CONF_PATHS_NULSTR("nvpcr"), &f, &path);
+ if (r < 0)
+ return r;
+
+ _cleanup_(sd_json_variant_unrefp) sd_json_variant *v = NULL;
+ r = sd_json_parse_file(
+ f,
+ path,
+ /* flags= */ 0,
+ &v,
+ /* reterr_line= */ NULL,
+ /* reterr_column= */ NULL);
+ if (r < 0)
+ return log_debug_errno(r, "Failed to load '%s': %m", path);
+
+ static const sd_json_dispatch_field dispatch_table[] = {
+ { "name", SD_JSON_VARIANT_STRING, sd_json_dispatch_string, offsetof(NvPCRData, name), SD_JSON_MANDATORY },
+ { "algorithm", _SD_JSON_VARIANT_TYPE_INVALID, json_dispatch_tpm2_algorithm, offsetof(NvPCRData, algorithm), 0 },
+ { "nvIndex", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint32, offsetof(NvPCRData, nv_index), SD_JSON_MANDATORY },
+ {},
+ };
+
+ _cleanup_(nvpcr_data_done) NvPCRData p = {
+ .algorithm = TPM2_ALG_SHA256,
+ };
+ r = sd_json_dispatch(v, dispatch_table, SD_JSON_ALLOW_EXTENSIONS, &p);
+ if (r < 0)
+ return r;
+
+ if (!streq_ptr(p.name, name))
+ return log_debug_errno(SYNTHETIC_ERRNO(ESTALE), "NvPCR name doesn't match filename, refusing.");
+
+ *ret = TAKE_STRUCT(p);
+ return 0;
+}
+
+int tpm2_nvpcr_get_index(const char *name, uint32_t *ret) {
+ int r;
+
+ _cleanup_(nvpcr_data_done) NvPCRData p = {};
+ r = nvpcr_data_load(name, &p);
+ if (r < 0)
+ return r;
+
+ if (ret)
+ *ret = p.nv_index;
+
+ return 0;
+}
+
+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) {
+
+#if HAVE_OPENSSL
+ _cleanup_close_ int log_fd = -EBADF;
+ int r;
+
+ assert(c);
+ assert(name);
+ assert(iovec_is_valid(data));
+ assert(iovec_is_valid(secret));
+
+ _cleanup_(nvpcr_data_done) NvPCRData p = {};
+ r = nvpcr_data_load(name, &p);
+ if (r < 0)
+ return r;
+
+ /* Open + lock the log file *before* we start measuring, so that no one else can come between our log
+ * and our measurement and change either */
+ log_fd = tpm2_userspace_log_open();
+
+ /* Check if this NvPCR is already anchored */
+ const char *anchor_fname = strjoina("/run/systemd/nvpcr/", name, ".anchor");
+ if (faccessat(AT_FDCWD, anchor_fname, F_OK, AT_SYMLINK_NOFOLLOW) < 0) {
+ if (errno != ENOENT)
+ return log_debug_errno(errno, "Failed to check if '%s' exists: %m", anchor_fname);
+
+ return log_debug_errno(SYNTHETIC_ERRNO(ENETDOWN), "NvPCR '%s' not anchored yet, refusing.", name);
+ }
+
+ const char *an = tpm2_hash_alg_to_string(p.algorithm);
+ if (!an)
+ return log_debug_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "Unsupported algorithm for NvPCR, refusing.");
+
+ const EVP_MD *implementation;
+ assert_se(implementation = EVP_get_digestbyname(an));
+
+ _cleanup_(iovec_done) struct iovec digest = {
+ .iov_len = EVP_MD_size(implementation),
+ };
+
+ digest.iov_base = malloc(digest.iov_len);
+ if (!digest.iov_base)
+ return log_oom_debug();
+
+ if (!iovec_is_set(data))
+ data = &iovec_empty;
+
+ if (iovec_is_set(secret)) {
+ if (!HMAC(implementation, secret->iov_base, secret->iov_len, data->iov_base, data->iov_len, digest.iov_base, NULL))
+ return log_debug_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), "Failed to calculate HMAC of data to measure.");
+ } else if (EVP_Digest(data->iov_base, data->iov_len, digest.iov_base, NULL, implementation, NULL) != 1)
+ return log_debug_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), "Failed to hash data to measure.");
+
+ _cleanup_(tpm2_handle_freep) Tpm2Handle *nv_handle = NULL;
+ r = tpm2_index_to_handle(
+ c,
+ p.nv_index,
+ session,
+ /* ret_public= */ NULL,
+ /* ret_name= */ NULL,
+ /* ret_qname= */ NULL,
+ &nv_handle);
+ if (r < 0)
+ return log_debug_errno(r, "Failed to acquire handle to NV index 0x%" PRIu32 ".", p.nv_index);
+
+ log_debug("Successfully acquired handle to existing NV index 0x%" PRIx32 ".", p.nv_index);
+
+ (void) tpm2_userspace_log_dirty(log_fd);
+
+ r = tpm2_extend_nvpcr_nv_index(
+ c,
+ p.nv_index,
+ nv_handle,
+ &digest);
+ if (r < 0)
+ return r;
+
+ TPML_DIGEST_VALUES digest_values = {
+ .count = 1,
+ .digests[0].hashAlg = p.algorithm,
+ };
+ memcpy(&digest_values.digests[0].digest, digest.iov_base, digest.iov_len);
+
+ /* Now, write what we just extended to the log, too. */
+ (void) tpm2_userspace_log(
+ log_fd,
+ /* pcr_index= */ UINT_MAX,
+ p.nv_index,
+ name,
+ &digest_values,
+ event_type,
+ description);
+
+ return 0;
+#else /* HAVE_OPENSSL */
+ return log_debug_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "OpenSSL support is disabled.");
+#endif
+}
+
+#if HAVE_OPENSSL
+static int tpm2_nvpcr_write_anchor_secret(
+ const char *dir,
+ const char *fname,
+ const struct iovec *credential) {
+
+ int r;
+
+ assert(dir);
+ assert(fname);
+ assert(iovec_is_set(credential));
+
+ /* Writes the encrypted credential of the anchor secret to directory 'dir' and file 'fname' */
+
+ _cleanup_close_ int dfd = open_mkdir(dir, O_CLOEXEC, 0755);
+ if (dfd < 0)
+ return log_error_errno(dfd, "Failed to create '%s' directory: %m", dir);
+
+ _cleanup_free_ char *joined = path_join(dir, fname);
+ if (!joined)
+ return log_oom();
+
+ _cleanup_(iovec_done) struct iovec existing = {};
+ r = read_full_file_full(
+ dfd,
+ fname,
+ /* offset= */ UINT64_MAX,
+ CREDENTIAL_ENCRYPTED_SIZE_MAX,
+ READ_FULL_FILE_UNBASE64|READ_FULL_FILE_FAIL_WHEN_LARGER,
+ /* bind_name= */ NULL,
+ (char**) &existing.iov_base,
+ &existing.iov_len);
+ if (r < 0) {
+ if (r != -ENOENT)
+ return log_error_errno(r, "Failed to read '%s' file: %m", joined);
+ } else if (iovec_memcmp(&existing, credential) == 0) {
+ log_debug("Anchor secret file '%s' already matches expectations, not updating.", joined);
+ return 0;
+ } else
+ log_notice("Anchor secret file '%s' different from current anchor secret, updating.", joined);
+
+ r = write_base64_file_at(
+ dfd,
+ fname,
+ credential,
+ WRITE_STRING_FILE_ATOMIC|WRITE_STRING_FILE_CREATE|WRITE_STRING_FILE_SYNC);
+ if (r < 0)
+ return log_error_errno(r, "Failed to write anchor secret file to '%s': %m", joined);
+
+ log_info("Successfully written anchor secret to '%s'.", joined);
+ return 1;
+}
+
+static int tpm2_nvpcr_write_anchor_secret_to_var(const struct iovec *credential) {
+ return tpm2_nvpcr_write_anchor_secret("/var/lib/systemd/nvpcr", "nvpcr-anchor.cred", credential);
+}
+
+static int tpm2_nvpcr_write_anchor_secret_to_boot(const struct iovec *credential) {
+ int r;
+
+ assert(iovec_is_set(credential));
+
+ _cleanup_free_ char *dir = NULL;
+ r = get_global_boot_credentials_path(&dir);
+ if (r < 0)
+ return r;
+ if (r == 0) {
+ log_debug("No XBOOTLDR/ESP partition found, not writing boot anchor secret file.");
+ return 0;
+ }
+
+ sd_id128_t machine_id;
+ r = sd_id128_get_machine(&machine_id);
+ if (r < 0)
+ return log_error_errno(r, "Failed to read machine ID: %m");
+
+ BootEntryTokenType entry_token_type = BOOT_ENTRY_TOKEN_AUTO;
+ _cleanup_free_ char *entry_token = NULL;
+ r = boot_entry_token_ensure(
+ /* root= */ NULL,
+ /* conf_root= */ NULL,
+ machine_id,
+ /* machine_id_is_random = */ false,
+ &entry_token_type,
+ &entry_token);
+ if (r < 0)
+ return r;
+
+ _cleanup_free_ char *fname = strjoin("nvpcr-anchor.", entry_token, ".cred");
+ if (!fname)
+ return log_oom();
+
+ if (!filename_is_valid(fname))
+ return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Credential name '%s' would not be a valid file name, refusing.", fname);
+
+ return tpm2_nvpcr_write_anchor_secret(dir, fname, credential);
+}
+
+static int tpm2_nvpcr_acquire_anchor_secret_from_var(struct iovec *ret_credential) {
+ int r;
+
+ assert(ret_credential);
+
+ r = read_full_file_full(
+ AT_FDCWD,
+ "/var/lib/systemd/nvpcr/nvpcr-anchor.cred",
+ /* offset= */ UINT64_MAX,
+ CREDENTIAL_ENCRYPTED_SIZE_MAX,
+ READ_FULL_FILE_UNBASE64|READ_FULL_FILE_FAIL_WHEN_LARGER|READ_FULL_FILE_VERIFY_REGULAR,
+ /* bind_name= */ NULL,
+ (char**) &ret_credential->iov_base,
+ &ret_credential->iov_len);
+ if (r == -ENOENT) {
+ log_debug_errno(r, "No '/var/lib/systemd/nvpcr/nvpcr-anchor.cred' file.");
+ *ret_credential = (struct iovec) {};
+ return 0;
+ }
+ if (r < 0)
+ return log_error_errno(r, "Failed to read '/var/lib/systemd/nvpcr/nvpcr-anchor.cred': %m");
+
+ return 1;
+}
+
+static int tpm2_nvpcr_acquire_anchor_secret_from_credential(struct iovec *ret_credential, struct iovec *ret_secret) {
+ int r;
+
+ assert(ret_credential);
+ assert(ret_secret);
+
+ /* We need the anchor secret before the first measurement into an NvPCR. That means very early. Hence
+ * we'll try to pass it into the system via the system credentials logic. Because we must expect a
+ * multi-boot scenario it's hard to know which secret to use for which system. Hence we'll just try
+ * to unlock all of the available ones, until we can decrypt one of them, and then we'll use that. */
+
+ const char *dp;
+ r = get_encrypted_system_credentials_dir(&dp);
+ if (r < 0)
+ return log_error_errno(r, "Failed to get encrypted system credentials directory: %m");
+
+ /* Define early, so that it is definitely initialized, even if we take "goto not_found" branch below. */
+ _cleanup_free_ DirectoryEntries *de = NULL;
+
+ _cleanup_close_ int dfd = open(dp, O_CLOEXEC|O_DIRECTORY);
+ if (dfd < 0) {
+ if (errno == ENOENT) {
+ log_debug("No encrypted system credentials passed.");
+ goto not_found;
+ }
+
+ return log_error_errno(errno, "Failed to open system credentials directory.");
+ }
+
+ r = readdir_all(dfd, RECURSE_DIR_IGNORE_DOT, &de);
+ if (r < 0)
+ return log_error_errno(r, "Failed to enumerate system credentials: %m");
+
+ FOREACH_ARRAY(i, de->entries, de->n_entries) {
+ _cleanup_(iovec_done) struct iovec credential = {};
+ struct dirent *d = *i;
+
+ if (!startswith_no_case(d->d_name, "nvpcr-anchor.")) /* VFAT is case-insensitive, hence don't be too strict here */
+ continue;
+
+ r = read_full_file_full(
+ dfd,
+ d->d_name,
+ /* offset= */ UINT64_MAX,
+ CREDENTIAL_ENCRYPTED_SIZE_MAX,
+ READ_FULL_FILE_UNBASE64|READ_FULL_FILE_FAIL_WHEN_LARGER,
+ /* bind_name= */ NULL,
+ (char**) &credential.iov_base,
+ &credential.iov_len);
+ if (r == -ENOENT)
+ continue;
+ if (r < 0) {
+ log_warning_errno(r, "Failed to read anchor secret file '%s/%s', skipping: %m", dp, d->d_name);
+ continue;
+ }
+
+ r = decrypt_credential_and_warn(
+ "nvpcr-anchor.cred",
+ now(CLOCK_REALTIME),
+ /* tpm2_device= */ NULL,
+ /* tpm2_signature_path= */ NULL,
+ /* uid= */ UID_INVALID,
+ &credential,
+ /* flags= */ 0,
+ ret_secret);
+ if (r < 0)
+ log_debug_errno(r, "Failed to decrypt anchor secret file '%s' passed in as system credential, skipping: %m", d->d_name);
+ else {
+ *ret_credential = TAKE_STRUCT(credential);
+ return 1;
+ }
+ }
+
+ log_debug("No suitable anchor secret passed as system credential.");
+
+not_found:
+ *ret_credential = (struct iovec) {};
+ *ret_secret = (struct iovec) {};
+ return 0;
+}
+#endif
+
+#define ANCHOR_SECRET_SIZE 4096U
+
+int tpm2_nvpcr_acquire_anchor_secret(struct iovec *ret, bool sync_secondary) {
+#if HAVE_OPENSSL
+ _cleanup_close_ int fd = -EBADF;
+ int r;
+
+ /* Acquires the anchor secret. We store it in a credential. The primary location (and primary truth)
+ * for it is /run/systemd/nvpcr/ (i.e. volatile) [this file also doubles as lock file for the whole
+ * logic]. But something has to place it there once. We do keep two copies of it: one in
+ * /var/lib/systemd/nvpcr/, which is the persistent place for it, but which is only available at late
+ * boot, potentially. And one in the ESP/XBOOTLDR which will make it available in the initrd
+ * already via system credentials. */
+
+ _cleanup_close_ int dfd = open_mkdir("/run/systemd/nvpcr", O_CLOEXEC, 0755);
+ if (dfd < 0)
+ return log_error_errno(dfd, "Failed to open directory '/run/systemd/nvpcr': %m");
+
+ /* Use restrictive access mode of 0600. Not because the data inside needs to be kept inaccessible
+ * (it's encrypted, hence that'd be fine), but because we need to lock it, and unprivileged clients
+ * shouldn't be permitted to lock it. */
+ fd = openat(dfd, "nvpcr-anchor.cred", O_RDWR|O_CLOEXEC|O_CREAT|O_NOCTTY, 0644);
+ if (fd < 0)
+ return log_error_errno(errno, "Failed to open anchor secret: %m");
+
+ r = lock_generic(fd, LOCK_BSD, LOCK_SH);
+ if (r < 0)
+ return log_error_errno(r, "Failed to lock anchor secret file: %m");
+
+ struct stat st;
+ if (fstat(fd, &st) < 0)
+ return log_error_errno(errno, "Failed to stat() anchor secret: %m");
+
+ r = stat_verify_regular(&st);
+ if (r < 0)
+ return log_error_errno(r, "Anchor secret file is not a regular file: %m");
+
+ if (st.st_size == 0) {
+ /* If this is not initialized yet, then let's update the lock to an exclusive lock */
+ r = lock_generic(fd, LOCK_BSD, LOCK_EX);
+ if (r < 0)
+ return log_error_errno(r, "Failed to upgrade lock on anchor secret file: %m");
+
+ /* Refresh size info, in case someone else has initialized it by now */
+ if (fstat(fd, &st) < 0)
+ return log_error_errno(errno, "Failed to stat() anchor secret: %m");
+ }
+
+ bool copy_to_var = true, copy_to_boot = true;
+
+ _cleanup_(iovec_done) struct iovec credential = {};
+ _cleanup_(iovec_done_erase) struct iovec secret = {};
+ if (st.st_size == 0) { /* No initialized yet? */
+
+ /* Check if we have a secret in /var/lib/systemd/nvpcr/. If so, import the secret from there */
+ if (!sync_secondary) {
+ r = tpm2_nvpcr_acquire_anchor_secret_from_var(&credential);
+ if (r < 0)
+ return r;
+ if (r > 0)
+ copy_to_var = false; /* We read the secret from /var/, hence we don't have to copy it there. */
+ }
+
+ /* Did the copy_source logic work? If not, let's search for the secret among passed system credentials. */
+ if (!iovec_is_set(&credential)) {
+ r = tpm2_nvpcr_acquire_anchor_secret_from_credential(&credential, &secret);
+ if (r < 0)
+ return r;
+ if (r > 0)
+ copy_to_boot = false; /* We read the secret from the boot partition, hence we don't have to copy it there. */
+ }
+
+ /* Did the copy_source or system credential logic work? If not, let's generate a new random one */
+ if (!iovec_is_set(&credential)) {
+ r = crypto_random_bytes_allocate_iovec(ANCHOR_SECRET_SIZE, &secret);
+ if (r < 0)
+ return log_error_errno(r, "Failed to acquire entropy for anchor secret: %m");
+
+ r = encrypt_credential_and_warn(
+ _CRED_AUTO_TPM2,
+ "nvpcr-anchor.cred",
+ now(CLOCK_REALTIME),
+ /* not_after= */ USEC_INFINITY,
+ /* tpm2_device= */ NULL,
+ /* tpm2_hash_pcr_mask= */ 0,
+ /* tpm2_pubkey_path= */ NULL,
+ /* tpm2_pubkey_pcrs= */ UINT32_MAX,
+ /* uid= */ UID_INVALID,
+ &secret,
+ /* flags= */ 0,
+ &credential);
+ if (r < 0)
+ return r;
+ }
+
+ _cleanup_free_ char *encoded = NULL;
+ ssize_t n = base64mem_full(credential.iov_base, credential.iov_len, 79, &encoded);
+ if (n < 0)
+ return log_error_errno(n, "Failed to base64 encode credential: %m");
+
+ if (!strextend(&encoded, "\n"))
+ return log_oom();
+
+ n++;
+
+ r = loop_write(fd, encoded, n);
+ if (r < 0)
+ return log_error_errno(r, "Failed to write anchor secret to disk: %m");
+ } else {
+ /* The file was already initialized? Then just read it. */
+ r = read_full_file_full(
+ fd,
+ /* filename= */ NULL,
+ /* offset= */ UINT64_MAX,
+ CREDENTIAL_ENCRYPTED_SIZE_MAX,
+ READ_FULL_FILE_UNBASE64|READ_FULL_FILE_FAIL_WHEN_LARGER,
+ /* bind_name= */ NULL,
+ (char**) &credential.iov_base,
+ &credential.iov_len);
+ if (r < 0)
+ return log_error_errno(r, "Failed to read anchor secret file: %m");
+ }
+
+ /* if we don't have the plaintext secret yet, then decrypt it now. */
+ if (!iovec_is_set(&secret)) {
+ assert(iovec_is_set(&credential));
+
+ r = decrypt_credential_and_warn(
+ "nvpcr-anchor.cred",
+ now(CLOCK_REALTIME),
+ /* tpm2_device= */ NULL,
+ /* tpm2_signature_path= */ NULL,
+ /* uid= */ UID_INVALID,
+ &credential,
+ /* flags= */ 0,
+ &secret);
+ if (r < 0)
+ return r;
+ }
+
+ if (sync_secondary) {
+ if (copy_to_var) {
+ r = tpm2_nvpcr_write_anchor_secret_to_var(&credential);
+ if (r < 0)
+ return r;
+ }
+
+ if (copy_to_boot) {
+ r = tpm2_nvpcr_write_anchor_secret_to_boot(&credential);
+ if (r < 0)
+ return r;
+ }
+ }
+
+ if (ret)
+ *ret = TAKE_STRUCT(secret);
+ return 0;
+#else /* HAVE_OPENSSL */
+ return log_debug_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "OpenSSL support is disabled.");
+#endif
+}
+
+int tpm2_nvpcr_initialize(
+ Tpm2Context *c,
+ const Tpm2Handle *session,
+ const char *name,
+ const struct iovec *anchor_secret) {
+
+#if HAVE_OPENSSL
+ TPM2_RC rc;
+ int r;
+
+ assert(c);
+ assert(name);
+
+ _cleanup_(nvpcr_data_done) NvPCRData p = {};
+ r = nvpcr_data_load(name, &p);
+ if (r < 0)
+ return r;
+
+ /* Open + lock the log file *before* we check for the *.anchor flag file. */
+ _cleanup_close_ int log_fd = tpm2_userspace_log_open();
+
+ _cleanup_close_ int dfd = open_mkdir("/run/systemd/nvpcr", O_CLOEXEC, 0755);
+ if (dfd < 0)
+ return log_error_errno(dfd, "Failed to open directory '/run/systemd/nvpcr': %m");
+
+ const char *anchor_fname = strjoina(name, ".anchor");
+ if (faccessat(dfd, anchor_fname, F_OK, AT_SYMLINK_NOFOLLOW) < 0) {
+ if (errno != ENOENT)
+ return log_debug_errno(errno, "Failed to check if /run/systemd/nvpcr/%s exists: %m", anchor_fname);
+ } else {
+ log_debug("NvPCR '%s' is already anchored.", name);
+ return 0;
+ }
+
+ if (!iovec_is_set(anchor_secret))
+ return log_debug_errno(SYNTHETIC_ERRNO(EUNATCH), "Need anchor secret.");
+
+ const char *an = tpm2_hash_alg_to_string(p.algorithm);
+ if (!an)
+ return log_debug_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "Unsupported algorithm for NvPCR, refusing.");
+
+ const EVP_MD *implementation;
+ assert_se(implementation = EVP_get_digestbyname(an));
+
+ int digest_size = EVP_MD_get_size(implementation);
+ assert_se(digest_size > 0);
+
+ if ((size_t) digest_size > sizeof_field(TPM2B_MAX_NV_BUFFER, buffer))
+ return log_debug_errno(SYNTHETIC_ERRNO(E2BIG), "Hash function result too large for TPM, refusing.");
+
+ /* Put together a buffer consisting if the nvindex number and the NvPCR name, that we can calculate an HMAC() off, see below */
+ size_t hmac_buffer_size = sizeof(le32_t) + strlen(p.name);
+ _cleanup_free_ void* hmac_buffer = malloc(hmac_buffer_size);
+ if (!hmac_buffer)
+ return log_oom_debug();
+
+ *(le32_t*) hmac_buffer = htole32(p.nv_index);
+ memcpy((uint8_t*) hmac_buffer + sizeof(le32_t), name, strlen(name));
+
+ TPM2B_MAX_NV_BUFFER buf = {
+ .size = digest_size,
+ };
+ CLEANUP_ERASE(buf);
+
+ /* We measure HMAC(anchor_secret, name) into the NvPCR to anchor it on our secret. */
+ if (!HMAC(implementation, anchor_secret->iov_base, anchor_secret->iov_len, hmac_buffer, hmac_buffer_size, buf.buffer, NULL))
+ return log_debug_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), "Failed to calculate HMAC of data to measure.");
+
+ _cleanup_(tpm2_handle_freep) Tpm2Handle *nv_handle = NULL;
+ r = tpm2_define_nvpcr_nv_index(
+ c,
+ session,
+ p.nv_index,
+ p.algorithm,
+ &nv_handle);
+ if (r < 0)
+ return r;
+
+ log_debug("Successfully acquired handle to NV index 0x%" PRIx32 ".", p.nv_index);
+
+ tpm2_userspace_log_dirty(log_fd);
+ rc = sym_Esys_NV_Extend(
+ c->esys_context,
+ /* authHandle= */ nv_handle->esys_handle,
+ /* nvIndex= */ nv_handle->esys_handle,
+ /* shandle1= */ ESYS_TR_PASSWORD,
+ /* shandle2= */ ESYS_TR_NONE,
+ /* shandle3= */ ESYS_TR_NONE,
+ &buf);
+ if (rc != TSS2_RC_SUCCESS)
+ return log_debug_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE),
+ "Failed to extend NV index: %s", sym_Tss2_RC_Decode(rc));
+
+ log_debug("Successfully extended NvPCR '%s' with anchor secret.", name);
+
+ /* Now pre-calculate the initial measurement of an "anchor" secret. This makes sure that others
+ * cannot delete and reproduce the same fake PCR, unless they also know the "anchor" secret. */
+ TPM2B_DIGEST start = { /* initialize to zero */
+ .size = digest_size,
+ };
+ r = tpm2_digest_buffer(
+ p.algorithm,
+ &start,
+ buf.buffer,
+ buf.size,
+ /* extend= */ true);
+ if (r < 0)
+ return log_debug_errno(r, "Failed to calculate initial value: %m");
+
+ /* Now create the anchor flag file */
+ _cleanup_free_ char *h = hexmem(start.buffer, start.size);
+ if (!h)
+ return log_oom_debug();
+
+ r = write_string_file_at(dfd, anchor_fname, h, WRITE_STRING_FILE_CREATE|WRITE_STRING_FILE_ATOMIC);
+ if (r < 0)
+ return log_debug_errno(r, "Failed to write anchor file: %m");
+
+ tpm2_userspace_log_clean(log_fd);
+ return 1;
+#else /* HAVE_OPENSSL */
+ return log_debug_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "OpenSSL support is disabled.");
+#endif
+}
+
+int tpm2_nvpcr_read(
+ Tpm2Context *c,
+ const Tpm2Handle *session,
+ const char *name,
+ struct iovec *ret_value,
+ uint32_t *ret_nv_index) {
+
+#if HAVE_OPENSSL
+ int r;
+
+ assert(c);
+ assert(name);
+
+ if (!tpm2_nvpcr_name_is_valid(name))
+ return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "Attempt to read from NvPCR with invalid name, refusing: %s", name);
+
+ _cleanup_(nvpcr_data_done) NvPCRData p = {};
+ r = nvpcr_data_load(name, &p);
+ if (r < 0)
+ return r;
+
+ _cleanup_(tpm2_handle_freep) Tpm2Handle *nv_handle = NULL;
+ r = tpm2_index_to_handle(
+ c,
+ p.nv_index,
+ session,
+ /* ret_public= */ NULL,
+ /* ret_name= */ NULL,
+ /* ret_qname= */ NULL,
+ &nv_handle);
+ if (r < 0)
+ return log_debug_errno(r, "Failed to acquire handle to NV index 0x%" PRIu32 ".", p.nv_index);
+
+ log_debug("Successfully acquired handle to NV index 0x%" PRIx32 ".", p.nv_index);
+
+ r = tpm2_read_nv_index(
+ c,
+ /* session= */ NULL,
+ p.nv_index,
+ nv_handle,
+ ret_value);
+ if (r < 0)
+ return r;
+
+ if (ret_nv_index)
+ *ret_nv_index = p.nv_index;
+
+ return 0;
+#else /* HAVE_OPENSSL */
+ return log_debug_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "OpenSSL support is disabled.");
+#endif
}
TPM2B_DIGEST *tpm2_pcr_prediction_result_get_hash(Tpm2PCRPredictionResult *result, uint16_t alg) {
@@ -6907,18 +7903,6 @@ void tpm2_pcrlock_policy_done(Tpm2PCRLockPolicy *data) {
iovec_done(&data->pin_private);
}
-static int json_dispatch_tpm2_algorithm(const char *name, sd_json_variant *variant, sd_json_dispatch_flags_t flags, void *userdata) {
- uint16_t *algorithm = ASSERT_PTR(userdata);
- int r;
-
- r = tpm2_hash_alg_from_string(sd_json_variant_string(variant));
- if (r < 0 || tpm2_hash_algorithm_index(r) == SIZE_MAX)
- return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid hash algorithm: %s", sd_json_variant_string(variant));
-
- *algorithm = r;
- return 0;
-}
-
int tpm2_pcrlock_search_file(const char *path, FILE **ret_file, char **ret_path) {
static const char search[] =
"/run/systemd\0"
@@ -7065,9 +8049,12 @@ int tpm2_pcrlock_policy_from_credentials(
* multi-boot), hence we use the SRK and NV data from the LUKS2 header as search key, and parse all
* such JSON policies until we find a matching one. */
- const char *cp = secure_getenv("SYSTEMD_ENCRYPTED_SYSTEM_CREDENTIALS_DIRECTORY") ?: ENCRYPTED_SYSTEM_CREDENTIALS_DIRECTORY;
+ const char *dp;
+ r = get_encrypted_system_credentials_dir(&dp);
+ if (r < 0)
+ return log_error_errno(r, "Failed to get encrypted system credentials directory: %m");
- dfd = open(cp, O_CLOEXEC|O_DIRECTORY);
+ dfd = open(dp, O_CLOEXEC|O_DIRECTORY);
if (dfd < 0) {
if (errno == ENOENT) {
log_debug("No encrypted system credentials passed.");
@@ -7100,7 +8087,7 @@ int tpm2_pcrlock_policy_from_credentials(
if (r == -ENOENT)
continue;
if (r < 0) {
- log_warning_errno(r, "Failed to read credentials file %s/%s, skipping: %m", ENCRYPTED_SYSTEM_CREDENTIALS_DIRECTORY, d->d_name);
+ log_warning_errno(r, "Failed to read credentials file %s/%s, skipping: %m", dp, d->d_name);
continue;
}
@@ -8141,3 +9128,9 @@ static const char* const tpm2_pcr_index_table[_TPM2_PCR_INDEX_MAX_DEFINED] = {
DEFINE_STRING_TABLE_LOOKUP_FROM_STRING_WITH_FALLBACK(tpm2_pcr_index, int, TPM2_PCRS_MAX - 1);
DEFINE_STRING_TABLE_LOOKUP_TO_STRING(tpm2_pcr_index, int);
+
+bool tpm2_nvpcr_name_is_valid(const char *name) {
+ return filename_is_valid(name) &&
+ string_is_safe(name) &&
+ tpm2_pcr_index_from_string(name) < 0; /* don't allow nvpcrs to be name like pcrs */
+}
diff --git a/src/shared/tpm2-util.h b/src/shared/tpm2-util.h
index b051c8435e..abf8adfdaf 100644
--- a/src/shared/tpm2-util.h
+++ b/src/shared/tpm2-util.h
@@ -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);
diff --git a/src/shared/varlink-io.systemd.PCRExtend.c b/src/shared/varlink-io.systemd.PCRExtend.c
index d104fc0853..84ee2e731b 100644
--- a/src/shared/varlink-io.systemd.PCRExtend.c
+++ b/src/shared/varlink-io.systemd.PCRExtend.c
@@ -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);
diff --git a/src/systemd/sd-messages.h b/src/systemd/sd-messages.h
index 02edf9facf..6dc7dd527a 100644
--- a/src/systemd/sd-messages.h
+++ b/src/systemd/sd-messages.h
@@ -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)
diff --git a/src/tpm2-setup/meson.build b/src/tpm2-setup/meson.build
index 39fc97b91e..9814fe1005 100644
--- a/src/tpm2-setup/meson.build
+++ b/src/tpm2-setup/meson.build
@@ -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
diff --git a/src/tpm2-setup/nvpcr/cryptsetup.nvpcr.in b/src/tpm2-setup/nvpcr/cryptsetup.nvpcr.in
new file mode 100644
index 0000000000..acec95206f
--- /dev/null
+++ b/src/tpm2-setup/nvpcr/cryptsetup.nvpcr.in
@@ -0,0 +1,5 @@
+{
+ "name" : "cryptsetup",
+ "algorithm" : "sha256",
+ "nvIndex" : {{TPM2_NVPCR_BASE + 1}}
+}
diff --git a/src/tpm2-setup/nvpcr/hardware.nvpcr.in b/src/tpm2-setup/nvpcr/hardware.nvpcr.in
new file mode 100644
index 0000000000..ddeb7f99e5
--- /dev/null
+++ b/src/tpm2-setup/nvpcr/hardware.nvpcr.in
@@ -0,0 +1,5 @@
+{
+ "name" : "hardware",
+ "algorithm" : "sha256",
+ "nvIndex" : {{TPM2_NVPCR_BASE + 0}}
+}
diff --git a/src/tpm2-setup/tpm2-setup.c b/src/tpm2-setup/tpm2-setup.c
index 032b1e3381..baa485debe 100644
--- a/src/tpm2-setup/tpm2-setup.c
+++ b/src/tpm2-setup/tpm2-setup.c
@@ -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);
diff --git a/test/units/TEST-65-ANALYZE.sh b/test/units/TEST-65-ANALYZE.sh
index d71b76e92a..f368d21653 100755
--- a/test/units/TEST-65-ANALYZE.sh
+++ b/test/units/TEST-65-ANALYZE.sh
@@ -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
diff --git a/test/units/TEST-70-TPM2.pcrextend.sh b/test/units/TEST-70-TPM2.pcrextend.sh
index 318fce02a1..e5cd4843d6 100755
--- a/test/units/TEST-70-TPM2.pcrextend.sh
+++ b/test/units/TEST-70-TPM2.pcrextend.sh
@@ -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 <