diff --git a/NEWS b/NEWS index 1c6e644137a..e7f828a7ada 100644 --- a/NEWS +++ b/NEWS @@ -31,6 +31,12 @@ CHANGES WITH 262: older systemd versions, however systemd continues to accept TPM-sealed credentials that were created before this change. + * The "tpm2-measure-bank=" crypttab option of systemd-cryptsetup is + now deprecated and has no effect. Volume key measurements are + performed via varlink call to systemd-pcrextend, which automatically + selects all suitable TPM2 PCR banks; the banks can no longer be + restricted per volume. + * The legacy socket for controlling systemd-udevd has been removed, and udevadm now unconditionally uses Varlink IPC. The legacy UNIX socket /run/udev/control, which is sometimes used to check whether udevd is diff --git a/man/crypttab.xml b/man/crypttab.xml index 38ba4ceafbe..2e93e11d26e 100644 --- a/man/crypttab.xml +++ b/man/crypttab.xml @@ -939,11 +939,8 @@ - Selects one or more TPM2 PCR banks to measure the volume key into, as configured with - above. Multiple banks may be specified, separated by a colon - character. If not specified, automatically determines available and used banks. Expects a message - digest name (e.g. sha1, sha256, …) as argument, to identify the - bank. + This option is deprecated and has no effect since version 262. All suitable PCR + banks are used. diff --git a/src/cryptsetup/cryptsetup.c b/src/cryptsetup/cryptsetup.c index dcd830e4434..5641681861f 100644 --- a/src/cryptsetup/cryptsetup.c +++ b/src/cryptsetup/cryptsetup.c @@ -7,12 +7,10 @@ #include "sd-device.h" #include "sd-event.h" #include "sd-json.h" -#include "sd-messages.h" #include "alloc-util.h" #include "ask-password-api.h" #include "build.h" -#include "crypto-util.h" #include "cryptsetup-fido2.h" #include "cryptsetup-keyfile.h" #include "cryptsetup-pkcs11.h" @@ -40,6 +38,7 @@ #include "options.h" #include "parse-util.h" #include "path-util.h" +#include "pcrextend-util.h" #include "pkcs11-util.h" #include "pretty-print.h" #include "process-util.h" @@ -123,7 +122,6 @@ static bool arg_tpm2_pin = false; static char *arg_tpm2_pcrlock = NULL; static usec_t arg_token_timeout_usec = 30*USEC_PER_SEC; static unsigned arg_tpm2_measure_pcr = UINT_MAX; /* This and the following field is about measuring the unlocked volume key to the local TPM */ -static char **arg_tpm2_measure_banks = NULL; static char *arg_tpm2_measure_keyslot_nvpcr = NULL; static char *arg_link_keyring = NULL; static char *arg_link_key_type = NULL; @@ -140,7 +138,6 @@ STATIC_DESTRUCTOR_REGISTER(arg_fido2_cid, freep); STATIC_DESTRUCTOR_REGISTER(arg_fido2_rp_id, freep); STATIC_DESTRUCTOR_REGISTER(arg_tpm2_device, freep); STATIC_DESTRUCTOR_REGISTER(arg_tpm2_signature, freep); -STATIC_DESTRUCTOR_REGISTER(arg_tpm2_measure_banks, strv_freep); STATIC_DESTRUCTOR_REGISTER(arg_tpm2_measure_keyslot_nvpcr, freep); STATIC_DESTRUCTOR_REGISTER(arg_tpm2_pcrlock, freep); STATIC_DESTRUCTOR_REGISTER(arg_link_keyring, freep); @@ -537,30 +534,9 @@ static int parse_one_option(const char *option) { } else if ((val = startswith(option, "tpm2-measure-bank="))) { -#if HAVE_OPENSSL - _cleanup_strv_free_ char **l = NULL; - - r = dlopen_libcrypto(LOG_ERR); - if (r < 0) - return r; - - l = strv_split(val, ":"); - if (!l) - return log_oom(); - - STRV_FOREACH(i, l) { - const EVP_MD *implementation; - - implementation = sym_EVP_get_digestbyname(*i); - if (!implementation) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Unknown bank '%s', refusing.", val); - - if (strv_extend(&arg_tpm2_measure_banks, sym_EVP_MD_get0_name(implementation)) < 0) - return log_oom(); - } -#else - log_error("Build lacks OpenSSL support, cannot measure to PCR banks, ignoring: %s", option); -#endif + /* Deprecated: the PCR banks to measure into are now chosen by systemd-pcrextend + * (it extends all suitable banks). Kept for compatibility, but ignored. */ + log_warning("The tpm2-measure-bank= option is deprecated and has no effect, ignoring."); } else if ((val = startswith(option, "tpm2-measure-keyslot-nvpcr="))) { @@ -1041,57 +1017,21 @@ static int measure_volume_key( return 0; } -#if HAVE_TPM2 - _cleanup_(tpm2_context_unrefp) Tpm2Context *c = NULL; - r = tpm2_context_new_or_warn(arg_tpm2_device, &c); - if (r < 0) - return r; - - _cleanup_strv_free_ char **l = NULL; - if (strv_isempty(arg_tpm2_measure_banks)) { - r = tpm2_get_good_pcr_banks_strv(c, UINT32_C(1) << arg_tpm2_measure_pcr, &l); - if (r < 0) - return log_error_errno(r, "Could not verify pcr banks: %m"); - } - - _cleanup_free_ char *joined = strv_join(l ?: arg_tpm2_measure_banks, ", "); - if (!joined) - return log_oom(); - - /* Note: we don't directly measure the volume key, it might be a security problem to send an - * unprotected direct hash of the secret volume key over the wire to the TPM. Hence let's instead - * send a HMAC signature instead. */ - _cleanup_free_ char *prefix = NULL; /* Note: what is extended to the SHA256 bank here must match the expected hash of 'fixate-volume-key=' * calculated by cryptsetup_get_volume_key_id(). */ r = cryptsetup_get_volume_key_prefix(cd, name, &prefix); - if (r) - return log_error_errno(r, "Could not verify pcr banks: %m"); + if (r < 0) + return log_error_errno(r, "Failed to get volume key prefix: %m"); - r = tpm2_pcr_extend_bytes( - c, - /* banks= */ l ?: arg_tpm2_measure_banks, - /* pcr_index = */ arg_tpm2_measure_pcr, - /* data = */ &IOVEC_MAKE_STRING(prefix), - /* secret = */ &IOVEC_MAKE(volume_key, volume_key_size), - /* event_type = */ TPM2_EVENT_VOLUME_KEY, - /* description = */ prefix); + /* Pass the volume key as HMAC secret. pcrextend extends HMAC(volume_key, prefix), + * never a bare hash. Matches cryptsetup_get_volume_key_id(). */ + r = pcrextend_pcr_now(arg_tpm2_measure_pcr, prefix, &IOVEC_MAKE(volume_key, volume_key_size), "volume_key"); if (r < 0) return log_error_errno(r, "Could not extend PCR: %m"); - log_struct(LOG_INFO, - LOG_MESSAGE_ID(SD_MESSAGE_TPM_PCR_EXTEND_STR), - LOG_MESSAGE("Successfully extended PCR index %u with '%s' and volume key (banks %s).", arg_tpm2_measure_pcr, prefix, joined), - LOG_ITEM("MEASURING=%s", prefix), - LOG_ITEM("PCR=%u", arg_tpm2_measure_pcr), - LOG_ITEM("BANKS=%s", joined)); - return 0; -#else - return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "TPM2 support disabled, not measuring volume key."); -#endif } static int measure_keyslot( @@ -1100,9 +1040,8 @@ static int measure_keyslot( const char *mechanism, int keyslot) { -#if HAVE_TPM2 int r; -#endif + assert(cd); assert(name); @@ -1111,7 +1050,6 @@ static int measure_keyslot( return 0; } -#if HAVE_TPM2 r = efi_measured_os(LOG_WARNING); if (r < 0) return r; @@ -1120,11 +1058,6 @@ static int measure_keyslot( return 0; } - _cleanup_(tpm2_context_unrefp) Tpm2Context *c = NULL; - r = tpm2_context_new_or_warn(arg_tpm2_device, &c); - if (r < 0) - return r; - _cleanup_free_ char *escaped = NULL; escaped = xescape(name, ":"); /* avoid ambiguity around ":" once we join things below */ if (!escaped) @@ -1139,27 +1072,11 @@ static int measure_keyslot( if (!s) return log_oom(); - r = tpm2_nvpcr_extend_bytes( - c, - /* session= */ NULL, - arg_tpm2_measure_keyslot_nvpcr, - &IOVEC_MAKE_STRING(s), - /* secret= */ NULL, - TPM2_EVENT_KEYSLOT, - s); + r = pcrextend_nvpcr_now(arg_tpm2_measure_keyslot_nvpcr, s, "keyslot"); if (r < 0) return log_error_errno(r, "Could not extend NvPCR: %m"); - log_struct(LOG_INFO, - "MESSAGE_ID=" SD_MESSAGE_TPM_NVPCR_EXTEND_STR, - LOG_MESSAGE("Successfully extended NvPCR index '%s' with '%s'.", arg_tpm2_measure_keyslot_nvpcr, s), - "MEASURING=%s", s, - "NVPCR=%s", arg_tpm2_measure_keyslot_nvpcr); - return 0; -#else - return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "TPM2 support disabled, not measuring keyslot."); -#endif } static int log_external_activation(int r, const char *volume) { diff --git a/src/pcrextend/pcrextend.c b/src/pcrextend/pcrextend.c index 6c87f244f8b..06cc7dc5a73 100644 --- a/src/pcrextend/pcrextend.c +++ b/src/pcrextend/pcrextend.c @@ -233,21 +233,22 @@ static int determine_banks(Tpm2Context *c, uint32_t target_pcr_mask) { return 0; } -static int escape_and_truncate_data(const void *data, size_t size, char **ret) { +static int escape_and_truncate_data(const struct iovec *data, char **ret) { _cleanup_free_ char *safe = NULL; - assert(data || size == 0); + assert(data); + assert(iovec_is_valid(data)); assert(ret); - if (size > EXTENSION_STRING_SAFE_LIMIT) { - safe = cescape_length(data, EXTENSION_STRING_SAFE_LIMIT); + if (data->iov_len > EXTENSION_STRING_SAFE_LIMIT) { + safe = cescape_length(data->iov_base, EXTENSION_STRING_SAFE_LIMIT); if (!safe) return -ENOMEM; if (!strextend(&safe, "...")) return -ENOMEM; } else { - safe = cescape_length(data, size); + safe = cescape_length(data->iov_base, data->iov_len); if (!safe) return -ENOMEM; } @@ -300,8 +301,8 @@ static int tpm2_context_new_for_measurement(Tpm2Context **ret) { static int extend_pcr_now( uint32_t pcr_mask, - const void *data, - size_t size, + const struct iovec *data, + const struct iovec *secret, Tpm2UserspaceEventType event) { _cleanup_(tpm2_context_unrefp) Tpm2Context *c = NULL; @@ -325,13 +326,13 @@ static int extend_pcr_now( return log_oom(); _cleanup_free_ char *safe = NULL; - if (escape_and_truncate_data(data, size, &safe) < 0) + if (escape_and_truncate_data(data, &safe) < 0) return log_oom(); BIT_FOREACH(pcr, pcr_mask) { log_debug("Measuring '%s' into PCR index %i, banks %s.", safe, pcr, joined_banks); - r = tpm2_pcr_extend_bytes(c, arg_banks, pcr, &IOVEC_MAKE(data, size), /* secret= */ NULL, event, safe); + r = tpm2_pcr_extend_bytes(c, arg_banks, pcr, data, secret, event, safe); if (r < 0) return log_error_errno(r, "Could not extend PCR: %m"); @@ -348,8 +349,8 @@ static int extend_pcr_now( static int extend_nvpcr_now( const char *name, - const void *data, - size_t size, + const struct iovec *data, + const struct iovec *secret, Tpm2UserspaceEventType event) { _cleanup_(tpm2_context_unrefp) Tpm2Context *c = NULL; @@ -362,7 +363,7 @@ static int extend_nvpcr_now( return r; _cleanup_free_ char *safe = NULL; - if (escape_and_truncate_data(data, size, &safe) < 0) + if (escape_and_truncate_data(data, &safe) < 0) return log_oom(); log_debug("Measuring '%s' into NvPCR index '%s'.", safe, name); @@ -371,8 +372,8 @@ static int extend_nvpcr_now( c, /* session= */ NULL, name, - &IOVEC_MAKE(data, size), - /* secret= */ NULL, + data, + secret, event, safe); if (r == -ENOBUFS) @@ -394,6 +395,7 @@ typedef struct MethodExtendParameters { const char *nvpcr; const char *text; struct iovec data; + struct iovec secret; Tpm2UserspaceEventType event_type; } MethodExtendParameters; @@ -401,6 +403,7 @@ static void method_extend_parameters_done(MethodExtendParameters *p) { assert(p); iovec_done(&p->data); + iovec_done_erase(&p->secret); } static JSON_DISPATCH_ENUM_DEFINE(json_dispatch_tpm2_userspace_event_type, Tpm2UserspaceEventType, tpm2_userspace_event_type_from_string); @@ -408,10 +411,11 @@ static JSON_DISPATCH_ENUM_DEFINE(json_dispatch_tpm2_userspace_event_type, Tpm2Us 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 }, + { "secret", SD_JSON_VARIANT_STRING, json_dispatch_unbase64_iovec, offsetof(MethodExtendParameters, secret), 0 }, { "eventType", SD_JSON_VARIANT_STRING, json_dispatch_tpm2_userspace_event_type, offsetof(MethodExtendParameters, event_type), 0 }, {} }; @@ -457,13 +461,13 @@ static int vl_method_extend(sd_varlink *link, sd_json_variant *parameters, sd_va return sd_varlink_error_invalid_parameter_name(link, p.text ? "text" : "data"); if (p.nvpcr) { - r = extend_nvpcr_now(p.nvpcr, extend_iovec->iov_base, extend_iovec->iov_len, p.event_type); + r = extend_nvpcr_now(p.nvpcr, extend_iovec, &p.secret, p.event_type); if (IN_SET(r, -ENOENT, -ENODEV)) return sd_varlink_error(link, "io.systemd.PCRExtend.NoSuchNvPCR", NULL); if (r == -ENOBUFS) return sd_varlink_error(link, "io.systemd.PCRExtend.NvPCRSpaceExhausted", NULL); } else - r = extend_pcr_now(INDEX_TO_MASK(uint32_t, p.pcr), extend_iovec->iov_base, extend_iovec->iov_len, p.event_type); + r = extend_pcr_now(INDEX_TO_MASK(uint32_t, p.pcr), extend_iovec, &p.secret, p.event_type); if (r < 0) return r; @@ -475,7 +479,7 @@ static int vl_server(void) { int r; r = varlink_server_new(&varlink_server, - SD_VARLINK_SERVER_ROOT_ONLY | SD_VARLINK_SERVER_MYSELF_ONLY, + SD_VARLINK_SERVER_ROOT_ONLY | SD_VARLINK_SERVER_MYSELF_ONLY | SD_VARLINK_SERVER_INPUT_SENSITIVE, /* userdata= */ NULL); if (r < 0) return log_error_errno(r, "Failed to allocate Varlink server: %m"); @@ -594,9 +598,9 @@ static int run(int argc, char *argv[]) { } if (arg_nvpcr_name) - r = extend_nvpcr_now(arg_nvpcr_name, word, strlen(word), event); + r = extend_nvpcr_now(arg_nvpcr_name, &IOVEC_MAKE(word, strlen(word)), NULL, event); else - r = extend_pcr_now(arg_pcr_mask, word, strlen(word), event); + r = extend_pcr_now(arg_pcr_mask, &IOVEC_MAKE(word, strlen(word)), NULL, event); /* Both extend paths report "TPM cannot be used for this measurement" (no PCR bank, missing crypto, * no TPM device — see tpm2_context_new_for_measurement()) as -EOPNOTSUPP. Under --graceful we skip * those rather than fail and block boot. Genuine faults keep their own errno and are never diff --git a/src/shared/pcrextend-util.c b/src/shared/pcrextend-util.c index 5bd87e03c91..158699a73d4 100644 --- a/src/shared/pcrextend-util.c +++ b/src/shared/pcrextend-util.c @@ -25,6 +25,99 @@ #include "tpm2-pcr.h" #include "user-record.h" +int pcrextend_pcr_now(unsigned pcr, const char *word, const struct iovec *secret, const char *event) { + +#if HAVE_TPM2 + int r; + + assert(word); + + if (!secret) + secret = &iovec_empty; + + _cleanup_(sd_varlink_unrefp) sd_varlink *vl = NULL; + r = sd_varlink_connect_address(&vl, "/run/systemd/io.systemd.PCRExtend"); + if (r < 0) + return r; + + /* Build the parameters explicitly so that, when they carry the secret we can mark the + * variant sensitive: this keeps the secret out of the debug log and ensures its heap + * buffer is erased rather than merely freed. */ + _cleanup_(sd_json_variant_unrefp) sd_json_variant *parameters = NULL; + r = sd_json_buildo( + ¶meters, + SD_JSON_BUILD_PAIR_INTEGER("pcr", pcr), + SD_JSON_BUILD_PAIR_STRING("text", word), + SD_JSON_BUILD_PAIR_CONDITION(iovec_is_set(secret), + "secret", + SD_JSON_BUILD_BASE64(secret->iov_base, secret->iov_len)), + SD_JSON_BUILD_PAIR_STRING("eventType", event)); + if (r < 0) + return log_debug_errno(r, "Failed to build io.systemd.PCRExtend.Extend() parameters: %m"); + + if (iovec_is_set(secret)) + sd_json_variant_sensitive(parameters); + + _cleanup_(sd_json_variant_unrefp) sd_json_variant *reply = NULL; + const char *error_id = NULL; + r = sd_varlink_call(vl, "io.systemd.PCRExtend.Extend", parameters, &reply, &error_id); + 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 PCR %u completed.", word, pcr); + return 1; +#else + return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "TPM2 support disabled, not measuring."); +#endif +} + +int pcrextend_nvpcr_now(const char *nvpcr, const char *word, const char *event) { + +#if HAVE_TPM2 + int r; + + assert(nvpcr); + assert(word); + + _cleanup_(sd_varlink_unrefp) 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", + &reply, + &error_id, + SD_JSON_BUILD_PAIR_STRING("nvpcr", nvpcr), + SD_JSON_BUILD_PAIR_STRING("text", word), + SD_JSON_BUILD_PAIR_STRING("eventType", event)); + 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 NvPCR '%s' completed.", word, nvpcr); + return 1; +#else + return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "TPM2 support disabled, not measuring."); +#endif +} + static int device_get_file_system_word( sd_device *d, const char *prefix, @@ -305,7 +398,6 @@ int pcrextend_verity_now( const struct iovec *root_hash, const struct iovec *root_hash_sig) { -#if HAVE_TPM2 int r; _cleanup_free_ char *word = NULL; @@ -317,36 +409,7 @@ int pcrextend_verity_now( if (r < 0) return r; - _cleanup_(sd_varlink_unrefp) 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 'verity' NvPCR completed.", word); - return 1; -#else - return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "TPM2 support disabled, not measuring Verity root hashes and signatures."); -#endif + return pcrextend_nvpcr_now("verity", word, "dm_verity"); } #define IMDS_USERDATA_TRUNCATED_MAX 256U @@ -375,8 +438,6 @@ int pcrextend_imds_userdata_word(const struct iovec *data, char **ret) { } int pcrextend_imds_userdata_now(const struct iovec *data) { - -#if HAVE_TPM2 int r; _cleanup_free_ char *word = NULL; @@ -384,34 +445,5 @@ int pcrextend_imds_userdata_now(const struct iovec *data) { if (r < 0) return r; - _cleanup_(sd_varlink_unrefp) 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_INTEGER("pcr", TPM2_PCR_KERNEL_CONFIG), - SD_JSON_BUILD_PAIR_STRING("text", word), - SD_JSON_BUILD_PAIR_STRING("eventType", "imds_userdata")); - 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 PCR 12 completed.", word); - return 1; -#else - return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "TPM2 support disabled, not measuring IMDS userdata."); -#endif + return pcrextend_pcr_now(TPM2_PCR_KERNEL_CONFIG, word, /* secret= */ NULL, "imds_userdata"); } diff --git a/src/shared/pcrextend-util.h b/src/shared/pcrextend-util.h index a81d8b19700..de1b1d49afb 100644 --- a/src/shared/pcrextend-util.h +++ b/src/shared/pcrextend-util.h @@ -5,6 +5,9 @@ #include "forward.h" +int pcrextend_pcr_now(unsigned pcr, const char *word, const struct iovec *secret, const char *event); +int pcrextend_nvpcr_now(const char *nvpcr, const char *word, const char *event); + int pcrextend_file_system_word(const char *path, char **ret, char **ret_normalized_path); int pcrextend_machine_id_word(char **ret); int pcrextend_product_id_word(char **ret); diff --git a/src/shared/varlink-io.systemd.PCRExtend.c b/src/shared/varlink-io.systemd.PCRExtend.c index cf42bf96246..62deaf60618 100644 --- a/src/shared/varlink-io.systemd.PCRExtend.c +++ b/src/shared/varlink-io.systemd.PCRExtend.c @@ -25,6 +25,8 @@ static SD_VARLINK_DEFINE_METHOD( 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_FIELD_COMMENT("HMAC key, encoded in Base64. When set, the HMAC of the measured data keyed by this secret is extended rather than a plain hash."), + SD_VARLINK_DEFINE_INPUT(secret, 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)); diff --git a/test/units/TEST-70-TPM2.cryptsetup-measure.sh b/test/units/TEST-70-TPM2.cryptsetup-measure.sh new file mode 100755 index 00000000000..45d49dc3948 --- /dev/null +++ b/test/units/TEST-70-TPM2.cryptsetup-measure.sh @@ -0,0 +1,111 @@ +#!/usr/bin/env bash +# SPDX-License-Identifier: LGPL-2.1-or-later +set -eux +set -o pipefail + +# Tests that systemd-cryptsetup's tpm2-measure-pcr= option measures the volume key +# through the io.systemd.PCRExtend Varlink service. +# +# Note: the tpm2-measure-keyslot-nvpcr= (NvPCR) path is intentionally not covered here. NvPCRs use a +# signed PCR policy that must be anchored first (see TEST-70-TPM2.nvpcr.sh), which is heavy setup +# orthogonal to what this test checks; the NvPCR extend-via-pcrextend path is already covered there. + +# shellcheck source=test/units/util.sh +. "$(dirname "$0")"/util.sh + +export SYSTEMD_LOG_LEVEL=debug + +if ! command -v systemd-cryptsetup >/dev/null || ! tpm_has_pcr sha256 15; then + echo "systemd-cryptsetup or PCR 15 (sha256) not available, skipping cryptsetup measurement test" + exit 0 +fi + +IMAGE="" + +at_exit() { + if [[ $? -ne 0 ]]; then + # Dump the event log on failure, to ease debugging + jq --seq --slurp /run/systemd/system/systemd-pcrextend.socket.d/50-no-condition.conf </tmp/passphrase +chmod 0600 /tmp/passphrase +cryptsetup luksFormat -q --pbkdf pbkdf2 --pbkdf-force-iterations 1000 --use-urandom "$IMAGE" /tmp/passphrase + +UUID="$(cryptsetup luksUUID "$IMAGE")" + +# Extract the raw volume key so we can recompute the expected HMAC below. The +# volume key is measured as HMAC(volume_key, prefix) rather than a plain hash +# (see cryptsetup_get_volume_key_prefix()/cryptsetup_get_volume_key_id()). +cryptsetup luksDump -q --dump-volume-key --volume-key-file=/tmp/vk --key-file=/tmp/passphrase "$IMAGE" +VK_HEX="$(od -An -v -tx1 /tmp/vk | tr -d ' \n')" + +# prefix = "cryptsetup:" ":" ; "test-volume" +# contains no ':' so it is unescaped. +PREFIX="cryptsetup:test-volume:$UUID" + +# Expected measured digest for the sha256 bank: HMAC-SHA256(volume_key, prefix) +printf '%s' "$PREFIX" | openssl dgst -binary -sha256 -mac HMAC -macopt "hexkey:$VK_HEX" >/tmp/vk-hmac.bin +DIGEST_EXPECTED="$(od -An -v -tx1 /tmp/vk-hmac.bin | tr -d ' \n')" + +# Remember the current event log length so we can index the record we add. +# (json-seq: each record is RS(0x1e)-prefixed, LF-suffixed.) The log is created +# lazily on the first measurement, and this subtest may be the first thing to +# measure (e.g. when run in isolation), so treat an absent log as zero records. +RECORD_COUNT=0 +if [[ -f /run/log/systemd/tpm2-measure.log ]]; then + RECORD_COUNT="$(jq --seq --slurp '. | length' /dev/null