diff --git a/TODO b/TODO
index b9200b9ea4..cf346e0509 100644
--- a/TODO
+++ b/TODO
@@ -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
diff --git a/man/crypttab.xml b/man/crypttab.xml
index f0d0ead947..d57823f88a 100644
--- a/man/crypttab.xml
+++ b/man/crypttab.xml
@@ -945,12 +945,12 @@
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.
+ 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 cryptsetup is selected as NvPCR. The slot index
+ and the used unlock mechanism (i.e. tpm2, fido2,
+ pkcs11) are measured along with the activated volume name and its UUID.
diff --git a/man/systemd-gpt-auto-generator.xml b/man/systemd-gpt-auto-generator.xml
index 398076abc7..e267fc9528 100644
--- a/man/systemd-gpt-auto-generator.xml
+++ b/man/systemd-gpt-auto-generator.xml
@@ -272,9 +272,13 @@
If the system was booted via
systemd-stub7 and the
stub reported to userspace that the kernel image was measured to a TPM2 PCR, then any discovered root and
- /var/ volume identifiers (and volume encryption key in case it is encrypted) will be
- automatically measured into PCR 15 on activation, via
- systemd-pcrfs@.service8.
+ /var/ volume identifiers (and volume encryption keys, in case they are encrypted)
+ will be automatically measured into PCR 15 on activation, via
+ systemd-pcrfs@.service8. Moreover,
+ information about the LUKS key slot used to unlock the volume is measured into NvPCR
+ cryptsetup. Finally, if the root or /usr/ 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 verity.
Mount constraint metadata contained in the file systems is validated by pulling in
systemd-validatefs@.service8
diff --git a/man/veritytab.xml b/man/veritytab.xml
index 9b131e350c..913acad9a2 100644
--- a/man/veritytab.xml
+++ b/man/veritytab.xml
@@ -298,6 +298,18 @@ This is based on crypttab(5).
+
+
+
+ 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 verity NvPCR. If
+ any other string is specified this selects the NvPCR to measure into by name.
+
+
+
+
At early boot and when the system manager configuration is
diff --git a/src/cryptsetup/cryptsetup.c b/src/cryptsetup/cryptsetup.c
index f715494418..dbd0b14d7b 100644
--- a/src/cryptsetup/cryptsetup.c
+++ b/src/cryptsetup/cryptsetup.c
@@ -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)
diff --git a/src/gpt-auto-generator/gpt-auto-generator.c b/src/gpt-auto-generator/gpt-auto-generator.c
index 5a4e5d101e..4fd92a6057 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,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;
}
diff --git a/src/pcrextend/pcrextend.c b/src/pcrextend/pcrextend.c
index d887da924f..c80bf376fd 100644
--- a/src/pcrextend/pcrextend.c
+++ b/src/pcrextend/pcrextend.c
@@ -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;
diff --git a/src/shared/dissect-image.c b/src/shared/dissect-image.c
index ec9247b048..4156903774 100644
--- a/src/shared/dissect-image.c
+++ b/src/shared/dissect-image.c
@@ -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;
}
diff --git a/src/shared/meson.build b/src/shared/meson.build
index b9630bef65..8b49ad9a52 100644
--- a/src/shared/meson.build
+++ b/src/shared/meson.build
@@ -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',
diff --git a/src/shared/pcrextend-util.c b/src/shared/pcrextend-util.c
index 15e0da822a..e4d3b958a8 100644
--- a/src/shared/pcrextend-util.c
+++ b/src/shared/pcrextend-util.c
@@ -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
+}
diff --git a/src/shared/pcrextend-util.h b/src/shared/pcrextend-util.h
index 88bd23c10d..00bc5b9b48 100644
--- a/src/shared/pcrextend-util.h
+++ b/src/shared/pcrextend-util.h
@@ -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);
diff --git a/src/shared/pkcs7-util.c b/src/shared/pkcs7-util.c
new file mode 100644
index 0000000000..0a0e102adf
--- /dev/null
+++ b/src/shared/pkcs7-util.c
@@ -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
+}
diff --git a/src/shared/pkcs7-util.h b/src/shared/pkcs7-util.h
new file mode 100644
index 0000000000..11a93cd857
--- /dev/null
+++ b/src/shared/pkcs7-util.h
@@ -0,0 +1,16 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+#pragma once
+
+#include
+
+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);
diff --git a/src/shared/tpm2-util.c b/src/shared/tpm2-util.c
index c751afb06b..3b559bf84c 100644
--- a/src/shared/tpm2-util.c
+++ b/src/shared/tpm2-util.c
@@ -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);
diff --git a/src/shared/tpm2-util.h b/src/shared/tpm2-util.h
index 6827719e86..68289bec48 100644
--- a/src/shared/tpm2-util.h
+++ b/src/shared/tpm2-util.h
@@ -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;
diff --git a/src/shared/varlink-io.systemd.PCRExtend.c b/src/shared/varlink-io.systemd.PCRExtend.c
index 87783f1ee9..87edec349e 100644
--- a/src/shared/varlink-io.systemd.PCRExtend.c
+++ b/src/shared/varlink-io.systemd.PCRExtend.c
@@ -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);
diff --git a/src/test/meson.build b/src/test/meson.build
index cd563654df..3ca614db36 100644
--- a/src/test/meson.build
+++ b/src/test/meson.build
@@ -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,
diff --git a/src/test/test-pkcs7-util.c b/src/test/test-pkcs7-util.c
new file mode 100644
index 0000000000..ca1d38a4de
--- /dev/null
+++ b/src/test/test-pkcs7-util.c
@@ -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);
diff --git a/src/tpm2-setup/meson.build b/src/tpm2-setup/meson.build
index 9814fe1005..a862e7239c 100644
--- a/src/tpm2-setup/meson.build
+++ b/src/tpm2-setup/meson.build
@@ -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',
diff --git a/src/tpm2-setup/nvpcr/verity.nvpcr.in b/src/tpm2-setup/nvpcr/verity.nvpcr.in
new file mode 100644
index 0000000000..b4fb62bd76
--- /dev/null
+++ b/src/tpm2-setup/nvpcr/verity.nvpcr.in
@@ -0,0 +1,5 @@
+{
+ "name" : "verity",
+ "algorithm" : "sha256",
+ "nvIndex" : {{TPM2_NVPCR_BASE + 2}}
+}
diff --git a/src/veritysetup/veritysetup.c b/src/veritysetup/veritysetup.c
index 91b5bcaaa2..e2135562c1 100644
--- a/src/veritysetup/veritysetup.c
+++ b/src/veritysetup/veritysetup.c
@@ -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;
}
diff --git a/test/units/TEST-70-TPM2.nvpcr.sh b/test/units/TEST-70-TPM2.nvpcr.sh
index ce5bdfc651..ba5d6c9b7f 100755
--- a/test/units/TEST-70-TPM2.nvpcr.sh
+++ b/test/units/TEST-70-TPM2.nvpcr.sh
@@ -20,7 +20,8 @@ at_exit() {
jq --seq --slurp "${OPENSSL_CONFIG:?}" <