diff --git a/catalog/systemd.catalog.in b/catalog/systemd.catalog.in
index cb1142b194..4152112078 100644
--- a/catalog/systemd.catalog.in
+++ b/catalog/systemd.catalog.in
@@ -743,6 +743,19 @@ 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
Defined-By: systemd
diff --git a/man/systemd-pcrphase.service.xml b/man/systemd-pcrphase.service.xml
index e301435f8e..a00eda08bb 100644
--- a/man/systemd-pcrphase.service.xml
+++ b/man/systemd-pcrphase.service.xml
@@ -138,7 +138,8 @@
OptionsThe /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 +156,22 @@
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 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. May not be combined with .
+
+
+
+
@@ -182,6 +194,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.)
+
+
+
+
@@ -230,6 +253,13 @@
+
+
+ /usr/lib/nvpcr/*.nvpcr
+
+ Definition files for NvPCRs.
+
+
diff --git a/src/pcrextend/pcrextend.c b/src/pcrextend/pcrextend.c
index cc50ad3ff7..68d90d4e4a 100644
--- a/src/pcrextend/pcrextend.c
+++ b/src/pcrextend/pcrextend.c
@@ -28,11 +28,14 @@ static char **arg_banks = NULL;
static char *arg_file_system = NULL;
static bool arg_machine_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
@@ -53,10 +56,12 @@ static int help(int argc, char *argv[], void *userdata) {
" --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"
+ " --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 +78,12 @@ 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_EARLY,
};
static const struct option options[] = {
@@ -84,10 +91,12 @@ 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 },
+ { "early", no_argument, NULL, ARG_EARLY },
{}
};
@@ -127,6 +136,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 +176,10 @@ static int parse_argv(int argc, char *argv[]) {
arg_machine_id = true;
break;
+ case ARG_EARLY:
+ arg_early = true;
+ break;
+
case '?':
return -EINVAL;
@@ -165,15 +187,18 @@ 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 > 1)
+ return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "--file-system=, --machine-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)
+ 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 */
TPM2_PCR_KERNEL_BOOT; /* → PCR 11 */
@@ -198,7 +223,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,18 +270,8 @@ 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);
@@ -247,8 +289,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 +351,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 +368,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;
@@ -354,7 +463,6 @@ static int run(int argc, char *argv[]) {
return r;
event = TPM2_EVENT_MACHINE_ID;
-
} else {
if (optind+1 != argc)
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Expected a single argument.");
@@ -386,9 +494,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/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)