From: Lennart Poettering Date: Mon, 10 Jun 2024 16:24:58 +0000 (+0200) Subject: cryptsetup: automatically measure used keyslot and mechanism (i.e. fido2, tpm2, pkcs1... X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=e5a2e7866572614e66cbf6c1d3969128562d9552;p=thirdparty%2Fsystemd.git cryptsetup: automatically measure used keyslot and mechanism (i.e. fido2, tpm2, pkcs11) to an NvPCR Fixes: #29877 --- diff --git a/man/crypttab.xml b/man/crypttab.xml index 1f779395899..a8a8242a62f 100644 --- a/man/crypttab.xml +++ b/man/crypttab.xml @@ -941,6 +941,20 @@ + + + + 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. + + + + diff --git a/src/cryptsetup/cryptsetup.c b/src/cryptsetup/cryptsetup.c index 09939bfa48d..508ca9750d1 100644 --- a/src/cryptsetup/cryptsetup.c +++ b/src/cryptsetup/cryptsetup.c @@ -123,6 +123,7 @@ 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; static char *arg_link_key_description = NULL; @@ -138,6 +139,7 @@ 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); STATIC_DESTRUCTOR_REGISTER(arg_link_key_type, freep); @@ -555,6 +557,21 @@ static int parse_one_option(const char *option) { log_error("Build lacks OpenSSL support, cannot measure to PCR banks, ignoring: %s", option); #endif + } else if ((val = startswith(option, "tpm2-measure-keyslot-nvpcr="))) { + + if (isempty(val)) { + arg_tpm2_measure_keyslot_nvpcr = mfree(arg_tpm2_measure_keyslot_nvpcr); + return 0; + } + + if (!tpm2_nvpcr_name_is_valid(val)) { + log_warning("Invalid NvPCR name, ignoring: %s", option); + return 0; + } + + if (free_and_strdup(&arg_tpm2_measure_keyslot_nvpcr, val) < 0) + return log_oom(); + } else if ((val = startswith(option, "try-empty-password="))) { r = parse_boolean(val); @@ -1012,7 +1029,7 @@ static int measure_volume_key( if (r < 0) return r; if (r == 0) { - log_debug("Kernel stub did not measure kernel image into the expected PCR, skipping userspace measurement, too."); + log_debug("Kernel stub did not measure kernel image into the expected PCR, skipping userspace volume key measurement, too."); return 0; } @@ -1060,7 +1077,80 @@ static int measure_volume_key( return 0; #else - return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "TPM2 support disabled, not measuring."); + return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "TPM2 support disabled, not measuring volume key."); +#endif +} + +static int measure_keyslot( + struct crypt_device *cd, + const char *name, + const char *mechanism, + int keyslot) { + + int r; + + assert(cd); + assert(name); + + if (!arg_tpm2_measure_keyslot_nvpcr) { + log_debug("Not measuring unlock keyslot, deactivated."); + return 0; + } + + r = efi_measured_uki(LOG_WARNING); + if (r < 0) + return r; + if (r == 0) { + log_debug("Kernel stub did not measure kernel image into the expected PCR, skipping userspace key slot measurement, too."); + 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_free_ char *escaped = NULL; + escaped = xescape(name, ":"); /* avoid ambiguity around ":" once we join things below */ + if (!escaped) + return log_oom(); + + _cleanup_free_ char *k = NULL; + if (keyslot >= 0 && asprintf(&k, "%i", keyslot) < 0) + return log_oom(); + + _cleanup_free_ char *s = NULL; + s = strjoin("cryptsetup-keyslot:", escaped, ":", strempty(crypt_get_uuid(cd)), ":", strempty(mechanism), ":", strempty(k)); + 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); + if (r == -ENETDOWN) { + /* NvPCR is not initialized yet. Do so now. */ + _cleanup_(iovec_done_erase) struct iovec anchor_secret = {}; + r = tpm2_nvpcr_acquire_anchor_secret(&anchor_secret, /* sync_secondary= */ false); + if (r < 0) + return r; + + r = tpm2_nvpcr_initialize(c, /* session= */ NULL, arg_tpm2_measure_keyslot_nvpcr, &anchor_secret); + if (r < 0) + return log_error_errno(r, "Failed to extend NvPCR index '%s' with anchor secret: %m", name); + + r = tpm2_nvpcr_extend_bytes(c, /* session= */ NULL, arg_tpm2_measure_keyslot_nvpcr, &IOVEC_MAKE_STRING(s), /* secret= */ NULL, TPM2_EVENT_KEYSLOT, s); + } + 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 } @@ -1074,6 +1164,8 @@ static int log_external_activation(int r, const char *volume) { static int measured_crypt_activate_by_volume_key( struct crypt_device *cd, const char *name, + const char *mechanism, + int keyslot, const void *volume_key, size_t volume_key_size, uint32_t flags) { @@ -1091,18 +1183,19 @@ static int measured_crypt_activate_by_volume_key( if (r < 0) return r; - if (volume_key_size == 0) { + if (volume_key_size > 0) + (void) measure_volume_key(cd, name, volume_key, volume_key_size); /* OK if fails */ + else log_debug("Not measuring volume key, none specified."); - return r; - } - (void) measure_volume_key(cd, name, volume_key, volume_key_size); /* OK if fails */ + (void) measure_keyslot(cd, name, mechanism, keyslot); /* ditto */ return r; } static int measured_crypt_activate_by_passphrase( struct crypt_device *cd, const char *name, + const char *mechanism, int keyslot, const char *passphrase, size_t passphrase_size, @@ -1136,17 +1229,21 @@ static int measured_crypt_activate_by_passphrase( if (!vk) return -ENOMEM; - r = crypt_volume_key_get(cd, keyslot, vk, &vks, passphrase, passphrase_size); - if (r < 0) - return r; + keyslot = crypt_volume_key_get(cd, keyslot, vk, &vks, passphrase, passphrase_size); + if (keyslot < 0) + return keyslot; - return measured_crypt_activate_by_volume_key(cd, name, vk, vks, flags); + return measured_crypt_activate_by_volume_key(cd, mechanism, name, keyslot, vk, vks, flags); shortcut: - r = crypt_activate_by_passphrase(cd, name, keyslot, passphrase, passphrase_size, flags); - if (r == -EEXIST) /* volume is already active */ - return log_external_activation(r, name); - return r; + keyslot = crypt_activate_by_passphrase(cd, name, keyslot, passphrase, passphrase_size, flags); + if (keyslot == -EEXIST) /* volume is already active */ + return log_external_activation(keyslot, name); + if (keyslot < 0) + return keyslot; + + (void) measure_keyslot(cd, name, mechanism, keyslot); + return keyslot; } static int attach_tcrypt( @@ -1226,7 +1323,14 @@ static int attach_tcrypt( return log_error_errno(r, "Failed to load tcrypt superblock on device %s: %m", crypt_get_device_name(cd)); } - r = measured_crypt_activate_by_volume_key(cd, name, NULL, 0, flags); + r = measured_crypt_activate_by_volume_key( + cd, + name, + /* mechanism= */ NULL, + /* keyslot= */ -1, + /* volume_key= */ NULL, + /* volume_key_size= */ 0, + flags); if (r < 0) return log_error_errno(r, "Failed to activate tcrypt device %s: %m", crypt_get_device_name(cd)); @@ -1351,6 +1455,8 @@ static bool use_token_plugins(void) { * plugins, if measurement has been requested. */ if (arg_tpm2_measure_pcr != UINT_MAX) return false; + if (arg_tpm2_measure_keyslot_nvpcr) + return false; #endif /* Disable tokens if we're in FIDO2 mode with manual parameters. */ @@ -1586,7 +1692,14 @@ static int attach_luks_or_plain_or_bitlk_by_fido2( } if (pass_volume_key) - r = measured_crypt_activate_by_volume_key(cd, name, decrypted_key, decrypted_key_size, flags); + r = measured_crypt_activate_by_volume_key( + cd, + name, + "fido2", + /* keyslot= */ -1, + decrypted_key, + decrypted_key_size, + flags); else { _cleanup_(erase_and_freep) char *base64_encoded = NULL; ssize_t base64_encoded_size; @@ -1597,7 +1710,14 @@ static int attach_luks_or_plain_or_bitlk_by_fido2( if (base64_encoded_size < 0) return log_oom(); - r = measured_crypt_activate_by_passphrase(cd, name, keyslot, base64_encoded, base64_encoded_size, flags); + r = measured_crypt_activate_by_passphrase( + cd, + name, + "fido2", + keyslot, + base64_encoded, + base64_encoded_size, + flags); } if (r == -EPERM) { log_error_errno(r, "Failed to activate with FIDO2 decrypted key. (Key incorrect?)"); @@ -1743,7 +1863,14 @@ static int attach_luks_or_plain_or_bitlk_by_pkcs11( assert(decrypted_key); if (pass_volume_key) - r = measured_crypt_activate_by_volume_key(cd, name, decrypted_key, decrypted_key_size, flags); + r = measured_crypt_activate_by_volume_key( + cd, + name, + "pkcs11", + /* keyslot= */ -1, + decrypted_key, + decrypted_key_size, + flags); else { _cleanup_(erase_and_freep) char *base64_encoded = NULL; ssize_t base64_encoded_size; @@ -1760,7 +1887,14 @@ static int attach_luks_or_plain_or_bitlk_by_pkcs11( if (base64_encoded_size < 0) return log_oom(); - r = measured_crypt_activate_by_passphrase(cd, name, keyslot, base64_encoded, base64_encoded_size, flags); + r = measured_crypt_activate_by_passphrase( + cd, + name, + "pkcs11", + keyslot, + base64_encoded, + base64_encoded_size, + flags); } if (r == -EPERM) { log_error_errno(r, "Failed to activate with PKCS#11 decrypted key. (Key incorrect?)"); @@ -2048,7 +2182,14 @@ static int attach_luks_or_plain_or_bitlk_by_tpm2( } if (pass_volume_key) - r = measured_crypt_activate_by_volume_key(cd, name, decrypted_key.iov_base, decrypted_key.iov_len, flags); + r = measured_crypt_activate_by_volume_key( + cd, + name, + "tpm2", + /* keyslot= */ -1, + decrypted_key.iov_base, + decrypted_key.iov_len, + flags); else { _cleanup_(erase_and_freep) char *base64_encoded = NULL; ssize_t base64_encoded_size; @@ -2059,7 +2200,14 @@ static int attach_luks_or_plain_or_bitlk_by_tpm2( if (base64_encoded_size < 0) return log_oom(); - r = measured_crypt_activate_by_passphrase(cd, name, keyslot, base64_encoded, base64_encoded_size, flags); + r = measured_crypt_activate_by_passphrase( + cd, + name, + "tpm2", + keyslot, + base64_encoded, + base64_encoded_size, + flags); } if (r == -EPERM) { log_error_errno(r, "Failed to activate with TPM2 decrypted key. (Key incorrect?)"); @@ -2085,9 +2233,9 @@ static int attach_luks_or_plain_or_bitlk_by_key_data( assert(key_data); if (pass_volume_key) - r = measured_crypt_activate_by_volume_key(cd, name, key_data->iov_base, key_data->iov_len, flags); + r = measured_crypt_activate_by_volume_key(cd, name, /* mechanism= */ NULL, /* keyslot= */ -1, key_data->iov_base, key_data->iov_len, flags); else - r = measured_crypt_activate_by_passphrase(cd, name, arg_key_slot, key_data->iov_base, key_data->iov_len, flags); + r = measured_crypt_activate_by_passphrase(cd, name, /* mechanism= */ NULL, arg_key_slot, key_data->iov_base, key_data->iov_len, flags); if (r == -EPERM) { log_error_errno(r, "Failed to activate. (Key incorrect?)"); return -EAGAIN; /* Log actual error, but return EAGAIN */ @@ -2138,9 +2286,9 @@ static int attach_luks_or_plain_or_bitlk_by_key_file( return log_error_errno(r, "Failed to read key file '%s': %m", key_file); if (pass_volume_key) - r = measured_crypt_activate_by_volume_key(cd, name, kfdata, kfsize, flags); + r = measured_crypt_activate_by_volume_key(cd, name, /* mechanism= */ NULL, /* keyslot= */ -1, kfdata, kfsize, flags); else - r = measured_crypt_activate_by_passphrase(cd, name, arg_key_slot, kfdata, kfsize, flags); + r = measured_crypt_activate_by_passphrase(cd, name, /* mechanism= */ NULL, arg_key_slot, kfdata, kfsize, flags); if (r == -EPERM) { log_error_errno(r, "Failed to activate with key file '%s'. (Key data incorrect?)", key_file); return -EAGAIN; /* Log actual error, but return EAGAIN */ @@ -2166,9 +2314,9 @@ static int attach_luks_or_plain_or_bitlk_by_passphrase( r = -EINVAL; STRV_FOREACH(p, passwords) { if (pass_volume_key) - r = measured_crypt_activate_by_volume_key(cd, name, *p, arg_key_size, flags); + r = measured_crypt_activate_by_volume_key(cd, name, /* mechanism= */ NULL, /* keyslot= */ -1, *p, arg_key_size, flags); else - r = measured_crypt_activate_by_passphrase(cd, name, arg_key_slot, *p, strlen(*p), flags); + r = measured_crypt_activate_by_passphrase(cd, name, /* mechanism= */ NULL, arg_key_slot, *p, strlen(*p), flags); if (r >= 0) break; } diff --git a/src/gpt-auto-generator/gpt-auto-generator.c b/src/gpt-auto-generator/gpt-auto-generator.c index 8ed8f15bbd9..6b5ca7ed6a0 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")) + if (!strextend_with_separator(&options, ",", "tpm2-measure-pcr=yes,tpm2-measure-slot-nvpcr=cryptsetup")) 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); diff --git a/src/shared/tpm2-util.h b/src/shared/tpm2-util.h index 667d783cea5..abf8adfdaf2 100644 --- a/src/shared/tpm2-util.h +++ b/src/shared/tpm2-util.h @@ -143,6 +143,7 @@ typedef enum Tpm2UserspaceEventType { TPM2_EVENT_VOLUME_KEY, TPM2_EVENT_MACHINE_ID, TPM2_EVENT_PRODUCT_ID, + TPM2_EVENT_KEYSLOT, _TPM2_USERSPACE_EVENT_TYPE_MAX, _TPM2_USERSPACE_EVENT_TYPE_INVALID = -EINVAL, } Tpm2UserspaceEventType; diff --git a/src/tpm2-setup/meson.build b/src/tpm2-setup/meson.build index 0221a5fba16..9814fe10056 100644 --- a/src/tpm2-setup/meson.build +++ b/src/tpm2-setup/meson.build @@ -35,7 +35,8 @@ executables += [ ] if conf.get('ENABLE_BOOTLOADER') == 1 and conf.get('HAVE_OPENSSL') == 1 and conf.get('HAVE_TPM2') == 1 - nvpcrs = [ 'hardware' ] + nvpcrs = [ 'cryptsetup', + 'hardware' ] foreach n : nvpcrs custom_target( input : 'nvpcr/' + n + '.nvpcr.in', diff --git a/src/tpm2-setup/nvpcr/cryptsetup.nvpcr.in b/src/tpm2-setup/nvpcr/cryptsetup.nvpcr.in new file mode 100644 index 00000000000..acec95206f8 --- /dev/null +++ b/src/tpm2-setup/nvpcr/cryptsetup.nvpcr.in @@ -0,0 +1,5 @@ +{ + "name" : "cryptsetup", + "algorithm" : "sha256", + "nvIndex" : {{TPM2_NVPCR_BASE + 1}} +}