diff --git a/meson.build b/meson.build index 64f2e00be5..921f7300b0 100644 --- a/meson.build +++ b/meson.build @@ -2920,7 +2920,8 @@ if conf.get('HAVE_LIBCRYPTSETUP') == 1 link_with : [libshared], dependencies : [libcryptsetup, libp11kit, - versiondep], + versiondep, + libopenssl], install_rpath : rootpkglibdir, install : true, install_dir : rootlibexecdir) diff --git a/src/cryptsetup/cryptsetup.c b/src/cryptsetup/cryptsetup.c index cd2065f480..5efce63a38 100644 --- a/src/cryptsetup/cryptsetup.c +++ b/src/cryptsetup/cryptsetup.c @@ -8,6 +8,7 @@ #include #include "sd-device.h" +#include "sd-messages.h" #include "alloc-util.h" #include "ask-password-api.h" @@ -38,6 +39,7 @@ #include "random-util.h" #include "string-table.h" #include "strv.h" +#include "tpm-pcr.h" #include "tpm2-util.h" /* internal helper */ @@ -89,13 +91,15 @@ static bool arg_fido2_device_auto = false; static void *arg_fido2_cid = NULL; static size_t arg_fido2_cid_size = 0; static char *arg_fido2_rp_id = NULL; -static char *arg_tpm2_device = NULL; +static char *arg_tpm2_device = NULL; /* These and the following fields are about locking an encypted volume to the local TPM */ static bool arg_tpm2_device_auto = false; static uint32_t arg_tpm2_pcr_mask = UINT32_MAX; static char *arg_tpm2_signature = NULL; static bool arg_tpm2_pin = false; static bool arg_headless = false; 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_DESTRUCTOR_REGISTER(arg_cipher, freep); STATIC_DESTRUCTOR_REGISTER(arg_hash, freep); @@ -107,6 +111,7 @@ 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 const char* const passphrase_type_table[_PASSPHRASE_TYPE_MAX] = { [PASSPHRASE_REGULAR] = "passphrase", @@ -420,6 +425,48 @@ static int parse_one_option(const char *option) { arg_tpm2_pin = r; + } else if ((val = startswith(option, "tpm2-measure-pcr="))) { + unsigned pcr; + + r = safe_atou(val, &pcr); + if (r < 0) { + r = parse_boolean(val); + if (r < 0) { + log_error_errno(r, "Failed to parse %s, ignoring: %m", option); + return 0; + } + + pcr = r ? TPM_PCR_INDEX_VOLUME_KEY : UINT_MAX; + } else if (pcr >= TPM2_PCRS_MAX) { + log_error("Selected TPM index for measurement %u outside of allowed range 0…%u, ignoring.", pcr, TPM2_PCRS_MAX-1); + return 0; + } + + arg_tpm2_measure_pcr = pcr; + + } else if ((val = startswith(option, "tpm2-measure-bank="))) { + +#if HAVE_OPENSSL + _cleanup_strv_free_ char **l = NULL; + + l = strv_split(optarg, ":"); + if (!l) + return log_oom(); + + STRV_FOREACH(i, l) { + const EVP_MD *implementation; + + implementation = 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, EVP_MD_name(implementation)) < 0) + return log_oom(); + } +#else + log_error("Build lacks OpenSSL support, cannot measure to PCR banks, ignoring: %s", option); +#endif + } else if ((val = startswith(option, "try-empty-password="))) { r = parse_boolean(val); @@ -762,6 +809,149 @@ static int get_password( return 0; } +static int measure_volume_key( + struct crypt_device *cd, + const char *name, + const void *volume_key, + size_t volume_key_size) { + + int r; + + assert(cd); + assert(name); + assert(volume_key); + assert(volume_key_size > 0); + + if (arg_tpm2_measure_pcr == UINT_MAX) { + log_debug("Not measuring volume key, deactivated."); + return 0; + } + +#if HAVE_TPM2 + r = dlopen_tpm2(); + if (r < 0) + return log_error_errno(r, "Failed to load TPM2 libraries: %m"); + + _cleanup_(tpm2_context_destroy) struct tpm2_context c = {}; + r = tpm2_context_init(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.esys_context, UINT32_C(1) << arg_tpm2_measure_pcr, &l); + if (r < 0) + return r; + } + + _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 *escaped = NULL; + escaped = xescape(name, ":"); /* avoid ambiguity around ":" once we join things below */ + if (!escaped) + return log_oom(); + + _cleanup_free_ char *s = NULL; + s = strjoin("cryptsetup:", escaped, ":", strempty(crypt_get_uuid(cd))); + if (!s) + return log_oom(); + + r = tpm2_extend_bytes(c.esys_context, l ?: arg_tpm2_measure_banks, arg_tpm2_measure_pcr, s, SIZE_MAX, volume_key, volume_key_size); + if (r < 0) + return r; + + log_struct(LOG_INFO, + "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, s, joined), + "MEASURING=%s", s, + "PCR=%u", arg_tpm2_measure_pcr, + "BANKS=%s", joined); + + return 0; +#else + return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "TPM2 support disabled, not measuring."); +#endif +} + +static int measured_crypt_activate_by_volume_key( + struct crypt_device *cd, + const char *name, + const void *volume_key, + size_t volume_key_size, + uint32_t flags) { + + int r; + + assert(cd); + assert(name); + + /* A wrapper around crypt_activate_by_volume_key() which also measures to a PCR if that's requested. */ + + r = crypt_activate_by_volume_key(cd, name, volume_key, volume_key_size, flags); + if (r < 0) + return r; + + if (volume_key_size == 0) { + log_debug("Not measuring volume key, none specified."); + return r; + } + + (void) measure_volume_key(cd, name, volume_key, volume_key_size); /* OK if fails */ + return r; +} + +static int measured_crypt_activate_by_passphrase( + struct crypt_device *cd, + const char *name, + int keyslot, + const char *passphrase, + size_t passphrase_size, + uint32_t flags) { + + _cleanup_(erase_and_freep) void *vk = NULL; + size_t vks; + int r; + + assert(cd); + + /* A wrapper around crypt_activate_by_passphrase() which also measures to a PCR if that's + * requested. Note that we need the volume key for the measurement, and + * crypt_activate_by_passphrase() doesn't give us access to this. Hence, we operate indirectly, and + * retrieve the volume key first, and then activate through that. */ + + if (arg_tpm2_measure_pcr == UINT_MAX) { + log_debug("Not measuring volume key, deactivated."); + goto shortcut; + } + + r = crypt_get_volume_key_size(cd); + if (r < 0) + return r; + if (r == 0) { + log_debug("Not measuring volume key, none defined."); + goto shortcut; + } + + vk = malloc(vks = r); + if (!vk) + return -ENOMEM; + + r = crypt_volume_key_get(cd, keyslot, vk, &vks, passphrase, passphrase_size); + if (r < 0) + return r; + + return measured_crypt_activate_by_volume_key(cd, name, vk, vks, flags); + +shortcut: + return crypt_activate_by_passphrase(cd, name, keyslot, passphrase, passphrase_size, flags); +} + static int attach_tcrypt( struct crypt_device *cd, const char *name, @@ -830,7 +1020,7 @@ static int attach_tcrypt( return log_error_errno(r, "Failed to load tcrypt superblock on device %s: %m", crypt_get_device_name(cd)); } - r = crypt_activate_by_volume_key(cd, name, NULL, 0, flags); + r = measured_crypt_activate_by_volume_key(cd, name, NULL, 0, flags); if (r < 0) return log_error_errno(r, "Failed to activate tcrypt device %s: %m", crypt_get_device_name(cd)); @@ -928,6 +1118,14 @@ static int run_security_device_monitor( } static bool libcryptsetup_plugins_support(void) { + +#if HAVE_TPM2 + /* Currently, there's no way for us to query the volume key when plugins are used. Hence don't use + * plugins, if measurement has been requested. */ + if (arg_tpm2_measure_pcr != UINT_MAX) + return false; +#endif + #if HAVE_LIBCRYPTSETUP_PLUGINS int r; @@ -1157,7 +1355,7 @@ static int attach_luks_or_plain_or_bitlk_by_fido2( } if (pass_volume_key) - r = crypt_activate_by_volume_key(cd, name, decrypted_key, decrypted_key_size, flags); + r = measured_crypt_activate_by_volume_key(cd, name, decrypted_key, decrypted_key_size, flags); else { _cleanup_(erase_and_freep) char *base64_encoded = NULL; ssize_t base64_encoded_size; @@ -1168,7 +1366,7 @@ static int attach_luks_or_plain_or_bitlk_by_fido2( if (base64_encoded_size < 0) return log_oom(); - r = crypt_activate_by_passphrase(cd, name, keyslot, base64_encoded, base64_encoded_size, flags); + r = measured_crypt_activate_by_passphrase(cd, name, keyslot, base64_encoded, base64_encoded_size, flags); } if (r == -EPERM) { log_error_errno(r, "Failed to activate with FIDO2 decrypted key. (Key incorrect?)"); @@ -1305,7 +1503,7 @@ static int attach_luks_or_plain_or_bitlk_by_pkcs11( assert(decrypted_key); if (pass_volume_key) - r = crypt_activate_by_volume_key(cd, name, decrypted_key, decrypted_key_size, flags); + r = measured_crypt_activate_by_volume_key(cd, name, decrypted_key, decrypted_key_size, flags); else { _cleanup_(erase_and_freep) char *base64_encoded = NULL; ssize_t base64_encoded_size; @@ -1322,7 +1520,7 @@ static int attach_luks_or_plain_or_bitlk_by_pkcs11( if (base64_encoded_size < 0) return log_oom(); - r = crypt_activate_by_passphrase(cd, name, keyslot, base64_encoded, base64_encoded_size, flags); + r = measured_crypt_activate_by_passphrase(cd, name, keyslot, base64_encoded, base64_encoded_size, flags); } if (r == -EPERM) { log_error_errno(r, "Failed to activate with PKCS#11 decrypted key. (Key incorrect?)"); @@ -1594,7 +1792,7 @@ static int attach_luks_or_plain_or_bitlk_by_tpm2( assert(decrypted_key); if (pass_volume_key) - r = crypt_activate_by_volume_key(cd, name, decrypted_key, decrypted_key_size, flags); + r = measured_crypt_activate_by_volume_key(cd, name, decrypted_key, decrypted_key_size, flags); else { _cleanup_(erase_and_freep) char *base64_encoded = NULL; ssize_t base64_encoded_size; @@ -1605,7 +1803,7 @@ static int attach_luks_or_plain_or_bitlk_by_tpm2( if (base64_encoded_size < 0) return log_oom(); - r = crypt_activate_by_passphrase(cd, name, keyslot, base64_encoded, base64_encoded_size, flags); + r = measured_crypt_activate_by_passphrase(cd, name, keyslot, base64_encoded, base64_encoded_size, flags); } if (r == -EPERM) { log_error_errno(r, "Failed to activate with TPM2 decrypted key. (Key incorrect?)"); @@ -1632,9 +1830,9 @@ static int attach_luks_or_plain_or_bitlk_by_key_data( assert(key_data); if (pass_volume_key) - r = crypt_activate_by_volume_key(cd, name, key_data, key_data_size, flags); + r = measured_crypt_activate_by_volume_key(cd, name, key_data, key_data_size, flags); else - r = crypt_activate_by_passphrase(cd, name, arg_key_slot, key_data, key_data_size, flags); + r = measured_crypt_activate_by_passphrase(cd, name, arg_key_slot, key_data, key_data_size, flags); if (r == -EPERM) { log_error_errno(r, "Failed to activate. (Key incorrect?)"); return -EAGAIN; /* Log actual error, but return EAGAIN */ @@ -1685,9 +1883,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 = crypt_activate_by_volume_key(cd, name, kfdata, kfsize, flags); + r = measured_crypt_activate_by_volume_key(cd, name, kfdata, kfsize, flags); else - r = crypt_activate_by_passphrase(cd, name, arg_key_slot, kfdata, kfsize, flags); + r = measured_crypt_activate_by_passphrase(cd, name, 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 */ @@ -1713,9 +1911,9 @@ static int attach_luks_or_plain_or_bitlk_by_passphrase( r = -EINVAL; STRV_FOREACH(p, passwords) { if (pass_volume_key) - r = crypt_activate_by_volume_key(cd, name, *p, arg_key_size, flags); + r = measured_crypt_activate_by_volume_key(cd, name, *p, arg_key_size, flags); else - r = crypt_activate_by_passphrase(cd, name, arg_key_slot, *p, strlen(*p), flags); + r = measured_crypt_activate_by_passphrase(cd, name, arg_key_slot, *p, strlen(*p), flags); if (r >= 0) break; } diff --git a/src/fundamental/tpm-pcr.h b/src/fundamental/tpm-pcr.h index d57291328d..e12b4ff607 100644 --- a/src/fundamental/tpm-pcr.h +++ b/src/fundamental/tpm-pcr.h @@ -17,6 +17,9 @@ /* This TPM PCR is where we extend the initrd sysext images into which we pass to the booted kernel */ #define TPM_PCR_INDEX_INITRD_SYSEXTS 13U +/* This TPM PCR is where we measure the root fs volume key (and maybe /var/'s) if it is split off */ +#define TPM_PCR_INDEX_VOLUME_KEY 15U + /* List of PE sections that have special meaning for us in unified kernels. This is the canonical order in * which we measure the sections into TPM PCR 11 (see above). PLEASE DO NOT REORDER! */ typedef enum UnifiedSection {