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:?}" <