cryptsetup: Add fixate-volume-key option to pin the expected volume key hash
The expected hash (SHA265 HMAC signature) uses the exact same algorithm which is used to calculate sha256 PCR bank digest when 'tpm2-measure-pcr=' is used.
This commit is contained in:
@@ -1012,6 +1012,21 @@
|
||||
</listitem>
|
||||
</varlistentry>
|
||||
|
||||
<varlistentry>
|
||||
<term><option>fixate-volume-key=</option></term>
|
||||
|
||||
<listitem><para>Pin the expected hash of the volume key.</para>
|
||||
|
||||
<para>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.
|
||||
<option>fixate-volume-key=</option> 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 <option>tpm2-measure-pcr=</option> is used.</para>
|
||||
|
||||
<xi:include href="version-info.xml" xpointer="v260"/>
|
||||
</listitem>
|
||||
</varlistentry>
|
||||
|
||||
</variablelist>
|
||||
|
||||
<para>At early boot and when the system manager configuration is
|
||||
|
||||
+52
-15
@@ -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. */
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user