systemd/0482-cryptsetup-add-tpm2-me...

408 lines
17 KiB
Diff

From 46659ff4eac28b7a87658668894058bd63c28e81 Mon Sep 17 00:00:00 2001
From: Lennart Poettering <lennart@poettering.net>
Date: Wed, 12 Oct 2022 09:56:32 +0200
Subject: [PATCH] cryptsetup: add tpm2-measure-pcr= and tpm2-measure-bank=
crypttab options
These options allow measuring the volume key used for unlocking the
volume to a TPM2 PCR. This is ideally used for the volume key of the
root file system and can then be used to bind other resources to the
root file system volume in a secure way.
See: #24503
(cherry picked from commit 94c0c85e302d00923dc5bbf9d1b937875f1d0c66)
Related: RHEL-16182
---
meson.build | 3 +-
src/cryptsetup/cryptsetup.c | 226 +++++++++++++++++++++++++++++++++---
src/fundamental/tpm-pcr.h | 3 +
3 files changed, 217 insertions(+), 15 deletions(-)
diff --git a/meson.build b/meson.build
index e09c426a72..fe7b47eef5 100644
--- a/meson.build
+++ b/meson.build
@@ -2837,7 +2837,8 @@ if conf.get('HAVE_LIBCRYPTSETUP') == 1
include_directories : includes,
link_with : [libshared],
dependencies : [libcryptsetup,
- libp11kit],
+ libp11kit,
+ libopenssl],
install_rpath : rootpkglibdir,
install : true,
install_dir : rootlibexecdir)
diff --git a/src/cryptsetup/cryptsetup.c b/src/cryptsetup/cryptsetup.c
index a25fb28948..20862e926d 100644
--- a/src/cryptsetup/cryptsetup.c
+++ b/src/cryptsetup/cryptsetup.c
@@ -8,6 +8,7 @@
#include <unistd.h>
#include "sd-device.h"
+#include "sd-messages.h"
#include "alloc-util.h"
#include "ask-password-api.h"
@@ -38,6 +39,7 @@
#include "random-util.h"
#include "string-table.h"
#include "strv.h"
+#include "tpm-pcr.h"
#include "tpm2-util.h"
/* internal helper */
@@ -89,13 +91,15 @@ static bool arg_fido2_device_auto = false;
static void *arg_fido2_cid = NULL;
static size_t arg_fido2_cid_size = 0;
static char *arg_fido2_rp_id = NULL;
-static char *arg_tpm2_device = NULL;
+static char *arg_tpm2_device = NULL; /* These and the following fields are about locking an encypted volume to the local TPM */
static bool arg_tpm2_device_auto = false;
static uint32_t arg_tpm2_pcr_mask = UINT32_MAX;
static char *arg_tpm2_signature = NULL;
static bool arg_tpm2_pin = false;
static bool arg_headless = false;
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_DESTRUCTOR_REGISTER(arg_cipher, freep);
STATIC_DESTRUCTOR_REGISTER(arg_hash, freep);
@@ -107,6 +111,7 @@ STATIC_DESTRUCTOR_REGISTER(arg_fido2_cid, freep);
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 const char* const passphrase_type_table[_PASSPHRASE_TYPE_MAX] = {
[PASSPHRASE_REGULAR] = "passphrase",
@@ -420,6 +425,48 @@ static int parse_one_option(const char *option) {
arg_tpm2_pin = r;
+ } else if ((val = startswith(option, "tpm2-measure-pcr="))) {
+ unsigned pcr;
+
+ r = safe_atou(val, &pcr);
+ if (r < 0) {
+ r = parse_boolean(val);
+ if (r < 0) {
+ log_error_errno(r, "Failed to parse %s, ignoring: %m", option);
+ return 0;
+ }
+
+ pcr = r ? TPM_PCR_INDEX_VOLUME_KEY : UINT_MAX;
+ } else if (pcr >= TPM2_PCRS_MAX) {
+ log_error("Selected TPM index for measurement %u outside of allowed range 0…%u, ignoring.", pcr, TPM2_PCRS_MAX-1);
+ return 0;
+ }
+
+ arg_tpm2_measure_pcr = pcr;
+
+ } else if ((val = startswith(option, "tpm2-measure-bank="))) {
+
+#if HAVE_OPENSSL
+ _cleanup_strv_free_ char **l = NULL;
+
+ l = strv_split(optarg, ":");
+ if (!l)
+ return log_oom();
+
+ STRV_FOREACH(i, l) {
+ const EVP_MD *implementation;
+
+ implementation = EVP_get_digestbyname(*i);
+ if (!implementation)
+ return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Unknown bank '%s', refusing.", val);
+
+ if (strv_extend(&arg_tpm2_measure_banks, EVP_MD_name(implementation)) < 0)
+ return log_oom();
+ }
+#else
+ log_error("Build lacks OpenSSL support, cannot measure to PCR banks, ignoring: %s", option);
+#endif
+
} else if ((val = startswith(option, "try-empty-password="))) {
r = parse_boolean(val);
@@ -762,6 +809,149 @@ static int get_password(
return 0;
}
+static int measure_volume_key(
+ struct crypt_device *cd,
+ const char *name,
+ const void *volume_key,
+ size_t volume_key_size) {
+
+ int r;
+
+ assert(cd);
+ assert(name);
+ assert(volume_key);
+ assert(volume_key_size > 0);
+
+ if (arg_tpm2_measure_pcr == UINT_MAX) {
+ log_debug("Not measuring volume key, deactivated.");
+ return 0;
+ }
+
+#if HAVE_TPM2
+ r = dlopen_tpm2();
+ if (r < 0)
+ return log_error_errno(r, "Failed to load TPM2 libraries: %m");
+
+ _cleanup_(tpm2_context_destroy) struct tpm2_context c = {};
+ r = tpm2_context_init(arg_tpm2_device, &c);
+ if (r < 0)
+ return r;
+
+ _cleanup_strv_free_ char **l = NULL;
+ if (strv_isempty(arg_tpm2_measure_banks)) {
+ r = tpm2_get_good_pcr_banks_strv(c.esys_context, UINT32_C(1) << arg_tpm2_measure_pcr, &l);
+ if (r < 0)
+ return r;
+ }
+
+ _cleanup_free_ char *joined = strv_join(l ?: arg_tpm2_measure_banks, ", ");
+ if (!joined)
+ return log_oom();
+
+ /* Note: we don't directly measure the volume key, it might be a security problem to send an
+ * 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 *s = NULL;
+ s = strjoin("cryptsetup:", escaped, ":", strempty(crypt_get_uuid(cd)));
+ if (!s)
+ return log_oom();
+
+ r = tpm2_extend_bytes(c.esys_context, l ?: arg_tpm2_measure_banks, arg_tpm2_measure_pcr, s, SIZE_MAX, volume_key, volume_key_size);
+ if (r < 0)
+ return r;
+
+ log_struct(LOG_INFO,
+ "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),
+ "MEASURING=%s", s,
+ "PCR=%u", arg_tpm2_measure_pcr,
+ "BANKS=%s", joined);
+
+ return 0;
+#else
+ return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "TPM2 support disabled, not measuring.");
+#endif
+}
+
+static int measured_crypt_activate_by_volume_key(
+ struct crypt_device *cd,
+ const char *name,
+ const void *volume_key,
+ size_t volume_key_size,
+ uint32_t flags) {
+
+ int r;
+
+ assert(cd);
+ assert(name);
+
+ /* A wrapper around crypt_activate_by_volume_key() which also measures to a PCR if that's requested. */
+
+ r = crypt_activate_by_volume_key(cd, name, volume_key, volume_key_size, flags);
+ if (r < 0)
+ return r;
+
+ if (volume_key_size == 0) {
+ log_debug("Not measuring volume key, none specified.");
+ return r;
+ }
+
+ (void) measure_volume_key(cd, name, volume_key, volume_key_size); /* OK if fails */
+ return r;
+}
+
+static int measured_crypt_activate_by_passphrase(
+ struct crypt_device *cd,
+ const char *name,
+ int keyslot,
+ const char *passphrase,
+ size_t passphrase_size,
+ uint32_t flags) {
+
+ _cleanup_(erase_and_freep) void *vk = NULL;
+ size_t vks;
+ int r;
+
+ 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
+ * 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.");
+ goto shortcut;
+ }
+
+ r = crypt_get_volume_key_size(cd);
+ if (r < 0)
+ return r;
+ if (r == 0) {
+ log_debug("Not measuring volume key, none defined.");
+ goto shortcut;
+ }
+
+ vk = malloc(vks = r);
+ if (!vk)
+ return -ENOMEM;
+
+ r = crypt_volume_key_get(cd, keyslot, vk, &vks, passphrase, passphrase_size);
+ if (r < 0)
+ return r;
+
+ return measured_crypt_activate_by_volume_key(cd, name, vk, vks, flags);
+
+shortcut:
+ return crypt_activate_by_passphrase(cd, name, keyslot, passphrase, passphrase_size, flags);
+}
+
static int attach_tcrypt(
struct crypt_device *cd,
const char *name,
@@ -830,7 +1020,7 @@ static int attach_tcrypt(
return log_error_errno(r, "Failed to load tcrypt superblock on device %s: %m", crypt_get_device_name(cd));
}
- r = crypt_activate_by_volume_key(cd, name, NULL, 0, flags);
+ r = measured_crypt_activate_by_volume_key(cd, name, NULL, 0, flags);
if (r < 0)
return log_error_errno(r, "Failed to activate tcrypt device %s: %m", crypt_get_device_name(cd));
@@ -928,6 +1118,14 @@ static int run_security_device_monitor(
}
static bool libcryptsetup_plugins_support(void) {
+
+#if HAVE_TPM2
+ /* Currently, there's no way for us to query the volume key when plugins are used. Hence don't use
+ * plugins, if measurement has been requested. */
+ if (arg_tpm2_measure_pcr != UINT_MAX)
+ return false;
+#endif
+
#if HAVE_LIBCRYPTSETUP_PLUGINS
int r;
@@ -1173,7 +1371,7 @@ static int attach_luks_or_plain_or_bitlk_by_fido2(
}
if (pass_volume_key)
- r = crypt_activate_by_volume_key(cd, name, decrypted_key, decrypted_key_size, flags);
+ r = measured_crypt_activate_by_volume_key(cd, name, decrypted_key, decrypted_key_size, flags);
else {
_cleanup_(erase_and_freep) char *base64_encoded = NULL;
ssize_t base64_encoded_size;
@@ -1184,7 +1382,7 @@ static int attach_luks_or_plain_or_bitlk_by_fido2(
if (base64_encoded_size < 0)
return log_oom();
- r = crypt_activate_by_passphrase(cd, name, keyslot, base64_encoded, base64_encoded_size, flags);
+ r = measured_crypt_activate_by_passphrase(cd, name, keyslot, base64_encoded, base64_encoded_size, flags);
}
if (r == -EPERM) {
log_error_errno(r, "Failed to activate with FIDO2 decrypted key. (Key incorrect?)");
@@ -1321,7 +1519,7 @@ static int attach_luks_or_plain_or_bitlk_by_pkcs11(
assert(decrypted_key);
if (pass_volume_key)
- r = crypt_activate_by_volume_key(cd, name, decrypted_key, decrypted_key_size, flags);
+ r = measured_crypt_activate_by_volume_key(cd, name, decrypted_key, decrypted_key_size, flags);
else {
_cleanup_(erase_and_freep) char *base64_encoded = NULL;
ssize_t base64_encoded_size;
@@ -1338,7 +1536,7 @@ static int attach_luks_or_plain_or_bitlk_by_pkcs11(
if (base64_encoded_size < 0)
return log_oom();
- r = crypt_activate_by_passphrase(cd, name, keyslot, base64_encoded, base64_encoded_size, flags);
+ r = measured_crypt_activate_by_passphrase(cd, name, keyslot, base64_encoded, base64_encoded_size, flags);
}
if (r == -EPERM) {
log_error_errno(r, "Failed to activate with PKCS#11 decrypted key. (Key incorrect?)");
@@ -1610,7 +1808,7 @@ static int attach_luks_or_plain_or_bitlk_by_tpm2(
assert(decrypted_key);
if (pass_volume_key)
- r = crypt_activate_by_volume_key(cd, name, decrypted_key, decrypted_key_size, flags);
+ r = measured_crypt_activate_by_volume_key(cd, name, decrypted_key, decrypted_key_size, flags);
else {
_cleanup_(erase_and_freep) char *base64_encoded = NULL;
ssize_t base64_encoded_size;
@@ -1621,7 +1819,7 @@ static int attach_luks_or_plain_or_bitlk_by_tpm2(
if (base64_encoded_size < 0)
return log_oom();
- r = crypt_activate_by_passphrase(cd, name, keyslot, base64_encoded, base64_encoded_size, flags);
+ r = measured_crypt_activate_by_passphrase(cd, name, keyslot, base64_encoded, base64_encoded_size, flags);
}
if (r == -EPERM) {
log_error_errno(r, "Failed to activate with TPM2 decrypted key. (Key incorrect?)");
@@ -1648,9 +1846,9 @@ static int attach_luks_or_plain_or_bitlk_by_key_data(
assert(key_data);
if (pass_volume_key)
- r = crypt_activate_by_volume_key(cd, name, key_data, key_data_size, flags);
+ r = measured_crypt_activate_by_volume_key(cd, name, key_data, key_data_size, flags);
else
- r = crypt_activate_by_passphrase(cd, name, arg_key_slot, key_data, key_data_size, flags);
+ r = measured_crypt_activate_by_passphrase(cd, name, arg_key_slot, key_data, key_data_size, flags);
if (r == -EPERM) {
log_error_errno(r, "Failed to activate. (Key incorrect?)");
return -EAGAIN; /* Log actual error, but return EAGAIN */
@@ -1701,9 +1899,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 = crypt_activate_by_volume_key(cd, name, kfdata, kfsize, flags);
+ r = measured_crypt_activate_by_volume_key(cd, name, kfdata, kfsize, flags);
else
- r = crypt_activate_by_passphrase(cd, name, arg_key_slot, kfdata, kfsize, flags);
+ r = measured_crypt_activate_by_passphrase(cd, name, 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 */
@@ -1729,9 +1927,9 @@ static int attach_luks_or_plain_or_bitlk_by_passphrase(
r = -EINVAL;
STRV_FOREACH(p, passwords) {
if (pass_volume_key)
- r = crypt_activate_by_volume_key(cd, name, *p, arg_key_size, flags);
+ r = measured_crypt_activate_by_volume_key(cd, name, *p, arg_key_size, flags);
else
- r = crypt_activate_by_passphrase(cd, name, arg_key_slot, *p, strlen(*p), flags);
+ r = measured_crypt_activate_by_passphrase(cd, name, arg_key_slot, *p, strlen(*p), flags);
if (r >= 0)
break;
}
diff --git a/src/fundamental/tpm-pcr.h b/src/fundamental/tpm-pcr.h
index 794d593825..ec4c3a2b85 100644
--- a/src/fundamental/tpm-pcr.h
+++ b/src/fundamental/tpm-pcr.h
@@ -25,6 +25,9 @@
/* This TPM PCR is where we extend the initrd sysext images into which we pass to the booted kernel */
#define TPM_PCR_INDEX_INITRD_SYSEXTS 13U
+/* This TPM PCR is where we measure the root fs volume key (and maybe /var/'s) if it is split off */
+#define TPM_PCR_INDEX_VOLUME_KEY 15U
+
/* List of PE sections that have special meaning for us in unified kernels. This is the canonical order in
* which we measure the sections into TPM PCR 11 (see above). PLEASE DO NOT REORDER! */
typedef enum UnifiedSection {