verity: measure all root hashes as we activate to a new NvPCR (#40648)

This commit is contained in:
Lennart Poettering
2026-02-17 23:44:46 +01:00
committed by GitHub
22 changed files with 458 additions and 53 deletions
+3 -19
View File
@@ -720,10 +720,6 @@ Features:
deleting entries for rotation, place an event that declares how many items
have been dropped, and what the hash before and after that.
* measure information about all DDIs as we activate them to an NvPCR. We
probably should measure the dm-verity root hash from the kernel side, but
DDI meta info from userspace.
* use name_to_handle_at() with AT_HANDLE_FID instead of .st_ino (inode
number) for identifying inodes, for example in copy.c when finding hard
links, or loop-util.c for tracking backing files, and other places.
@@ -1299,9 +1295,9 @@ Features:
- If run on every boot, should it use the sysupdate config from the host on
subsequent boots?
* To mimic the new tpm2-measure-pcr= crypttab option add the same to veritytab
(measuring the root hash) and integritytab (measuring the HMAC key if one is
used)
* To mimic the new tpm2-measure-pcr= crypttab option and tpm2-measure-nvpcr=
veritytab option, add the same to integritytab (measuring the HMAC key if one
is used)
* We should start measuring all services, containers, and system extensions we
activate. probably into PCR 13. i.e. add --tpm2-measure-pcr= or so to
@@ -1720,18 +1716,6 @@ Features:
keys of /etc/crypttab. That way people can store/provide the roothash
externally and provide to us on demand only.
* we probably should extend the root verity hash of the root fs into some PCR
on boot. (i.e. maybe add a veritytab option tpm2-measure=12 or so to measure
it into PCR 12); Similar: we probably should extend the LUKS volume key of
the root fs into some PCR on boot. (i.e. maybe add a crypttab option
tpm2-measure=15 or so to measure it into PCR 15); once both are in place
update gpt-auto-discovery to generate these by default for the partitions it
discovers. Static vendor stuff should probably end up in PCR 12 (i.e. the
verity hash), with local keys in PCR 15 (i.e. the encryption volume
key). That way, we nicely distinguish resources supplied by the OS vendor
(i.e. sysext, root verity) from those inherently local (i.e. encryption key),
which is useful if they shall be signed separately.
* rework recursive read-only remount to use new mount API
* when mounting disk images: if IMAGE_ID/IMAGE_VERSION is set in os-release
+6 -6
View File
@@ -945,12 +945,12 @@
<term><option>tpm2-measure-keyslot-nvpcr=</option></term>
<listitem><para>Controls whether to measure information about the used LUKS unlock keyslot to a TPM2
non-volatile index (nvindex in PCR mode). If set to to an empty string (which is the default) no TPM2
nvindex extension is done, otherwise keyslot information is measured to an nvindex of the specified
name, which is allocated if needed. It is recommended to set this to <literal>cryptsetup</literal> to
enable this logic. The slot index and the used unlock mechanism (i.e. <literal>tpm2</literal>,
<literal>fido2</literal>, <literal>pkcs11</literal>) is measured along with the activated volume name
and its UUID.</para>
non-volatile index (nvindex in PCR mode). Takes a boolean argument, or an NvPCR name. If set to false
or 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. If set to
true the recommended default of <literal>cryptsetup</literal> is selected as NvPCR. The slot index
and the used unlock mechanism (i.e. <literal>tpm2</literal>, <literal>fido2</literal>,
<literal>pkcs11</literal>) are measured along with the activated volume name and its UUID.</para>
<xi:include href="version-info.xml" xpointer="v259"/></listitem>
</varlistentry>
+7 -3
View File
@@ -272,9 +272,13 @@
<para>If the system was booted via
<citerefentry><refentrytitle>systemd-stub</refentrytitle><manvolnum>7</manvolnum></citerefentry> and the
stub reported to userspace that the kernel image was measured to a TPM2 PCR, then any discovered root and
<filename>/var/</filename> volume identifiers (and volume encryption key in case it is encrypted) will be
automatically measured into PCR 15 on activation, via
<citerefentry><refentrytitle>systemd-pcrfs@.service</refentrytitle><manvolnum>8</manvolnum></citerefentry>.</para>
<filename>/var/</filename> volume identifiers (and volume encryption keys, in case they are encrypted)
will be automatically measured into PCR 15 on activation, via
<citerefentry><refentrytitle>systemd-pcrfs@.service</refentrytitle><manvolnum>8</manvolnum></citerefentry>. Moreover,
information about the LUKS key slot used to unlock the volume is measured into NvPCR
<literal>cryptsetup</literal>. Finally, if the root or <filename>/usr/</filename> partition is protected
via Verity its root hash and the serial/issuer of the key used for the provided root hash signature (if
any) are measured into the NvPCR <literal>verity</literal>.</para>
<para>Mount constraint metadata contained in the file systems is validated by pulling in
<citerefentry><refentrytitle>systemd-validatefs@.service</refentrytitle><manvolnum>8</manvolnum></citerefentry>
+12
View File
@@ -298,6 +298,18 @@ This is based on crypttab(5).
</listitem>
</varlistentry>
<varlistentry>
<term><option>tpm2-measure-nvpcr=</option></term>
<listitem><para>Takes a boolean argument, or an NvPCR name as argument. May be used to enable
automatic measurement of the volume name and Verity root hash, as well as the serials and issuers of
the certificates used to generate the provided signatures (if any) will be measured. Passing false
disables the mechanism. Passing true enables measurement into the <literal>verity</literal> NvPCR. If
any other string is specified this selects the NvPCR to measure into by name.</para>
<xi:include href="version-info.xml" xpointer="v260"/></listitem>
</varlistentry>
</variablelist>
<para>At early boot and when the system manager configuration is
+8 -5
View File
@@ -557,12 +557,14 @@ static int parse_one_option(const char *option) {
} else if ((val = startswith(option, "tpm2-measure-keyslot-nvpcr="))) {
if (isempty(val)) {
r = isempty(val) ? false : parse_boolean(val);
if (r == 0) {
arg_tpm2_measure_keyslot_nvpcr = mfree(arg_tpm2_measure_keyslot_nvpcr);
return 0;
}
if (!tpm2_nvpcr_name_is_valid(val)) {
if (r > 0)
val = "cryptsetup";
else if (!tpm2_nvpcr_name_is_valid(val)) {
log_warning("Invalid NvPCR name, ignoring: %s", option);
return 0;
}
@@ -1095,8 +1097,9 @@ static int measure_keyslot(
const char *mechanism,
int keyslot) {
#if HAVE_TPM2
int r;
#endif
assert(cd);
assert(name);
@@ -1105,6 +1108,7 @@ static int measure_keyslot(
return 0;
}
#if HAVE_TPM2
r = efi_measured_uki(LOG_WARNING);
if (r < 0)
return r;
@@ -1113,7 +1117,6 @@ static int measure_keyslot(
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)
+21 -5
View File
@@ -127,7 +127,7 @@ static int add_cryptsetup(
* assignment, under the assumption that people who are fine to use sd-stub with its PCR
* assignments are also OK with our PCR 15 use here. */
if (r > 0)
if (!strextend_with_separator(&options, ",", "tpm2-measure-pcr=yes,tpm2-measure-keyslot-nvpcr=cryptsetup"))
if (!strextend_with_separator(&options, ",", "tpm2-measure-pcr=yes,tpm2-measure-keyslot-nvpcr=yes"))
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);
@@ -190,7 +190,8 @@ static int add_veritysetup(
const char *id,
const char *data_what,
const char *hash_what,
const char *mount_opts) {
const char *mount_opts,
MountPointFlags flags) {
#if HAVE_LIBCRYPTSETUP
int r;
@@ -233,13 +234,26 @@ static int add_veritysetup(
"After=%1$s %2$s\n",
dd, dh);
_cleanup_free_ char *options =
strdup("root-hash-signature=auto"); /* auto means: derive signature from udev property ID_DISSECT_PART_ROOTHASH_SIG */
if (!options)
return log_oom();
if (FLAGS_SET(flags, MOUNT_MEASURE)) {
r = efi_measured_uki(LOG_WARNING);
if (r > 0 && !strextend_with_separator(&options, ",", "tpm2-measure-nvpcr=yes"))
return log_oom();
if (r == 0)
log_debug("Will not measure root hash/signature of volume '%s', not booted via systemd-stub with measurements enabled.", id);
}
r = generator_write_veritysetup_service_section(
f,
id,
data_what,
hash_what,
/* roothash= */ NULL, /* NULL means: derive root hash from udev property ID_DISSECT_PART_ROOTHASH */
"root-hash-signature=auto"); /* auto means: derive signature from udev property ID_DISSECT_PART_ROOTHASH_SIG */
options);
if (r < 0)
return r;
@@ -871,7 +885,8 @@ static int add_root_mount(void) {
"root",
"/dev/disk/by-designator/root-verity-data",
"/dev/disk/by-designator/root-verity",
arg_root_options);
arg_root_options,
MOUNT_MEASURE);
if (r < 0)
return r;
}
@@ -952,7 +967,8 @@ static int add_usr_mount(void) {
"usr",
"/dev/disk/by-designator/usr-verity-data",
"/dev/disk/by-designator/usr-verity",
arg_usr_options);
arg_usr_options,
MOUNT_MEASURE);
if (r < 0)
return r;
}
+11 -6
View File
@@ -369,6 +369,7 @@ typedef struct MethodExtendParameters {
const char *nvpcr;
const char *text;
struct iovec data;
Tpm2UserspaceEventType event_type;
} MethodExtendParameters;
static void method_extend_parameters_done(MethodExtendParameters *p) {
@@ -377,17 +378,21 @@ static void method_extend_parameters_done(MethodExtendParameters *p) {
iovec_done(&p->data);
}
static JSON_DISPATCH_ENUM_DEFINE(json_dispatch_tpm2_userspace_event_type, Tpm2UserspaceEventType, tpm2_userspace_event_type_from_string);
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), 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 },
{ "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 },
{ "eventType", SD_JSON_VARIANT_STRING, json_dispatch_tpm2_userspace_event_type, offsetof(MethodExtendParameters, event_type), 0 },
{}
};
_cleanup_(method_extend_parameters_done) MethodExtendParameters p = {
.pcr = UINT_MAX,
.event_type = _TPM2_USERSPACE_EVENT_TYPE_INVALID,
};
int r;
@@ -424,11 +429,11 @@ static int vl_method_extend(sd_varlink *link, sd_json_variant *parameters, sd_va
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);
r = extend_nvpcr_now(p.nvpcr, extend_iovec->iov_base, extend_iovec->iov_len, p.event_type);
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);
r = extend_pcr_now(p.pcr, extend_iovec->iov_base, extend_iovec->iov_len, p.event_type);
if (r < 0)
return r;
+18 -3
View File
@@ -60,6 +60,7 @@
#include "openssl-util.h"
#include "os-util.h"
#include "path-util.h"
#include "pcrextend-util.h"
#include "pidref.h"
#include "proc-cmdline.h"
#include "process-util.h"
@@ -3219,13 +3220,13 @@ static int do_crypt_activate_verity(
DissectImageFlags flags,
PartitionPolicyFlags policy_flags) {
bool check_signature;
int r, k;
int r;
assert(cd);
assert(name);
assert(verity);
bool check_signature;
if (iovec_is_set(&verity->root_hash_sig) && FLAGS_SET(policy_flags, PARTITION_POLICY_SIGNED)) {
r = secure_getenv_bool("SYSTEMD_DISSECT_VERITY_SIGNATURE");
if (r < 0 && r != -ENXIO)
@@ -3235,7 +3236,10 @@ static int do_crypt_activate_verity(
} else
check_signature = false;
bool measure_signature;
if (check_signature) {
int k;
/* First, if we have support for signed keys in the kernel, then try that first. */
r = sym_crypt_activate_by_signed_key(
cd,
@@ -3247,7 +3251,8 @@ static int do_crypt_activate_verity(
CRYPT_ACTIVATE_READONLY);
if (r >= 0) {
log_debug("Verity activation via kernel signature logic worked.");
return 0;
measure_signature = true;
goto done;
}
log_debug_errno(r, "Validation of dm-verity signature failed via the kernel, trying userspace validation instead: %m");
@@ -3280,9 +3285,13 @@ static int do_crypt_activate_verity(
/* Otherwise let's see what signature-less activation results in. */
measure_signature = true;
} else if (!FLAGS_SET(policy_flags, PARTITION_POLICY_VERITY))
return log_debug_errno(SYNTHETIC_ERRNO(ERFKILL),
"No-signature activation of Verity volume not allowed by policy, refusing.");
else
measure_signature = false;
r = sym_crypt_activate_by_volume_key(
cd,
@@ -3294,6 +3303,12 @@ static int do_crypt_activate_verity(
return log_debug_errno(r, "Activation of Verity via root hash failed: %m");
log_debug("Activation of Verity via root hash succeeded.");
done:
(void) pcrextend_verity_now(
name,
&verity->root_hash,
measure_signature ? &verity->root_hash_sig : NULL);
return 0;
}
+1
View File
@@ -152,6 +152,7 @@ shared_sources = files(
'pcre2-util.c',
'pcrextend-util.c',
'pe-binary.c',
'pkcs7-util.c',
'pkcs11-util.c',
'plymouth-util.c',
'polkit-agent.c',
+111
View File
@@ -2,6 +2,7 @@
#include "sd-device.h"
#include "sd-id128.h"
#include "sd-varlink.h"
#include "alloc-util.h"
#include "blkid-util.h"
@@ -10,10 +11,13 @@
#include "errno-util.h"
#include "escape.h"
#include "fd-util.h"
#include "hexdecoct.h"
#include "id128-util.h"
#include "iovec-util.h"
#include "log.h"
#include "mountpoint-util.h"
#include "pcrextend-util.h"
#include "pkcs7-util.h"
#include "string-util.h"
#include "strv.h"
@@ -180,3 +184,110 @@ int pcrextend_product_id_word(char **ret) {
*ret = TAKE_PTR(word);
return 0;
}
int pcrextend_verity_word(
const char *name,
const struct iovec *root_hash,
const struct iovec *root_hash_sig,
char **ret) {
int r;
assert(name);
assert(iovec_is_set(root_hash));
_cleanup_free_ char *name_escaped = xescape(name, ":"); /* Avoid ambiguity around ":" */
if (!name_escaped)
return log_oom();
_cleanup_free_ char *h = hexmem(root_hash->iov_base, root_hash->iov_len);
if (!h)
return log_oom();
_cleanup_free_ char *sigs = NULL;
if (iovec_is_set(root_hash_sig)) {
size_t n_signers = 0;
Signer *signers = NULL;
/* Let's extract the X.509 issuer + serial number from the PKCS#7 signature and include that
* in the measurement record. This is useful since it allows us to have different signing
* keys for confext + sysext + other types of DDIs, and by means of this information we can
* discern which kind it was. Ideally, we'd measure the fingerprint of the X.509 certificate,
* but typically that's not available in a PKCS#7 signature. */
CLEANUP_ARRAY(signers, n_signers, signer_free_many);
r = pkcs7_extract_signers(root_hash_sig, &signers, &n_signers);
if (r < 0)
return r;
FOREACH_ARRAY(i, signers, n_signers) {
_cleanup_free_ char *serial = hexmem(i->serial.iov_base, i->serial.iov_len);
if (!serial)
return log_oom();
_cleanup_free_ char *issuer = NULL;
if (base64mem(i->issuer.iov_base, i->issuer.iov_len, &issuer) < 0)
return log_oom();
if (strextendf_with_separator(&sigs, ",", "%s/%s", serial, issuer) < 0)
return log_oom();
}
}
_cleanup_free_ char *word = strjoin("verity:", name_escaped, ":", h, ":", strempty(sigs));
if (!word)
return log_oom();
*ret = TAKE_PTR(word);
return 0;
}
int pcrextend_verity_now(
const char *name,
const struct iovec *root_hash,
const struct iovec *root_hash_sig) {
#if HAVE_TPM2
int r;
_cleanup_free_ char *word = NULL;
r = pcrextend_verity_word(
name,
root_hash,
root_hash_sig,
&word);
if (r < 0)
return r;
_cleanup_free_ sd_varlink *vl = NULL;
r = sd_varlink_connect_address(&vl, "/run/systemd/io.systemd.PCRExtend");
if (r < 0)
return r;
_cleanup_(sd_json_variant_unrefp) sd_json_variant *reply = NULL;
const char *error_id = NULL;
r = sd_varlink_callbo(
vl,
"io.systemd.PCRExtend.Extend",
/* ret_reply= */ NULL,
&error_id,
SD_JSON_BUILD_PAIR_STRING("nvpcr", "verity"),
SD_JSON_BUILD_PAIR_STRING("text", word),
SD_JSON_BUILD_PAIR_STRING("eventType", "dm_verity"));
if (r < 0)
return log_debug_errno(r, "Failed to issue io.systemd.PCRExtend.Extend() varlink call: %m");
if (error_id) {
r = sd_varlink_error_to_errno(error_id, reply);
if (r != -EBADR)
return log_debug_errno(r, "Failed to issue io.systemd.PCRExtend.Extend() varlink call: %m");
return log_debug_errno(r, "Failed to issue io.systemd.PCRExtend.Extend() varlink call: %s", error_id);
}
log_debug("Measurement of '%s' into 'images' NvPCR completed.", word);
return 1;
#else
return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "TPM2 support disabled, not measuring Verity root hashes and signatures.");
#endif
}
+3
View File
@@ -4,3 +4,6 @@
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);
int pcrextend_verity_word(const char *name, const struct iovec *root_hash, const struct iovec *root_hash_sig, char **ret);
int pcrextend_verity_now(const char *name, const struct iovec *root_hash,const struct iovec *root_hash_sig);
+84
View File
@@ -0,0 +1,84 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#include "alloc-util.h"
#include "openssl-util.h"
#include "pkcs7-util.h"
#include "log.h"
#define SIGNERS_MAX 32
static void signer_done(Signer *signer) {
assert(signer);
iovec_done(&signer->issuer);
iovec_done(&signer->serial);
}
void signer_free_many(Signer *signers, size_t n) {
assert(signers || n == 0);
FOREACH_ARRAY(i, signers, n)
signer_done(i);
free(signers);
}
int pkcs7_extract_signers(
const struct iovec *sig,
Signer **ret_signers,
size_t *ret_n_signers) {
assert(ret_signers);
assert(ret_n_signers);
if (!iovec_is_set(sig))
return -EBADMSG;
#if HAVE_OPENSSL
const unsigned char *d = sig->iov_base;
_cleanup_(PKCS7_freep) PKCS7 *p7 = NULL;
p7 = d2i_PKCS7(/* a= */ NULL, &d, (long) sig->iov_len);
if (!p7)
return log_debug_errno(SYNTHETIC_ERRNO(EBADMSG), "Failed to parse PKCS7 DER signature data.");
STACK_OF(PKCS7_SIGNER_INFO) *sinfos = PKCS7_get_signer_info(p7);
if (!sinfos)
return log_debug_errno(SYNTHETIC_ERRNO(ENODATA), "No signature information in PKCS7 signature?");
int n = sk_PKCS7_SIGNER_INFO_num(sinfos);
if (n == 0)
return log_debug_errno(SYNTHETIC_ERRNO(ENODATA), "No signatures in PKCS7 signature, refusing.");
if (n > SIGNERS_MAX) /* safety net, in case people send us weirdly complex signatures */
return log_debug_errno(SYNTHETIC_ERRNO(EBADMSG), "Too many signatures, refusing.");
assert(n > 0);
size_t n_signers = 0;
Signer *signers = new(Signer, n);
if (!signers)
return log_oom_debug();
CLEANUP_ARRAY(signers, n_signers, signer_free_many);
for (int i = 0; i < n; i++) {
PKCS7_SIGNER_INFO *si = sk_PKCS7_SIGNER_INFO_value(PKCS7_get_signer_info(p7), i);
if (!si)
return log_debug_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), "Failed to get signer information.");
_cleanup_(signer_done) Signer signer = {};
_cleanup_free_ unsigned char *p = NULL;
int len = i2d_X509_NAME(si->issuer_and_serial->issuer, &p);
signer.issuer = IOVEC_MAKE(TAKE_PTR(p), len);
len = i2d_ASN1_INTEGER(si->issuer_and_serial->serial, &p);
signer.serial = IOVEC_MAKE(TAKE_PTR(p), len);
signers[n_signers++] = TAKE_STRUCT(signer);
}
*ret_signers = TAKE_PTR(signers);
*ret_n_signers = n_signers;
return n;
#else
return -EOPNOTSUPP;
#endif
}
+16
View File
@@ -0,0 +1,16 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#pragma once
#include <sys/uio.h>
typedef struct Signer {
struct iovec issuer;
struct iovec serial;
} Signer;
void signer_free_many(Signer *signers, size_t n);
int pkcs7_extract_signers(
const struct iovec *sig,
Signer **ret_signers,
size_t *ret_n_signers);
+2 -1
View File
@@ -5441,7 +5441,7 @@ int tpm2_seal(Tpm2Context *c,
seal_key_handle);
primary_alg = primary_public->publicArea.type;
/* Propagate fixedTPM/fixedParent flags from sealing key to hmac key */
hmac_template.objectAttributes = (hmac_template.objectAttributes & ~(TPMA_OBJECT_FIXEDTPM|TPMA_OBJECT_FIXEDPARENT)) |
(primary_public->publicArea.objectAttributes & (TPMA_OBJECT_FIXEDTPM|TPMA_OBJECT_FIXEDPARENT));
@@ -6478,6 +6478,7 @@ static const char* tpm2_userspace_event_type_table[_TPM2_USERSPACE_EVENT_TYPE_MA
[TPM2_EVENT_KEYSLOT] = "keyslot",
[TPM2_EVENT_NVPCR_INIT] = "nvpcr-init",
[TPM2_EVENT_NVPCR_SEPARATOR] = "nvpcr-separator",
[TPM2_EVENT_DM_VERITY] = "dm-verity",
};
DEFINE_STRING_TABLE_LOOKUP(tpm2_userspace_event_type, Tpm2UserspaceEventType);
+1
View File
@@ -145,6 +145,7 @@ typedef enum Tpm2UserspaceEventType {
TPM2_EVENT_KEYSLOT,
TPM2_EVENT_NVPCR_INIT,
TPM2_EVENT_NVPCR_SEPARATOR,
TPM2_EVENT_DM_VERITY,
_TPM2_USERSPACE_EVENT_TYPE_MAX,
_TPM2_USERSPACE_EVENT_TYPE_INVALID = -EINVAL,
} Tpm2UserspaceEventType;
+17 -1
View File
@@ -2,6 +2,18 @@
#include "varlink-io.systemd.PCRExtend.h"
static SD_VARLINK_DEFINE_ENUM_TYPE(
EventType,
SD_VARLINK_DEFINE_ENUM_VALUE(phase),
SD_VARLINK_DEFINE_ENUM_VALUE(filesystem),
SD_VARLINK_DEFINE_ENUM_VALUE(volume_key),
SD_VARLINK_DEFINE_ENUM_VALUE(machine_id),
SD_VARLINK_DEFINE_ENUM_VALUE(product_id),
SD_VARLINK_DEFINE_ENUM_VALUE(keyslot),
SD_VARLINK_DEFINE_ENUM_VALUE(nvpcr_init),
SD_VARLINK_DEFINE_ENUM_VALUE(nvpcr_separator),
SD_VARLINK_DEFINE_ENUM_VALUE(dm_verity));
static SD_VARLINK_DEFINE_METHOD(
Extend,
SD_VARLINK_FIELD_COMMENT("PCR number to extend, in range of 0…23. Either this or 'nvpcr' must be specified, not both, not neither."),
@@ -11,7 +23,9 @@ static SD_VARLINK_DEFINE_METHOD(
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));
SD_VARLINK_DEFINE_INPUT(data, SD_VARLINK_STRING, SD_VARLINK_NULLABLE),
SD_VARLINK_FIELD_COMMENT("Event type to include in the (userspace) event log). This is optional, and mostly for debugging."),
SD_VARLINK_DEFINE_INPUT_BY_TYPE(eventType, EventType, SD_VARLINK_NULLABLE));
static SD_VARLINK_DEFINE_ERROR(NoSuchNvPCR);
@@ -21,4 +35,6 @@ SD_VARLINK_DEFINE_INTERFACE(
SD_VARLINK_INTERFACE_COMMENT("TPM PCR Extension APIs"),
SD_VARLINK_SYMBOL_COMMENT("Measure some text or binary data into a PCR"),
&vl_method_Extend,
SD_VARLINK_SYMBOL_COMMENT("Event type to store in event log"),
&vl_type_EventType,
&vl_error_NoSuchNvPCR);
+5
View File
@@ -405,6 +405,11 @@ executables += [
'dependencies' : libopenssl,
'conditions' : ['HAVE_OPENSSL'],
},
test_template + {
'sources' : files('test-pkcs7-util.c'),
'dependencies' : libopenssl,
'conditions' : ['HAVE_OPENSSL'],
},
test_template + {
'sources' : files('test-parse-util.c'),
'dependencies' : libm,
+55
View File
@@ -0,0 +1,55 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#include "hexdecoct.h"
#include "iovec-util.h"
#include "pkcs7-util.h"
#include "tests.h"
TEST(pkcs7_extract_signers) {
const char tsig[] =
"MIIEgwYJKoZIhvcNAQcCoIIEdDCCBHACAQExDzANBglghkgBZQMEAgEFADALBgkqhkiG9w0BBwGgggLp"
"MIIC5TCCAc2gAwIBAgIURlvlj5ak0ZhvNS8hENNKwVv60x0wDQYJKoZIhvcNAQELBQAwGzEZMBcGA1UE"
"AwwQbWtvc2kgb2YgbGVubmFydDAeFw0yNTAyMDMxMTAwMjNaFw0yNzAyMDMxMTAwMjNaMBsxGTAXBgNV"
"BAMMEG1rb3NpIG9mIGxlbm5hcnQwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCldQdmHVgU"
"m4saaSqEpF1EVRf9pcIxpVShROBGAbxpxs4BQ7vV7zCg5bXwEVaqaENlIyzqPAj+YQifQS3Dj6LQfH3i"
"War+ciKAv4PYcESG+pcdBb2kJOS8cVD6abZlO9rInvdhXK6PhYF7VohMwMPj/yJYdO50skwA5OQsCHO6"
"amowh0tVzNzpbaJZg6wWIyTf3+ZzZdnOl5EvBCaUqeUhbaxRV9SrAw51rzAOnndUhW1we/vVKEGAPk3c"
"SCb0LLPBG20XX2C00UXgnCWBkU5rkq6BnWSuInhFKCa48avstIet1ZFBA5T5J83ncU9UOVFksEYBXFoc"
"lzcmcbx/2/oFAgMBAAGjITAfMB0GA1UdDgQWBBQewILFCixwq2rejOvJqmZSug2BBDANBgkqhkiG9w0B"
"AQsFAAOCAQEAIvaeNPaJUoIUN5lQC/kcCiKeys96WNRGL2wbTp5PqdnRw14sbWY5iC2z13ih3dmTI9NF"
"TBa7C/ji+5BaAfiJF17LOV00Y8eP5V94fHz4isb0sv5RzLsE4h8X7QFk4JBdV5GiCDzPXjxQAx9kM2so"
"9RGtL8EhHpNygYDgyZ18YeiwcUPkCXT+xG2rM6s/Xlsji0s/18ycI4G8AC8dj5HycyS9BiZHgKrkgqTb"
"VPo4zHYzhZdh0Qrd0J4YpoaotzQ35bkH9PtIkF6C7mE1Z7uMSGFkGQASgJ0BDTpM8QPAf2HIR2xxEtJR"
"ZXkwxxdC+W9AJAzqJldmCHYGSrSR54J0rDGCAV4wggFaAgEBMDMwGzEZMBcGA1UEAwwQbWtvc2kgb2Yg"
"bGVubmFydAIURlvlj5ak0ZhvNS8hENNKwVv60x0wDQYJYIZIAWUDBAIBBQAwDQYJKoZIhvcNAQEBBQAE"
"ggEAXccqvpiEWsz/xvuLhINVZKIOznVdqjkERbZSqCBK94BYESSd+cijaB4XbYaFUZ45Bb3uUDQ56Ojq"
"WoY1elEfqPyCb4vc887QoHmxI0BtdIaHhIDfCGBxhX8fwMknxqjgFa9YvONmDtv4QG4syTw+U3SEqBaa"
"Avftqaa4v4eLk4uZ0nMIgMkx4qOlaxknpP404/nyZPANkOIwDxviNtRBCN9zSiPSqo1zre1vqzaM57Ww"
"8zJASsPEzNR7OsPoLaIZv2OHXpowsRB78TuXGkQnm74T6xdG6DNs24jTYJuCPfGuYLHbrytdhXpFBS6m"
"Orz9715jK2NU5VvGhNVXX4chcw==";
_cleanup_free_ void *sig = NULL;
size_t siglen;
ASSERT_OK(unbase64mem(tsig, &sig, &siglen));
size_t n_signers = 0;
Signer *signers = NULL;
CLEANUP_ARRAY(signers, n_signers, signer_free_many);
ASSERT_OK_EQ(pkcs7_extract_signers(&IOVEC_MAKE(sig, siglen), &signers, &n_signers), 1);
ASSERT_EQ(n_signers, 1U);
ASSERT_EQ(signers[0].issuer.iov_len, 29U);
ASSERT_EQ(signers[0].serial.iov_len, 22U);
_cleanup_free_ char *issuer = NULL;
ASSERT_OK(base64mem(signers[0].issuer.iov_base, signers[0].issuer.iov_len, &issuer));
_cleanup_free_ char *serial = NULL;
ASSERT_OK(base64mem(signers[0].serial.iov_base, signers[0].serial.iov_len, &serial));
ASSERT_STREQ(issuer, "MBsxGTAXBgNVBAMMEG1rb3NpIG9mIGxlbm5hcnQ=");
ASSERT_STREQ(serial, "AhRGW+WPlqTRmG81LyEQ00rBW/rTHQ==");
}
DEFINE_TEST_MAIN(LOG_INFO);
+2 -2
View File
@@ -31,12 +31,12 @@ executables += [
'HAVE_TPM2',
],
},
]
if conf.get('ENABLE_BOOTLOADER') == 1 and conf.get('HAVE_OPENSSL') == 1 and conf.get('HAVE_TPM2') == 1
nvpcrs = [ 'cryptsetup',
'hardware' ]
'hardware',
'verity']
foreach n : nvpcrs
custom_target(
input : 'nvpcr/' + n + '.nvpcr.in',
+5
View File
@@ -0,0 +1,5 @@
{
"name" : "verity",
"algorithm" : "sha256",
"nvIndex" : {{TPM2_NVPCR_BASE + 2}}
}
+25 -1
View File
@@ -16,9 +16,11 @@
#include "main-func.h"
#include "parse-util.h"
#include "path-util.h"
#include "pcrextend-util.h"
#include "pretty-print.h"
#include "string-util.h"
#include "strv.h"
#include "tpm2-util.h"
#include "verbs.h"
static char *arg_hash = NULL; /* the hash algorithm */
@@ -38,12 +40,14 @@ static uint64_t arg_fec_roots = 2;
static void *arg_root_hash_signature = NULL;
static size_t arg_root_hash_signature_size = 0;
static bool arg_root_hash_signature_auto = false;
static char *arg_tpm2_measure_nvpcr = NULL;
STATIC_DESTRUCTOR_REGISTER(arg_hash, freep);
STATIC_DESTRUCTOR_REGISTER(arg_salt, freep);
STATIC_DESTRUCTOR_REGISTER(arg_uuid, freep);
STATIC_DESTRUCTOR_REGISTER(arg_fec_what, freep);
STATIC_DESTRUCTOR_REGISTER(arg_root_hash_signature, freep);
STATIC_DESTRUCTOR_REGISTER(arg_tpm2_measure_nvpcr, freep);
static int help(void) {
_cleanup_free_ char *link = NULL;
@@ -142,7 +146,7 @@ static int parse_options(const char *options) {
for (;;) {
_cleanup_free_ char *word = NULL;
char *val;
const char *val;
r = extract_first_word(&options, &word, ",", EXTRACT_DONT_COALESCE_SEPARATORS | EXTRACT_UNESCAPE_SEPARATORS);
if (r < 0)
@@ -290,6 +294,21 @@ static int parse_options(const char *options) {
if (r < 0)
return r;
} else if ((val = startswith(word, "tpm2-measure-nvpcr="))) {
r = isempty(val) ? 0 : parse_boolean(val);
if (r == 0) {
arg_tpm2_measure_nvpcr = mfree(arg_tpm2_measure_nvpcr);
return 0;
}
if (r > 0)
val = "verity";
else if (!tpm2_nvpcr_name_is_valid(val)) {
log_warning("Invalid NvPCR name, ignoring: %s", word);
return 0;
}
if (free_and_strdup(&arg_tpm2_measure_nvpcr, val) < 0)
return log_oom();
} else
log_warning("Encountered unknown option '%s', ignoring.", word);
}
@@ -423,6 +442,11 @@ static int verb_attach(int argc, char *argv[], void *userdata) {
if (r < 0)
return log_error_errno(r, "Failed to set up verity device '%s': %m", volume);
(void) pcrextend_verity_now(
volume,
&IOVEC_MAKE(rh, rh_size),
&IOVEC_MAKE(arg_root_hash_signature, arg_root_hash_signature_size));
return 0;
}
+45 -1
View File
@@ -20,7 +20,8 @@ at_exit() {
jq --seq --slurp </run/log/systemd/tpm2-measure.log
fi
rm -rf /run/nvpcr
rm -rf /run/nvpcr /tmp/nvpcr
rm -f /var/tmp/nvpcr.raw /run/verity.d/test-79-nvpcr.crt
}
trap at_exit EXIT
@@ -52,3 +53,46 @@ test "$DIGEST_ACTUAL2" != "$DIGEST_EXPECTED"
DIGEST_MEASURED2="$(echo -n "schnurz" | openssl dgst -sha256 -hex -r | cut -d' ' -f1)"
DIGEST_EXPECTED2="$(echo "$DIGEST_EXPECTED$DIGEST_MEASURED2" | tr '[:lower:]' '[:upper:]' | basenc --base16 -d | openssl dgst -sha256 -hex -r | cut -d' ' -f1)"
test "$DIGEST_ACTUAL2" = "$DIGEST_EXPECTED2"
mkdir /tmp/nvpcr
OPENSSL_CONFIG="/tmp/nvpcr/opensslconfig"
# Unfortunately OpenSSL insists on reading some config file, hence provide one with mostly placeholder contents
cat >"${OPENSSL_CONFIG:?}" <<EOF
[ req ]
prompt = no
distinguished_name = req_distinguished_name
[ req_distinguished_name ]
C = DE
ST = Test State
L = Test Locality
O = Org Name
OU = Org Unit Name
CN = Common Name
emailAddress = test@email.com
EOF
openssl req -config "$OPENSSL_CONFIG" -subj="/CN=waldo" \
-x509 -sha256 -nodes -days 365 -newkey rsa:4096 \
-keyout /tmp/nvpcr/test-70-nvpcr.key -out /tmp/nvpcr/test-70-nvpcr.crt
mkdir /tmp/nvpcr/tree
touch /tmp/nvpcr/tree/file
SYSTEMD_REPART_OVERRIDE_FSTYPE=squashfs \
systemd-repart -P \
-s /tmp/nvpcr/tree \
--certificate=/tmp/nvpcr/test-70-nvpcr.crt \
--private-key=/tmp/nvpcr/test-70-nvpcr.key \
/var/tmp/nvpcr.raw
mkdir -p /run/verity.d
cp /tmp/nvpcr/test-70-nvpcr.crt /run/verity.d/
cp /run/log/systemd/tpm2-measure.log /tmp/nvpcr/log-before
systemd-dissect --image-policy='root=signed:=absent+unused' --mtree /var/tmp/nvpcr.raw
set +o pipefail
diff /tmp/nvpcr/log-before /run/log/systemd/tpm2-measure.log | grep -F '"content":{"nvIndexName":"verity","string":"verity:'