]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
cryptenroll: Add support for unlocking through TPM2 enrollments
authorGabríel Arthúr Pétursson <gabriel.petursson@marel.com>
Wed, 3 Jan 2024 16:10:45 +0000 (16:10 +0000)
committerGabríel Arthúr Pétursson <gabriel.petursson@marel.com>
Thu, 1 Feb 2024 12:37:12 +0000 (12:37 +0000)
man/systemd-cryptenroll.xml
shell-completion/bash/systemd-cryptenroll
src/cryptenroll/cryptenroll-tpm2.c
src/cryptenroll/cryptenroll-tpm2.h
src/cryptenroll/cryptenroll.c
src/cryptenroll/cryptenroll.h
test/units/testsuite-70.cryptenroll.sh

index 041337ab8af92a1a89a7344f3a921af4db5e030a..e7b89b4194ea720f96570eb3b976df52fb8f16be 100644 (file)
     <title>Limitations</title>
 
     <para>Note that currently when enrolling a new key of one of the five supported types listed above, it is
-    required to first provide a passphrase, a recovery key or a FIDO2 token. It's currently not supported to
-    unlock a device with a TPM2/PKCS#11 key in order to enroll a new TPM2/PKCS#11 key. Thus, if in future key
-    roll-over is desired it's generally recommended to ensure a passphrase, a recovery key or a FIDO2 token
-    is always enrolled.</para>
+    required to first provide a passphrase, a recovery key, a FIDO2 token, or a TPM2 key. It's currently not
+    supported to unlock a device with a PKCS#11 key in order to enroll a new PKCS#11 key. Thus, if in future
+    key roll-over is desired it's generally recommended to ensure a passphrase, a recovery key, a FIDO2
+    token, or a TPM2 key is always enrolled.</para>
 
     <para>Also note that support for enrolling multiple FIDO2 tokens is currently limited. When multiple FIDO2
     tokens are enrolled, <command>systemd-cryptseup</command> will perform pre-flight requests to attempt to
         <xi:include href="version-info.xml" xpointer="v253"/></listitem>
       </varlistentry>
 
+      <varlistentry>
+        <term><option>--unlock-tpm2-device=</option><replaceable>PATH</replaceable></term>
+
+        <listitem><para>Use a TPM2 device insteaad of a password/passhprase read from stdin to unlock the
+        volume. Expects a device node path referring to the TPM2 chip (e.g. <filename>/dev/tpmrm0</filename>).
+        Alternatively the special value <literal>auto</literal> may be specified, in order to automatically
+        determine the device node of a currently discovered TPM2 device (of which there must be exactly one).
+        </para>
+
+        <xi:include href="version-info.xml" xpointer="v256"/></listitem>
+      </varlistentry>
+
       <varlistentry>
         <term><option>--pkcs11-token-uri=</option><replaceable>URI</replaceable></term>
 
index 1723f75beea25b67fa8bced3de2de879d41879b1..f40a33bee6f268a3f8f0f34a8d1806295d538d01 100644 (file)
@@ -52,6 +52,7 @@ _systemd_cryptenroll() {
                      --password --recovery-key'
         [ARG]='--unlock-key-file
                --unlock-fido2-device
+               --unlock-tpm2-device
                --pkcs11-token-uri
                --fido2-credential-algorithm
                --fido2-device
@@ -81,6 +82,9 @@ _systemd_cryptenroll() {
             --unlock-fido2-device)
                 comps="auto $(__get_fido2_devices)"
                 ;;
+            --unlock-tpm2-device)
+                comps="auto $(__get_tpm2_devices)"
+                ;;
             --pkcs11-token-uri)
                 comps='auto list pkcs11:'
                 ;;
index b556c70931f5fadb5964a0407b6d75964bd3fa79..b0937ec6840f40afbe467bcce8b86e8314dfd687 100644 (file)
@@ -3,10 +3,13 @@
 #include "alloc-util.h"
 #include "ask-password-api.h"
 #include "cryptenroll-tpm2.h"
+#include "cryptsetup-tpm2.h"
 #include "env-util.h"
+#include "errno-util.h"
 #include "fileio.h"
 #include "hexdecoct.h"
 #include "json.h"
+#include "log.h"
 #include "memory-util.h"
 #include "random-util.h"
 #include "sha256.h"
@@ -129,6 +132,114 @@ static int get_pin(char **ret_pin_str, TPM2Flags *ret_flags) {
         return 0;
 }
 
+int load_volume_key_tpm2(
+                struct crypt_device *cd,
+                const char *cd_node,
+                const char *device,
+                void *ret_vk,
+                size_t *ret_vks) {
+
+        _cleanup_(iovec_done_erase) struct iovec decrypted_key = {};
+        _cleanup_(erase_and_freep) char *passphrase = NULL;
+        ssize_t passphrase_size;
+        int r;
+
+        assert_se(cd);
+        assert_se(cd_node);
+        assert_se(ret_vk);
+        assert_se(ret_vks);
+
+        bool found_some = false;
+        int token = 0; /* first token to look at */
+
+        for (;;) {
+                _cleanup_(iovec_done) struct iovec pubkey = {}, salt = {}, srk = {}, pcrlock_nv = {};
+                _cleanup_(iovec_done) struct iovec blob = {}, policy_hash = {};
+                uint32_t hash_pcr_mask, pubkey_pcr_mask;
+                uint16_t pcr_bank, primary_alg;
+                TPM2Flags tpm2_flags;
+                int keyslot;
+
+                r = find_tpm2_auto_data(
+                                cd,
+                                UINT32_MAX,
+                                token,
+                                &hash_pcr_mask,
+                                &pcr_bank,
+                                &pubkey,
+                                &pubkey_pcr_mask,
+                                &primary_alg,
+                                &blob,
+                                &policy_hash,
+                                &salt,
+                                &srk,
+                                &pcrlock_nv,
+                                &tpm2_flags,
+                                &keyslot,
+                                &token);
+                if (r == -ENXIO)
+                        return log_full_errno(LOG_NOTICE,
+                                              SYNTHETIC_ERRNO(EAGAIN),
+                                              found_some
+                                              ? "No TPM2 metadata matching the current system state found in LUKS2 header."
+                                              : "No TPM2 metadata enrolled in LUKS2 header.");
+                if (ERRNO_IS_NEG_NOT_SUPPORTED(r))
+                        /* TPM2 support not compiled in? */
+                        return log_debug_errno(SYNTHETIC_ERRNO(EAGAIN), "TPM2 support not available.");
+                if (r < 0)
+                        return r;
+
+                found_some = true;
+
+                r = acquire_tpm2_key(
+                                cd_node,
+                                device,
+                                hash_pcr_mask,
+                                pcr_bank,
+                                &pubkey,
+                                pubkey_pcr_mask,
+                                /* signature_path= */ NULL,
+                                /* pcrlock_path= */ NULL,
+                                primary_alg,
+                                /* key_file= */ NULL, /* key_file_size= */ 0, /* key_file_offset= */ 0, /* no key file */
+                                &blob,
+                                &policy_hash,
+                                &salt,
+                                &srk,
+                                &pcrlock_nv,
+                                tpm2_flags,
+                                /* until= */ 0,
+                                /* headless= */ false,
+                                /* ask_password_flags */ false,
+                                &decrypted_key);
+                if (IN_SET(r, -EACCES, -ENOLCK))
+                        return log_notice_errno(SYNTHETIC_ERRNO(EAGAIN), "TPM2 PIN unlock failed");
+                if (r != -EPERM)
+                        break;
+
+                token++; /* try a different token next time */
+        }
+
+        if (r < 0)
+                return log_error_errno(r, "Unlocking via TPM2 device failed: %m");
+
+        passphrase_size = base64mem(decrypted_key.iov_base, decrypted_key.iov_len, &passphrase);
+        if (passphrase_size < 0)
+                return log_oom();
+
+        r = crypt_volume_key_get(
+                        cd,
+                        CRYPT_ANY_SLOT,
+                        ret_vk,
+                        ret_vks,
+                        passphrase,
+                        passphrase_size);
+        if (r < 0)
+                return log_error_errno(r, "Unlocking via TPM2 device failed: %m");
+
+        return r;
+}
+
 int enroll_tpm2(struct crypt_device *cd,
                 const void *volume_key,
                 size_t volume_key_size,
index 2fbcdd4b2f1650329ea35c6a4f4975ce380784ef..7908b0331043432939179bc64e9b5b90a4787f2d 100644 (file)
@@ -8,9 +8,15 @@
 #include "tpm2-util.h"
 
 #if HAVE_TPM2
+int load_volume_key_tpm2(struct crypt_device *cd, const char *cd_node, const char *device, void *ret_vk, size_t *ret_vks);
 int enroll_tpm2(struct crypt_device *cd, const void *volume_key, size_t volume_key_size, const char *device, uint32_t seal_key_handle, const char *device_key, Tpm2PCRValue *hash_pcrs, size_t n_hash_pcrs, const char *pubkey_path, uint32_t pubkey_pcr_mask, const char *signature_path, bool use_pin, const char *pcrlock_path);
 #else
-static inline int enroll_tpm2(struct crypt_device *cd, const void *volume_key, size_t volume_key_size, const char *device, uint32_t seal_key_handle, const char *device_key, Tpm2PCRValue *hash_pcrs, size_t n_hash_pcrs, const char *pubkey_path, uint32_t pubkey_pcr_mask, const char *signature_path, bool use_pin, const char *pcrlock_path) {
+static inline int load_volume_key_tpm2(struct crypt_device *cd, const char *cd_node, const char *device, void *ret_vk, size_t *ret_vks) {
+        return log_debug_errno(SYNTHETIC_ERRNO(EOPNOTSUPP),
+                               "TPM2 unlocking not supported.");
+}
+
+static inline int enroll_tpm2(struct crypt_device *cd, const void *volume_key, size_t volume_key_size, const char *device, uint32_t seal_key_handle, const char *device_key, Tpm2PCRValue *hash_pcrs, size_t n_hash_pcrs, const char *pubkey_path, uint32_t pubkey_pcr_mask, const char *signature_path, bool use_pin, const char *pcrlock_path, int *slot_to_wipe) {
         return log_debug_errno(SYNTHETIC_ERRNO(EOPNOTSUPP),
                                "TPM2 key enrollment not supported.");
 }
index 0674116ec8c441a6725e256bfbd9e2b6dbc4d546..76ef3da624301764275955f2cc8f2de189ae986c 100644 (file)
@@ -34,6 +34,7 @@ static EnrollType arg_enroll_type = _ENROLL_TYPE_INVALID;
 static char *arg_unlock_keyfile = NULL;
 static UnlockType arg_unlock_type = UNLOCK_PASSWORD;
 static char *arg_unlock_fido2_device = NULL;
+static char *arg_unlock_tpm2_device = NULL;
 static char *arg_pkcs11_token_uri = NULL;
 static char *arg_fido2_device = NULL;
 static char *arg_tpm2_device = NULL;
@@ -62,6 +63,7 @@ assert_cc(sizeof(arg_wipe_slots_mask) * 8 >= _ENROLL_TYPE_MAX);
 
 STATIC_DESTRUCTOR_REGISTER(arg_unlock_keyfile, freep);
 STATIC_DESTRUCTOR_REGISTER(arg_unlock_fido2_device, freep);
+STATIC_DESTRUCTOR_REGISTER(arg_unlock_tpm2_device, freep);
 STATIC_DESTRUCTOR_REGISTER(arg_pkcs11_token_uri, freep);
 STATIC_DESTRUCTOR_REGISTER(arg_fido2_device, freep);
 STATIC_DESTRUCTOR_REGISTER(arg_tpm2_device, freep);
@@ -118,6 +120,8 @@ static int help(void) {
                "                       Use a file to unlock the volume\n"
                "     --unlock-fido2-device=PATH\n"
                "                       Use a FIDO2 device to unlock the volume\n"
+               "     --unlock-tpm2-device=PATH\n"
+               "                       Use a TPM2 device to unlock the volume\n"
                "\n%3$sSimple Enrollment:%4$s\n"
                "     --password        Enroll a user-supplied password\n"
                "     --recovery-key    Enroll a recovery key\n"
@@ -173,6 +177,7 @@ static int parse_argv(int argc, char *argv[]) {
                 ARG_RECOVERY_KEY,
                 ARG_UNLOCK_KEYFILE,
                 ARG_UNLOCK_FIDO2_DEVICE,
+                ARG_UNLOCK_TPM2_DEVICE,
                 ARG_PKCS11_TOKEN_URI,
                 ARG_FIDO2_DEVICE,
                 ARG_TPM2_DEVICE,
@@ -198,6 +203,7 @@ static int parse_argv(int argc, char *argv[]) {
                 { "recovery-key",                 no_argument,       NULL, ARG_RECOVERY_KEY          },
                 { "unlock-key-file",              required_argument, NULL, ARG_UNLOCK_KEYFILE        },
                 { "unlock-fido2-device",          required_argument, NULL, ARG_UNLOCK_FIDO2_DEVICE   },
+                { "unlock-tpm2-device",           required_argument, NULL, ARG_UNLOCK_TPM2_DEVICE    },
                 { "pkcs11-token-uri",             required_argument, NULL, ARG_PKCS11_TOKEN_URI      },
                 { "fido2-credential-algorithm",   required_argument, NULL, ARG_FIDO2_CRED_ALG        },
                 { "fido2-device",                 required_argument, NULL, ARG_FIDO2_DEVICE          },
@@ -305,6 +311,26 @@ static int parse_argv(int argc, char *argv[]) {
                         break;
                 }
 
+                case ARG_UNLOCK_TPM2_DEVICE: {
+                        _cleanup_free_ char *device = NULL;
+
+                        if (arg_unlock_type != UNLOCK_PASSWORD)
+                                return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
+                                                       "Multiple unlock methods specified at once, refusing.");
+
+                        assert(!arg_unlock_tpm2_device);
+
+                        if (!streq(optarg, "auto")) {
+                                device = strdup(optarg);
+                                if (!device)
+                                        return log_oom();
+                        }
+
+                        arg_unlock_type = UNLOCK_TPM2;
+                        arg_unlock_tpm2_device = TAKE_PTR(device);
+                        break;
+                }
+
                 case ARG_PKCS11_TOKEN_URI: {
                         _cleanup_free_ char *uri = NULL;
 
@@ -667,6 +693,10 @@ static int prepare_luks(
 
         switch (arg_unlock_type) {
 
+        case UNLOCK_PASSWORD:
+                r = load_volume_key_password(cd, arg_node, vk, &vks);
+                break;
+
         case UNLOCK_KEYFILE:
                 r = load_volume_key_keyfile(cd, vk, &vks);
                 break;
@@ -675,8 +705,8 @@ static int prepare_luks(
                 r = load_volume_key_fido2(cd, arg_node, arg_unlock_fido2_device, vk, &vks);
                 break;
 
-        case UNLOCK_PASSWORD:
-                r = load_volume_key_password(cd, arg_node, vk, &vks);
+        case UNLOCK_TPM2:
+                r = load_volume_key_tpm2(cd, arg_node, arg_unlock_tpm2_device, vk, &vks);
                 break;
 
         default:
index 335d9ccd715467c03e29d4bc2bddccdb38275de8..08ded3e0e85b2d311021b7f66252712ff8d830f2 100644 (file)
@@ -17,6 +17,7 @@ typedef enum UnlockType {
         UNLOCK_PASSWORD,
         UNLOCK_KEYFILE,
         UNLOCK_FIDO2,
+        UNLOCK_TPM2,
         _UNLOCK_TYPE_MAX,
         _UNLOCK_TYPE_INVALID = -EINVAL,
 } UnlockType;
index 3f8c14e54e31c5368afa59e5a7652516e3e19ea1..266c5de692af28ec85d6e10a8b6722d011b3abca 100755 (executable)
@@ -59,6 +59,11 @@ systemd-cryptenroll --fido2-with-user-verification=false "$IMAGE"
 systemd-cryptenroll --tpm2-pcrs=8 "$IMAGE"
 systemd-cryptenroll --tpm2-pcrs=boot-loader-code+boot-loader-config "$IMAGE"
 
+# Unlocking using TPM2
+PASSWORD=foo systemd-cryptenroll --tpm2-device=auto "$IMAGE"
+systemd-cryptenroll --unlock-tpm2-device=auto --recovery-key "$IMAGE"
+systemd-cryptenroll --unlock-tpm2-device=auto --tpm2-device=auto --wipe-slot=tpm2 "$IMAGE"
+
 (! systemd-cryptenroll --fido2-with-client-pin=false)
 (! systemd-cryptenroll --fido2-with-user-presence=f "$IMAGE" /tmp/foo)
 (! systemd-cryptenroll --fido2-with-client-pin=1234 "$IMAGE")