]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
cryptsetup: automatically measure used keyslot and mechanism (i.e. fido2, tpm2, pkcs1...
authorLennart Poettering <lennart@poettering.net>
Mon, 10 Jun 2024 16:24:58 +0000 (18:24 +0200)
committerLennart Poettering <lennart@poettering.net>
Sun, 2 Nov 2025 20:14:35 +0000 (21:14 +0100)
Fixes: #29877
man/crypttab.xml
src/cryptsetup/cryptsetup.c
src/gpt-auto-generator/gpt-auto-generator.c
src/shared/tpm2-util.h
src/tpm2-setup/meson.build
src/tpm2-setup/nvpcr/cryptsetup.nvpcr.in [new file with mode: 0644]

index 1f779395899826cb2d6993c118c6912dd0f257af..a8a8242a62f2411dbfbf4d8be3c6ddad3fea152c 100644 (file)
         <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>
 
index 09939bfa48d998c3657833e8afe76fa6134bbf98..508ca9750d19963084da63846855b3f7eab06068 100644 (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;
         }
index 8ed8f15bbd9c696a82a0265ec87a92c8d96ba917..6b5ca7ed6a06578effb04293033db3a8f2e75915 100644 (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);
index 667d783cea5306f61db8e2943f2f52b47304d927..abf8adfdaf2a3e195cbae55b95c3df72cbb56dfa 100644 (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;
index 0221a5fba16d5465b331a898df9bce81a2f35bec..9814fe1005660ab4fa70789d32e9f645edfe3577 100644 (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',
diff --git a/src/tpm2-setup/nvpcr/cryptsetup.nvpcr.in b/src/tpm2-setup/nvpcr/cryptsetup.nvpcr.in
new file mode 100644 (file)
index 0000000..acec952
--- /dev/null
@@ -0,0 +1,5 @@
+{
+    "name" : "cryptsetup",
+    "algorithm" : "sha256",
+    "nvIndex" : {{TPM2_NVPCR_BASE + 1}}
+}