From: Paul Meyer Date: Wed, 22 Jul 2026 06:28:53 +0000 (+0200) Subject: cryptsetup: measure via the pcrextend varlink service X-Git-Url: http://git.ipfire.org/gitweb/?a=commitdiff_plain;h=1bdf2db6f7761bb3ac3fbcf0ea6872abc64fa390;p=thirdparty%2Fsystemd.git cryptsetup: measure via the pcrextend varlink service Measure the volume key and unlock keyslot through io.systemd.PCRExtend instead of driving the TPM directly via tpm2-util, matching how the verity and imds measurements already work. Bank selection and the TPM context now live entirely in systemd-pcrextend. As a result the tpm2-measure-bank= crypttab option can no longer be honored per volume and is now a deprecated no-op. Signed-off-by: Paul Meyer --- diff --git a/NEWS b/NEWS index 2c760d48161..380ee3f75ff 100644 --- a/NEWS +++ b/NEWS @@ -27,6 +27,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"); - - 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); if (r < 0) - return log_error_errno(r, "Could not extend PCR: %m"); + return log_error_errno(r, "Failed to get volume key prefix: %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)); + /* 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"); 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/shared/pcrextend-util.c b/src/shared/pcrextend-util.c index fc1e3f2ff70..158699a73d4 100644 --- a/src/shared/pcrextend-util.c +++ b/src/shared/pcrextend-util.c @@ -25,7 +25,7 @@ #include "tpm2-pcr.h" #include "user-record.h" -static int pcrextend_pcr_now(unsigned pcr, const char *word, const struct iovec *secret, const char *event) { +int pcrextend_pcr_now(unsigned pcr, const char *word, const struct iovec *secret, const char *event) { #if HAVE_TPM2 int r; @@ -78,11 +78,14 @@ static int pcrextend_pcr_now(unsigned pcr, const char *word, const struct iovec #endif } -static int pcrextend_nvpcr_now(const char *nvpcr, const char *word, const char *event) { +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) 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/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