diff --git a/man/crypttab.xml b/man/crypttab.xml index a8a8242a62..dbbeb3fd5b 100644 --- a/man/crypttab.xml +++ b/man/crypttab.xml @@ -1012,6 +1012,21 @@ + + + + Pin the expected hash of the volume key. + + In certain cases, e.g. for LUKS volumes where the key is sealed to the TPM2, this may be required + to provide a guarantee that the volume being attached is the volume which was previously created. + can be used to set the expected volume key hash and refuse to attach the volume + if it has a different one. The expected hash matches the digest which is measured to the sha256 PCR bank of the + TPM2 when is used. + + + + + At early boot and when the system manager configuration is diff --git a/src/cryptsetup/cryptsetup.c b/src/cryptsetup/cryptsetup.c index e56941adf2..f715494418 100644 --- a/src/cryptsetup/cryptsetup.c +++ b/src/cryptsetup/cryptsetup.c @@ -126,6 +126,7 @@ 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; +static char *arg_fixate_volume_key = NULL; STATIC_DESTRUCTOR_REGISTER(arg_cipher, freep); STATIC_DESTRUCTOR_REGISTER(arg_hash, freep); @@ -143,6 +144,7 @@ STATIC_DESTRUCTOR_REGISTER(arg_tpm2_pcrlock, freep); STATIC_DESTRUCTOR_REGISTER(arg_link_keyring, freep); STATIC_DESTRUCTOR_REGISTER(arg_link_key_type, freep); STATIC_DESTRUCTOR_REGISTER(arg_link_key_description, freep); +STATIC_DESTRUCTOR_REGISTER(arg_fixate_volume_key, freep); static const char* const passphrase_type_table[_PASSPHRASE_TYPE_MAX] = { [PASSPHRASE_REGULAR] = "passphrase", @@ -651,6 +653,11 @@ static int parse_one_option(const char *option) { #else log_error("Build lacks libcryptsetup support for linking volume keys in user specified kernel keyrings upon device activation, ignoring: %s", option); #endif + } else if ((val = startswith(option, "fixate-volume-key="))) { + r = free_and_strdup(&arg_fixate_volume_key, val); + if (r < 0) + return log_oom(); + } else if (!streq(option, "x-initrd.attach")) log_warning("Encountered unknown /etc/crypttab option '%s', ignoring.", option); @@ -1050,24 +1057,29 @@ static int measure_volume_key( * 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 *prefix = NULL; - _cleanup_free_ char *s = NULL; - s = strjoin("cryptsetup:", escaped, ":", strempty(crypt_get_uuid(cd))); - if (!s) - return log_oom(); + /* 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, l ?: arg_tpm2_measure_banks, arg_tpm2_measure_pcr, &IOVEC_MAKE_STRING(s), &IOVEC_MAKE(volume_key, volume_key_size), TPM2_EVENT_VOLUME_KEY, s); + 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"); 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, s, joined), - LOG_ITEM("MEASURING=%s", s), + 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)); @@ -1173,12 +1185,36 @@ static int measured_crypt_activate_by_volume_key( /* A wrapper around crypt_activate_by_volume_key() which also measures to a PCR if that's requested. */ + /* First, check if volume key digest matches the expectation. */ + if (arg_fixate_volume_key) { + _cleanup_free_ char *key_id = NULL; + + r = cryptsetup_get_volume_key_id( + cd, + /* volume_name= */ name, + /* volume_key= */ volume_key, + /* volume_key_size= */ volume_key_size, + /* ret= */ &key_id); + if (r < 0) + return log_error_errno(r, "Failed to get volume key id."); + + if (!streq(arg_fixate_volume_key, key_id)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "Volume key id: '%s' does not match the expectation: '%s'.", + key_id, arg_fixate_volume_key); + } + r = crypt_activate_by_volume_key(cd, name, volume_key, volume_key_size, flags); if (r == -EEXIST) /* volume is already active */ return log_external_activation(r, name); if (r < 0) return r; + if (arg_tpm2_measure_pcr == UINT_MAX) { + log_debug("Not measuring volume key, deactivated."); + return 0; + } + if (volume_key_size > 0) (void) measure_volume_key(cd, name, volume_key, volume_key_size); /* OK if fails */ else @@ -1204,14 +1240,12 @@ static int measured_crypt_activate_by_passphrase( 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 + * requested. Note that we may need the volume key for the measurement and/or for the comparison, 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."); + if (arg_tpm2_measure_pcr == UINT_MAX && !arg_fixate_volume_key) goto shortcut; - } r = crypt_get_volume_key_size(cd); if (r < 0) @@ -1453,6 +1487,9 @@ static bool use_token_plugins(void) { return false; if (arg_tpm2_measure_keyslot_nvpcr) return false; + /* Volume key is also needed if the expected key id is set */ + if (arg_fixate_volume_key) + return false; #endif /* Disable tokens if we're in FIDO2 mode with manual parameters. */ diff --git a/src/shared/cryptsetup-util.c b/src/shared/cryptsetup-util.c index 5d99edd52d..a766a92b20 100644 --- a/src/shared/cryptsetup-util.c +++ b/src/shared/cryptsetup-util.c @@ -7,6 +7,9 @@ #include "alloc-util.h" #include "cryptsetup-util.h" #include "dlfcn-util.h" +#include "escape.h" +#include "hexdecoct.h" +#include "hmac.h" #include "log.h" #include "parse-util.h" #include "string-util.h" @@ -198,6 +201,64 @@ int cryptsetup_add_token_json(struct crypt_device *cd, sd_json_variant *v) { return 0; } + +int cryptsetup_get_volume_key_prefix( + struct crypt_device *cd, + const char *volume_name, + char **ret) { + + _cleanup_free_ char *volume = NULL; + const char *uuid; + char *s; + + uuid = sym_crypt_get_uuid(cd); + if (!uuid) + return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to get LUKS UUID."); + + if (volume_name) + /* avoid ambiguity around ":" once we join things below */ + volume = xescape(volume_name, ":"); + else + volume = strjoin("luks-", uuid); + if (!volume) + return log_oom_debug(); + + s = strjoin("cryptsetup:", volume, ":", uuid); + if (!s) + return log_oom_debug(); + + *ret = s; + + return 0; +} + +/* The hash must match what measure_volume_key() extends to the SHA256 bank of the TPM2. */ +int cryptsetup_get_volume_key_id( + struct crypt_device *cd, + const char *volume_name, + const void *volume_key, + size_t volume_key_size, + char **ret) { + + _cleanup_free_ char *prefix = NULL; + uint8_t digest[SHA256_DIGEST_SIZE]; + char *hex; + int r; + + r = cryptsetup_get_volume_key_prefix(cd, volume_name, &prefix); + if (r < 0) + return log_debug_errno(r, "Failed to get LUKS volume key prefix."); + + hmac_sha256(volume_key, volume_key_size, prefix, strlen(prefix), digest); + + hex = hexmem(digest, sizeof(digest)); + if (!hex) + return log_oom_debug(); + + *ret = hex; + + return 0; +} #endif int dlopen_cryptsetup(void) { diff --git a/src/shared/cryptsetup-util.h b/src/shared/cryptsetup-util.h index e42debeeb4..e9be8249fa 100644 --- a/src/shared/cryptsetup-util.h +++ b/src/shared/cryptsetup-util.h @@ -66,6 +66,9 @@ int cryptsetup_set_minimal_pbkdf(struct crypt_device *cd); int cryptsetup_get_token_as_json(struct crypt_device *cd, int idx, const char *verify_type, sd_json_variant **ret); int cryptsetup_add_token_json(struct crypt_device *cd, sd_json_variant *v); +int cryptsetup_get_volume_key_prefix(struct crypt_device *cd, const char *volume_name, char **ret); +int cryptsetup_get_volume_key_id(struct crypt_device *cd, const char *volume_name, const void *volume_key, + size_t volume_key_size, char **ret); #endif int dlopen_cryptsetup(void);