cryptsetup: automatically measure used keyslot and mechanism (i.e. fido2, tpm2, pkcs11) to an NvPCR

Fixes: #29877
This commit is contained in:
Lennart Poettering
2024-06-10 18:24:58 +02:00
parent 8a6e77f1a8
commit e5a2e78665
6 changed files with 198 additions and 29 deletions

View File

@@ -941,6 +941,20 @@
<xi:include href="version-info.xml" xpointer="v253"/></listitem>
</varlistentry>
<varlistentry>
<term><option>tpm2-measure-keyslot-nvpcr=</option></term>
<listitem><para>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 <literal>cryptsetup</literal> to
enable this logic. The slot index and the used unlock mechanism (i.e. <literal>tpm2</literal>,
<literal>fido2</literal>, <literal>pkcs11</literal>) is measured along with the activated volume name
and its UUID.</para>
<xi:include href="version-info.xml" xpointer="v259"/></listitem>
</varlistentry>
<varlistentry>
<term><option>token-timeout=</option></term>

View File

@@ -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;
}

View File

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

View File

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

View File

@@ -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',

View File

@@ -0,0 +1,5 @@
{
"name" : "cryptsetup",
"algorithm" : "sha256",
"nvIndex" : {{TPM2_NVPCR_BASE + 1}}
}