mirror of
https://github.com/morgan9e/systemd
synced 2026-04-14 00:14:32 +09:00
cryptsetup: automatically measure used keyslot and mechanism (i.e. fido2, tpm2, pkcs11) to an NvPCR
Fixes: #29877
This commit is contained in:
@@ -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>
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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',
|
||||
|
||||
5
src/tpm2-setup/nvpcr/cryptsetup.nvpcr.in
Normal file
5
src/tpm2-setup/nvpcr/cryptsetup.nvpcr.in
Normal file
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"name" : "cryptsetup",
|
||||
"algorithm" : "sha256",
|
||||
"nvIndex" : {{TPM2_NVPCR_BASE + 1}}
|
||||
}
|
||||
Reference in New Issue
Block a user