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);