]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
cryptenroll: add support for TPM2 enrolling
authorLennart Poettering <lennart@poettering.net>
Sat, 28 Nov 2020 14:27:34 +0000 (15:27 +0100)
committerLennart Poettering <lennart@poettering.net>
Thu, 17 Dec 2020 19:01:31 +0000 (20:01 +0100)
meson.build
meson_options.txt
src/cryptenroll/cryptenroll-tpm2.c [new file with mode: 0644]
src/cryptenroll/cryptenroll-tpm2.h [new file with mode: 0644]
src/cryptenroll/cryptenroll.c
src/shared/meson.build
src/shared/tpm2-util.c [new file with mode: 0644]
src/shared/tpm2-util.h [new file with mode: 0644]

index f434d685422d2e88fa3958c621ca09cdf3c2a9c2..70696a724b06e9d4c67e592f4c8f02ea7e801c50 100644 (file)
@@ -1185,6 +1185,17 @@ else
 endif
 conf.set10('HAVE_LIBFIDO2', have)
 
+want_tpm2 = get_option('tpm2')
+if want_tpm2 != 'false' and not skip_deps
+        tpm2 = dependency('tss2-esys tss2-rc tss2-mu',
+                              required : want_tpm2 == 'true')
+        have = tpm2.found()
+else
+        have = false
+        tpm2 = []
+endif
+conf.set10('HAVE_TPM2', have)
+
 want_elfutils = get_option('elfutils')
 if want_elfutils != 'false' and not skip_deps
         libdw = dependency('libdw',
@@ -2428,6 +2439,7 @@ if conf.get('HAVE_LIBCRYPTSETUP') == 1
                 src/cryptenroll/cryptenroll-pkcs11.h
                 src/cryptenroll/cryptenroll-recovery.c
                 src/cryptenroll/cryptenroll-recovery.h
+                src/cryptenroll/cryptenroll-tpm2.h
                 src/cryptenroll/cryptenroll.c
 '''.split())
 
@@ -2439,12 +2451,17 @@ if conf.get('HAVE_LIBCRYPTSETUP') == 1
                 systemd_cryptenroll_sources += files('src/cryptenroll/cryptenroll-fido2.c')
         endif
 
+        if conf.get('HAVE_TPM2') == 1
+                systemd_cryptenroll_sources += files('src/cryptenroll/cryptenroll-tpm2.c')
+        endif
+
         executable(
                 'systemd-cryptenroll',
                 systemd_cryptenroll_sources,
                 include_directories : includes,
                 link_with : [libshared],
                 dependencies : [libcryptsetup,
+                                libdl,
                                 libopenssl,
                                 libp11kit],
                 install_rpath : rootlibexecdir,
@@ -3770,6 +3787,7 @@ foreach tuple : [
         ['libfdisk'],
         ['p11kit'],
         ['libfido2'],
+        ['tpm2'],
         ['AUDIT'],
         ['IMA'],
         ['AppArmor'],
index eed3596b9beb49057c1ff050481b39f1db03854f..6e104d21c33e634cda633f96fd9180488c334440 100644 (file)
@@ -327,6 +327,8 @@ option('p11kit', type : 'combo', choices : ['auto', 'true', 'false'],
        description : 'p11kit support')
 option('libfido2', type : 'combo', choices : ['auto', 'true', 'false'],
        description : 'FIDO2 support')
+option('tpm2', type : 'combo', choices : ['auto', 'true', 'false'],
+       description : 'TPM2 support')
 option('elfutils', type : 'combo', choices : ['auto', 'true', 'false'],
        description : 'elfutils support')
 option('zlib', type : 'combo', choices : ['auto', 'true', 'false'],
diff --git a/src/cryptenroll/cryptenroll-tpm2.c b/src/cryptenroll/cryptenroll-tpm2.c
new file mode 100644 (file)
index 0000000..211f8f9
--- /dev/null
@@ -0,0 +1,131 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+#include "alloc-util.h"
+#include "cryptenroll-tpm2.h"
+#include "hexdecoct.h"
+#include "json.h"
+#include "memory-util.h"
+#include "tpm2-util.h"
+
+static int search_policy_hash(
+                struct crypt_device *cd,
+                const void *hash,
+                size_t hash_size) {
+
+        int r;
+
+        assert(cd);
+        assert(hash || hash_size == 0);
+
+        if (hash_size == 0)
+                return 0;
+
+        for (int token = 0; token < LUKS2_TOKENS_MAX; token ++) {
+                _cleanup_(json_variant_unrefp) JsonVariant *v = NULL;
+                _cleanup_free_ void *thash = NULL;
+                size_t thash_size = 0;
+                int keyslot;
+                JsonVariant *w;
+
+                r = cryptsetup_get_token_as_json(cd, token, "systemd-tpm2", &v);
+                if (IN_SET(r, -ENOENT, -EINVAL, -EMEDIUMTYPE))
+                        continue;
+                if (r < 0)
+                        return log_error_errno(r, "Failed to read JSON token data off disk: %m");
+
+                keyslot = cryptsetup_get_keyslot_from_token(v);
+                if (keyslot < 0)
+                        return log_error_errno(keyslot, "Failed to determine keyslot of JSON token: %m");
+
+                w = json_variant_by_key(v, "tpm2-policy-hash");
+                if (!w || !json_variant_is_string(w))
+                        return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
+                                               "TPM2 token data lacks 'tpm2-policy-hash' field.");
+
+                r = unhexmem(json_variant_string(w), (size_t) -1, &thash, &thash_size);
+                if (r < 0)
+                        return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
+                                               "Invalid base64 data in 'tpm2-policy-hash' field.");
+
+                if (memcmp_nn(hash, hash_size, thash, thash_size) == 0)
+                        return keyslot; /* Found entry with same hash. */
+        }
+
+        return -ENOENT; /* Not found */
+}
+
+int enroll_tpm2(struct crypt_device *cd,
+                const void *volume_key,
+                size_t volume_key_size,
+                const char *device,
+                uint32_t pcr_mask) {
+
+        _cleanup_(erase_and_freep) void *secret = NULL, *secret2 = NULL;
+        _cleanup_(json_variant_unrefp) JsonVariant *v = NULL, *a = NULL;
+        _cleanup_(erase_and_freep) char *base64_encoded = NULL;
+        size_t secret_size, secret2_size, blob_size, hash_size;
+        _cleanup_free_ void *blob = NULL, *hash = NULL;
+        const char *node;
+        int r, keyslot;
+
+        assert(cd);
+        assert(volume_key);
+        assert(volume_key_size > 0);
+        assert(pcr_mask < (1U << TPM2_PCRS_MAX)); /* Support 24 PCR banks */
+
+        assert_se(node = crypt_get_device_name(cd));
+
+        r = tpm2_seal(device, pcr_mask, &secret, &secret_size, &blob, &blob_size, &hash, &hash_size);
+        if (r < 0)
+                return r;
+
+        /* Let's see if we already have this specific PCR policy hash enrolled, if so, exit early. */
+        r = search_policy_hash(cd, hash, hash_size);
+        if (r == -ENOENT)
+                log_debug_errno(r, "PCR policy hash not yet enrolled, enrolling now.");
+        else if (r < 0)
+                return r;
+        else {
+                log_info("This PCR set is already enrolled, executing no operation.");
+                return r; /* return existing keyslot, so that wiping won't kill it */
+        }
+
+        /* Quick verification that everything is in order, we are not in a hurry after all. */
+        log_debug("Unsealing for verification...");
+        r = tpm2_unseal(device, pcr_mask, blob, blob_size, hash, hash_size, &secret2, &secret2_size);
+        if (r < 0)
+                return r;
+
+        if (memcmp_nn(secret, secret_size, secret2, secret2_size) != 0)
+                return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), "TPM2 seal/unseal verification failed.");
+
+        /* let's base64 encode the key to use, for compat with homed (and it's easier to every type it in by keyboard, if that might end up being necessary. */
+        r = base64mem(secret, secret_size, &base64_encoded);
+        if (r < 0)
+                return log_error_errno(r, "Failed to base64 encode secret key: %m");
+
+        r = cryptsetup_set_minimal_pbkdf(cd);
+        if (r < 0)
+                return log_error_errno(r, "Failed to set minimal PBKDF: %m");
+
+        keyslot = crypt_keyslot_add_by_volume_key(
+                        cd,
+                        CRYPT_ANY_SLOT,
+                        volume_key,
+                        volume_key_size,
+                        base64_encoded,
+                        strlen(base64_encoded));
+        if (keyslot < 0)
+                return log_error_errno(keyslot, "Failed to add new TPM2 key to %s: %m", node);
+
+        r = tpm2_make_luks2_json(keyslot, pcr_mask, blob, blob_size, hash, hash_size, &v);
+        if (r < 0)
+                return log_error_errno(r, "Failed to prepare TPM2 JSON token object: %m");
+
+        r = cryptsetup_add_token_json(cd, v);
+        if (r < 0)
+                return log_error_errno(r, "Failed to add TPM2 JSON token to LUKS2 header: %m");
+
+        log_info("New TPM2 token enrolled as key slot %i.", keyslot);
+        return keyslot;
+}
diff --git a/src/cryptenroll/cryptenroll-tpm2.h b/src/cryptenroll/cryptenroll-tpm2.h
new file mode 100644 (file)
index 0000000..d5dd1b0
--- /dev/null
@@ -0,0 +1,16 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+#pragma once
+
+#include <sys/types.h>
+
+#include "cryptsetup-util.h"
+#include "log.h"
+
+#if HAVE_TPM2
+int enroll_tpm2(struct crypt_device *cd, const void *volume_key, size_t volume_key_size, const char *device, uint32_t pcr_mask);
+#else
+static inline int enroll_tpm2(struct crypt_device *cd, const void *volume_key, size_t volume_key_size, const char *device, uint32_t pcr_mask) {
+        return log_debug_errno(SYNTHETIC_ERRNO(EOPNOTSUPP),
+                               "TPM2 key enrollment not supported.");
+}
+#endif
index e08f810b9b77010dcb6148ad5c2f6f33828b6300..e3a660d021674fba9f52836b59d987ca6a612d23 100644 (file)
@@ -7,22 +7,26 @@
 #include "cryptenroll-password.h"
 #include "cryptenroll-pkcs11.h"
 #include "cryptenroll-recovery.h"
+#include "cryptenroll-tpm2.h"
 #include "cryptsetup-util.h"
 #include "escape.h"
 #include "libfido2-util.h"
 #include "main-func.h"
 #include "memory-util.h"
+#include "parse-util.h"
 #include "path-util.h"
 #include "pkcs11-util.h"
 #include "pretty-print.h"
 #include "strv.h"
 #include "terminal-util.h"
+#include "tpm2-util.h"
 
 typedef enum EnrollType {
         ENROLL_PASSWORD,
         ENROLL_RECOVERY,
         ENROLL_PKCS11,
         ENROLL_FIDO2,
+        ENROLL_TPM2,
         _ENROLL_TYPE_MAX,
         _ENROLL_TYPE_INVALID = -1,
 } EnrollType;
@@ -31,6 +35,7 @@ static EnrollType arg_enroll_type = _ENROLL_TYPE_INVALID;
 static char *arg_pkcs11_token_uri = NULL;
 static char *arg_fido2_device = NULL;
 static char *arg_tpm2_device = NULL;
+static uint32_t arg_tpm2_pcr_mask = UINT32_MAX;
 static char *arg_node = NULL;
 
 STATIC_DESTRUCTOR_REGISTER(arg_pkcs11_token_uri, freep);
@@ -56,6 +61,10 @@ static int help(void) {
                "                       Specify PKCS#11 security token URI\n"
                "     --fido2-device=PATH\n"
                "                       Enroll a FIDO2-HMAC security token\n"
+               "     --tpm2-device=PATH\n"
+               "                       Enroll a TPM2 device\n"
+               "     --tpm2-pcrs=PCR1,PCR2,PCR3,…\n"
+               "                       Specifiy TPM2 PCRs to seal against\n"
                "\nSee the %s for details.\n"
                , program_invocation_short_name
                , ansi_highlight(), ansi_normal()
@@ -73,6 +82,8 @@ static int parse_argv(int argc, char *argv[]) {
                 ARG_RECOVERY_KEY,
                 ARG_PKCS11_TOKEN_URI,
                 ARG_FIDO2_DEVICE,
+                ARG_TPM2_DEVICE,
+                ARG_TPM2_PCRS,
         };
 
         static const struct option options[] = {
@@ -82,6 +93,8 @@ static int parse_argv(int argc, char *argv[]) {
                 { "recovery-key",     no_argument,       NULL, ARG_RECOVERY_KEY     },
                 { "pkcs11-token-uri", required_argument, NULL, ARG_PKCS11_TOKEN_URI },
                 { "fido2-device",     required_argument, NULL, ARG_FIDO2_DEVICE     },
+                { "tpm2-device",      required_argument, NULL, ARG_TPM2_DEVICE      },
+                { "tpm2-pcrs",        required_argument, NULL, ARG_TPM2_PCRS        },
                 {}
         };
 
@@ -169,6 +182,47 @@ static int parse_argv(int argc, char *argv[]) {
                         break;
                 }
 
+                case ARG_TPM2_DEVICE: {
+                        _cleanup_free_ char *device = NULL;
+
+                        if (streq(optarg, "list"))
+                                return tpm2_list_devices();
+
+                        if (arg_enroll_type >= 0 || arg_tpm2_device)
+                                return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
+                                                       "Multiple operations specified at once, refusing.");
+
+                        if (!streq(optarg, "auto")) {
+                                device = strdup(optarg);
+                                if (!device)
+                                        return log_oom();
+                        }
+
+                        arg_enroll_type = ENROLL_TPM2;
+                        arg_tpm2_device = TAKE_PTR(device);
+                        break;
+                }
+
+                case ARG_TPM2_PCRS: {
+                        uint32_t mask;
+
+                        if (isempty(optarg)) {
+                                arg_tpm2_pcr_mask = 0;
+                                break;
+                        }
+
+                        r = tpm2_parse_pcrs(optarg, &mask);
+                        if (r < 0)
+                                return r;
+
+                        if (arg_tpm2_pcr_mask == UINT32_MAX)
+                                arg_tpm2_pcr_mask = mask;
+                        else
+                                arg_tpm2_pcr_mask |= mask;
+
+                        break;
+                }
+
                 case '?':
                         return -EINVAL;
 
@@ -193,6 +247,9 @@ static int parse_argv(int argc, char *argv[]) {
         if (r < 0)
                 return r;
 
+        if (arg_tpm2_pcr_mask == UINT32_MAX)
+                arg_tpm2_pcr_mask = TPM2_PCR_MASK_DEFAULT;
+
         return 1;
 }
 
@@ -348,6 +405,10 @@ static int run(int argc, char *argv[]) {
                 r = enroll_fido2(cd, vk, vks, arg_fido2_device);
                 break;
 
+        case ENROLL_TPM2:
+                r = enroll_tpm2(cd, vk, vks, arg_tpm2_device, arg_tpm2_pcr_mask);
+                break;
+
         default:
                 return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Operation not implemented yet.");
         }
index 45b8d1b07cf2cbb1508233150e4064e9dabf8ab4..ec4f3e882af9def78d0180b959e24f81706338f7 100644 (file)
@@ -237,6 +237,8 @@ shared_sources = files('''
         tmpfile-util-label.h
         tomoyo-util.c
         tomoyo-util.h
+        tpm2-util.c
+        tpm2-util.h
         udev-util.c
         udev-util.h
         uid-range.c
diff --git a/src/shared/tpm2-util.c b/src/shared/tpm2-util.c
new file mode 100644 (file)
index 0000000..8a0f45c
--- /dev/null
@@ -0,0 +1,998 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+#include "extract-word.h"
+#include "parse-util.h"
+#include "tpm2-util.h"
+
+#if HAVE_TPM2
+#include "alloc-util.h"
+#include "dirent-util.h"
+#include "dlfcn-util.h"
+#include "fd-util.h"
+#include "format-table.h"
+#include "fs-util.h"
+#include "hexdecoct.h"
+#include "memory-util.h"
+#include "random-util.h"
+#include "time-util.h"
+
+static void *libtss2_esys_dl = NULL;
+static void *libtss2_rc_dl = NULL;
+static void *libtss2_mu_dl = NULL;
+
+TSS2_RC (*sym_Esys_Create)(ESYS_CONTEXT *esysContext, ESYS_TR parentHandle, ESYS_TR shandle1, ESYS_TR shandle2, ESYS_TR shandle3, const TPM2B_SENSITIVE_CREATE *inSensitive, const TPM2B_PUBLIC *inPublic, const TPM2B_DATA *outsideInfo, const TPML_PCR_SELECTION *creationPCR, TPM2B_PRIVATE **outPrivate, TPM2B_PUBLIC **outPublic, TPM2B_CREATION_DATA **creationData, TPM2B_DIGEST **creationHash, TPMT_TK_CREATION **creationTicket) = NULL;
+TSS2_RC (*sym_Esys_CreatePrimary)(ESYS_CONTEXT *esysContext, ESYS_TR primaryHandle, ESYS_TR shandle1, ESYS_TR shandle2, ESYS_TR shandle3, const TPM2B_SENSITIVE_CREATE *inSensitive, const TPM2B_PUBLIC *inPublic, const TPM2B_DATA *outsideInfo, const TPML_PCR_SELECTION *creationPCR, ESYS_TR *objectHandle, TPM2B_PUBLIC **outPublic, TPM2B_CREATION_DATA **creationData, TPM2B_DIGEST **creationHash, TPMT_TK_CREATION **creationTicket) = NULL;
+void (*sym_Esys_Finalize)(ESYS_CONTEXT **context) = NULL;
+TSS2_RC (*sym_Esys_FlushContext)(ESYS_CONTEXT *esysContext, ESYS_TR flushHandle) = NULL;
+void (*sym_Esys_Free)(void *ptr) = NULL;
+TSS2_RC (*sym_Esys_GetRandom)(ESYS_CONTEXT *esysContext, ESYS_TR shandle1, ESYS_TR shandle2, ESYS_TR shandle3, UINT16 bytesRequested, TPM2B_DIGEST **randomBytes) = NULL;
+TSS2_RC (*sym_Esys_Initialize)(ESYS_CONTEXT **esys_context,  TSS2_TCTI_CONTEXT *tcti, TSS2_ABI_VERSION *abiVersion) = NULL;
+TSS2_RC (*sym_Esys_Load)(ESYS_CONTEXT *esysContext, ESYS_TR parentHandle, ESYS_TR shandle1, ESYS_TR shandle2, ESYS_TR shandle3, const TPM2B_PRIVATE *inPrivate, const TPM2B_PUBLIC *inPublic, ESYS_TR *objectHandle) = NULL;
+TSS2_RC (*sym_Esys_PolicyGetDigest)(ESYS_CONTEXT *esysContext, ESYS_TR policySession, ESYS_TR shandle1, ESYS_TR shandle2, ESYS_TR shandle3, TPM2B_DIGEST **policyDigest) = NULL;
+TSS2_RC (*sym_Esys_PolicyPCR)(ESYS_CONTEXT *esysContext, ESYS_TR policySession, ESYS_TR shandle1, ESYS_TR shandle2, ESYS_TR shandle3, const TPM2B_DIGEST *pcrDigest, const TPML_PCR_SELECTION *pcrs) = NULL;
+TSS2_RC (*sym_Esys_StartAuthSession)(ESYS_CONTEXT *esysContext, ESYS_TR tpmKey, ESYS_TR bind, ESYS_TR shandle1, ESYS_TR shandle2, ESYS_TR shandle3, const TPM2B_NONCE *nonceCaller, TPM2_SE sessionType, const TPMT_SYM_DEF *symmetric, TPMI_ALG_HASH authHash, ESYS_TR *sessionHandle) = NULL;
+TSS2_RC (*sym_Esys_Startup)(ESYS_CONTEXT *esysContext, TPM2_SU startupType) = NULL;
+TSS2_RC (*sym_Esys_Unseal)(ESYS_CONTEXT *esysContext, ESYS_TR itemHandle, ESYS_TR shandle1, ESYS_TR shandle2, ESYS_TR shandle3, TPM2B_SENSITIVE_DATA **outData) = NULL;
+
+const char* (*sym_Tss2_RC_Decode)(TSS2_RC rc) = NULL;
+
+TSS2_RC (*sym_Tss2_MU_TPM2B_PRIVATE_Marshal)(TPM2B_PRIVATE const *src, uint8_t buffer[], size_t buffer_size, size_t *offset) = NULL;
+TSS2_RC (*sym_Tss2_MU_TPM2B_PRIVATE_Unmarshal)(uint8_t const buffer[], size_t buffer_size, size_t *offset, TPM2B_PRIVATE  *dest) = NULL;
+TSS2_RC (*sym_Tss2_MU_TPM2B_PUBLIC_Marshal)(TPM2B_PUBLIC const *src, uint8_t buffer[], size_t buffer_size, size_t *offset) = NULL;
+TSS2_RC (*sym_Tss2_MU_TPM2B_PUBLIC_Unmarshal)(uint8_t const buffer[], size_t buffer_size, size_t *offset, TPM2B_PUBLIC *dest) = NULL;
+
+int dlopen_tpm2(void) {
+        int r, k = 0;
+
+        if (!libtss2_esys_dl) {
+                _cleanup_(dlclosep) void *dl = NULL;
+
+                dl = dlopen("libtss2-esys.so.0", RTLD_LAZY);
+                if (!dl)
+                        return log_debug_errno(SYNTHETIC_ERRNO(EOPNOTSUPP),
+                                               "TPM2 support is not installed: %s", dlerror());
+
+                r = dlsym_many_and_warn(
+                                dl,
+                                LOG_DEBUG,
+                                DLSYM_ARG(Esys_Create),
+                                DLSYM_ARG(Esys_CreatePrimary),
+                                DLSYM_ARG(Esys_Finalize),
+                                DLSYM_ARG(Esys_FlushContext),
+                                DLSYM_ARG(Esys_Free),
+                                DLSYM_ARG(Esys_GetRandom),
+                                DLSYM_ARG(Esys_Initialize),
+                                DLSYM_ARG(Esys_Load),
+                                DLSYM_ARG(Esys_PolicyGetDigest),
+                                DLSYM_ARG(Esys_PolicyPCR),
+                                DLSYM_ARG(Esys_StartAuthSession),
+                                DLSYM_ARG(Esys_Startup),
+                                DLSYM_ARG(Esys_Unseal),
+                                NULL);
+                if (r < 0)
+                        return r;
+
+                libtss2_esys_dl = TAKE_PTR(dl);
+                k++;
+        }
+
+        if (!libtss2_rc_dl) {
+                _cleanup_(dlclosep) void *dl = NULL;
+
+                dl = dlopen("libtss2-rc.so.0", RTLD_LAZY);
+                if (!dl)
+                        return log_debug_errno(SYNTHETIC_ERRNO(EOPNOTSUPP),
+                                               "TPM2 support is not installed: %s", dlerror());
+
+                r = dlsym_many_and_warn(
+                                dl,
+                                LOG_DEBUG,
+                                DLSYM_ARG(Tss2_RC_Decode),
+                                NULL);
+                if (r < 0)
+                        return r;
+
+                libtss2_rc_dl = TAKE_PTR(dl);
+                k++;
+        }
+
+        if (!libtss2_mu_dl) {
+                _cleanup_(dlclosep) void *dl = NULL;
+
+                dl = dlopen("libtss2-mu.so.0", RTLD_LAZY);
+                if (!dl)
+                        return log_debug_errno(SYNTHETIC_ERRNO(EOPNOTSUPP),
+                                               "TPM2 support is not installed: %s", dlerror());
+
+                r = dlsym_many_and_warn(
+                                dl,
+                                LOG_DEBUG,
+                                DLSYM_ARG(Tss2_MU_TPM2B_PRIVATE_Marshal),
+                                DLSYM_ARG(Tss2_MU_TPM2B_PRIVATE_Unmarshal),
+                                DLSYM_ARG(Tss2_MU_TPM2B_PUBLIC_Marshal),
+                                DLSYM_ARG(Tss2_MU_TPM2B_PUBLIC_Unmarshal),
+                                NULL);
+                if (r < 0)
+                        return r;
+
+                libtss2_mu_dl = TAKE_PTR(dl);
+                k++;
+        }
+
+        return k;
+}
+
+struct tpm2_context {
+        ESYS_CONTEXT *esys_context;
+        void *tcti_dl;
+        TSS2_TCTI_CONTEXT *tcti_context;
+};
+
+static void tpm2_context_destroy(struct tpm2_context *c) {
+        assert(c);
+
+        if (c->esys_context)
+                sym_Esys_Finalize(&c->esys_context);
+
+        c->tcti_context = mfree(c->tcti_context);
+
+        if (c->tcti_dl) {
+                dlclose(c->tcti_dl);
+                c->tcti_dl = NULL;
+        }
+}
+
+static inline void Esys_Finalize_wrapper(ESYS_CONTEXT **c) {
+        /* A wrapper around Esys_Finalize() for use with _cleanup_(). Only reasons we need this wrapper is
+         * because the function itself warn logs if we'd pass a pointer to NULL, and we don't want that. */
+        if (*c)
+                sym_Esys_Finalize(c);
+}
+
+static inline void Esys_Freep(void *p) {
+        if (*(void**) p)
+                sym_Esys_Free(*(void**) p);
+}
+
+static ESYS_TR flush_context_verbose(ESYS_CONTEXT *c, ESYS_TR handle) {
+        TSS2_RC rc;
+
+        if (!c || handle == ESYS_TR_NONE)
+                return ESYS_TR_NONE;
+
+        rc = sym_Esys_FlushContext(c, handle);
+        if (rc != TSS2_RC_SUCCESS) /* We ignore failures here (besides debug logging), since this is called
+                                    * in error paths, where we cannot do anything about failures anymore. And
+                                    * when it is called in successful codepaths by this time we already did
+                                    * what we wanted to do, and got the results we wanted so there's no
+                                    * reason to make this fail more loudly than necessary. */
+                log_debug("Failed to get flush context of TPM, ignoring: %s", sym_Tss2_RC_Decode(rc));
+
+        return ESYS_TR_NONE;
+}
+
+static int tpm2_init(const char *device, struct tpm2_context *ret) {
+        _cleanup_(Esys_Finalize_wrapper) ESYS_CONTEXT *c = NULL;
+        _cleanup_free_ TSS2_TCTI_CONTEXT *tcti = NULL;
+        _cleanup_(dlclosep) void *dl = NULL;
+        TSS2_RC rc;
+        int r;
+
+        r = dlopen_tpm2();
+        if (r < 0)
+                return log_error_errno(r, "TPM2 support not installed: %m");
+
+        if (!device)
+                device = secure_getenv("SYSTEMD_TPM2_DEVICE");
+
+        if (device) {
+                const char *param, *driver, *fn;
+                const TSS2_TCTI_INFO* info;
+                TSS2_TCTI_INFO_FUNC func;
+                size_t sz = 0;
+
+                param = strchr(device, ':');
+                if (param) {
+                        driver = strndupa(device, param - device);
+                        param++;
+                } else {
+                        driver = "device";
+                        param = device;
+                }
+
+                fn = strjoina("libtss2-tcti-", driver, ".so.0");
+
+                dl = dlopen(fn, RTLD_NOW);
+                if (!dl)
+                        return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), "Failed to load %s: %s", fn, dlerror());
+
+                func = dlsym(dl, TSS2_TCTI_INFO_SYMBOL);
+                if (!func)
+                        return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE),
+                                               "Failed to find TCTI info symbol " TSS2_TCTI_INFO_SYMBOL ": %s",
+                                               dlerror());
+
+                info = func();
+                if (!info)
+                        return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), "Unable to get TCTI info data.");
+
+
+                log_debug("Loaded TCTI module '%s' (%s) [Version %" PRIu32 "]", info->name, info->description, info->version);
+
+                rc = info->init(NULL, &sz, NULL);
+                if (rc != TPM2_RC_SUCCESS)
+                        return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE),
+                                               "Failed to initialize TCTI context: %s", sym_Tss2_RC_Decode(rc));
+
+                tcti = malloc0(sz);
+                if (!tcti)
+                        return log_oom();
+
+                rc = info->init(tcti, &sz, device);
+                if (rc != TPM2_RC_SUCCESS)
+                        return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE),
+                                               "Failed to initialize TCTI context: %s", sym_Tss2_RC_Decode(rc));
+        }
+
+        rc = sym_Esys_Initialize(&c, tcti, NULL);
+        if (rc != TSS2_RC_SUCCESS)
+                return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE),
+                                       "Failed to initialize TPM context: %s", sym_Tss2_RC_Decode(rc));
+
+        rc = sym_Esys_Startup(c, TPM2_SU_CLEAR);
+        if (rc == TPM2_RC_INITIALIZE)
+                log_debug("TPM already started up.");
+        else if (rc == TSS2_RC_SUCCESS)
+                log_debug("TPM successfully started up.");
+        else
+                return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE),
+                                       "Failed to start up TPM: %s", sym_Tss2_RC_Decode(rc));
+
+        *ret = (struct tpm2_context) {
+                .esys_context = TAKE_PTR(c),
+                .tcti_context = TAKE_PTR(tcti),
+                .tcti_dl = TAKE_PTR(dl),
+        };
+
+        return 0;
+}
+
+static int tpm2_credit_random(ESYS_CONTEXT *c) {
+        size_t rps, done = 0;
+        TSS2_RC rc;
+        int r;
+
+        assert(c);
+
+        /* Pulls some entropy from the TPM and adds it into the kernel RNG pool. That way we can say that the
+         * key we will ultimately generate with the kernel random pool is at least as good as the TPM's RNG,
+         * but likely better. Note that we don't trust the TPM RNG very much, hence do not actually credit
+         * any entropy. */
+
+        for (rps = random_pool_size(); rps > 0;) {
+                _cleanup_(Esys_Freep) TPM2B_DIGEST *buffer = NULL;
+
+                rc = sym_Esys_GetRandom(
+                                c,
+                                ESYS_TR_NONE,
+                                ESYS_TR_NONE,
+                                ESYS_TR_NONE,
+                                MIN(rps, 32U), /* 32 is supposedly a safe choice, given that AES 256bit keys are this long, and TPM2 baseline requires support for those. */
+                                &buffer);
+                if (rc != TSS2_RC_SUCCESS)
+                        return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE),
+                                               "Failed to acquire entropy from TPM: %s", sym_Tss2_RC_Decode(rc));
+
+                if (buffer->size == 0)
+                        return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE),
+                                               "Zero-sized entropy returned from TPM.");
+
+                r = random_write_entropy(-1, buffer->buffer, buffer->size, false);
+                if (r < 0)
+                        return log_error_errno(r, "Failed wo write entropy to kernel: %m");
+
+                done += buffer->size;
+                rps = LESS_BY(rps, buffer->size);
+        }
+
+        log_debug("Added %zu bytes of entropy to the kernel random pool.", done);
+        return 0;
+}
+
+static int tpm2_make_primary(
+                ESYS_CONTEXT *c,
+                ESYS_TR *ret_primary) {
+
+        static const TPM2B_SENSITIVE_CREATE primary_sensitive = {};
+        static const TPM2B_PUBLIC primary_template = {
+                .size = sizeof(TPMT_PUBLIC),
+                .publicArea = {
+                        .type = TPM2_ALG_ECC,
+                        .nameAlg = TPM2_ALG_SHA256,
+                        .objectAttributes = TPMA_OBJECT_RESTRICTED|TPMA_OBJECT_DECRYPT|TPMA_OBJECT_FIXEDTPM|TPMA_OBJECT_FIXEDPARENT|TPMA_OBJECT_SENSITIVEDATAORIGIN|TPMA_OBJECT_USERWITHAUTH,
+                        .parameters = {
+                                .eccDetail = {
+                                        .symmetric = {
+                                                .algorithm = TPM2_ALG_AES,
+                                                .keyBits.aes = 128,
+                                                .mode.aes = TPM2_ALG_CFB,
+                                        },
+                                        .scheme.scheme = TPM2_ALG_NULL,
+                                        .curveID = TPM2_ECC_NIST_P256,
+                                        .kdf.scheme = TPM2_ALG_NULL,
+                                },
+                        },
+                },
+        };
+        static const TPML_PCR_SELECTION creation_pcr = {};
+        ESYS_TR primary = ESYS_TR_NONE;
+        TSS2_RC rc;
+
+        log_debug("Creating primary key on TPM.");
+
+        rc = sym_Esys_CreatePrimary(
+                        c,
+                        ESYS_TR_RH_OWNER,
+                        ESYS_TR_PASSWORD,
+                        ESYS_TR_NONE,
+                        ESYS_TR_NONE,
+                        &primary_sensitive,
+                        &primary_template,
+                        NULL,
+                        &creation_pcr,
+                        &primary,
+                        NULL,
+                        NULL,
+                        NULL,
+                        NULL);
+
+        if (rc != TSS2_RC_SUCCESS)
+                return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE),
+                                       "Failed to generate primary key in TPM: %s", sym_Tss2_RC_Decode(rc));
+
+        log_debug("Successfully created primary key on TPM.");
+
+        *ret_primary = primary;
+        return 0;
+}
+
+static int tpm2_make_pcr_session(
+                ESYS_CONTEXT *c,
+                uint32_t pcr_mask,
+                ESYS_TR *ret_session,
+                TPM2B_DIGEST **ret_policy_digest) {
+
+        static const TPMT_SYM_DEF symmetric = {
+                .algorithm = TPM2_ALG_AES,
+                .keyBits = {
+                        .aes = 128
+                },
+                .mode = {
+                        .aes = TPM2_ALG_CFB,
+                }
+        };
+        TPML_PCR_SELECTION pcr_selection = {
+                .count = 1,
+                .pcrSelections[0].hash = TPM2_ALG_SHA256,
+                .pcrSelections[0].sizeofSelect = 3,
+                .pcrSelections[0].pcrSelect[0] = pcr_mask & 0xFF,
+                .pcrSelections[0].pcrSelect[1] = (pcr_mask >> 8) & 0xFF,
+                .pcrSelections[0].pcrSelect[2] = (pcr_mask >> 16) & 0xFF,
+        };
+        _cleanup_(Esys_Freep) TPM2B_DIGEST *policy_digest = NULL;
+        ESYS_TR session = ESYS_TR_NONE;
+        TSS2_RC rc;
+        int r;
+
+        assert(c);
+
+        log_debug("Starting authentication session.");
+
+        rc = sym_Esys_StartAuthSession(
+                        c,
+                        ESYS_TR_NONE,
+                        ESYS_TR_NONE,
+                        ESYS_TR_NONE,
+                        ESYS_TR_NONE,
+                        ESYS_TR_NONE,
+                        NULL,
+                        TPM2_SE_POLICY,
+                        &symmetric,
+                        TPM2_ALG_SHA256,
+                        &session);
+        if (rc != TSS2_RC_SUCCESS)
+                return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE),
+                                       "Failed to open session in TPM: %s", sym_Tss2_RC_Decode(rc));
+
+        log_debug("Configuring PCR policy.");
+
+        rc = sym_Esys_PolicyPCR(
+                        c,
+                        session,
+                        ESYS_TR_NONE,
+                        ESYS_TR_NONE,
+                        ESYS_TR_NONE,
+                        NULL,
+                        &pcr_selection);
+        if (rc != TSS2_RC_SUCCESS) {
+                r = log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE),
+                                    "Failed to add PCR policy to TPM: %s", sym_Tss2_RC_Decode(rc));
+                goto finish;
+        }
+
+        if (DEBUG_LOGGING || ret_policy_digest) {
+                log_debug("Acquiring policy digest.");
+
+                rc = sym_Esys_PolicyGetDigest(
+                                c,
+                                session,
+                                ESYS_TR_NONE,
+                                ESYS_TR_NONE,
+                                ESYS_TR_NONE,
+                                &policy_digest);
+
+                if (rc != TSS2_RC_SUCCESS) {
+                        r = log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE),
+                                            "Failed to get policy digest from TPM: %s", sym_Tss2_RC_Decode(rc));
+                        goto finish;
+                }
+
+                if (DEBUG_LOGGING) {
+                        _cleanup_free_ char *h = NULL;
+
+                        h = hexmem(policy_digest->buffer, policy_digest->size);
+                        if (!h) {
+                                r = log_oom();
+                                goto finish;
+                        }
+
+                        log_debug("Session policy digest: %s", h);
+                }
+        }
+
+        if (ret_session) {
+                *ret_session = session;
+                session = ESYS_TR_NONE;
+        }
+
+        if (ret_policy_digest)
+                *ret_policy_digest = TAKE_PTR(policy_digest);
+
+        r = 0;
+
+finish:
+        session = flush_context_verbose(c, session);
+        return r;
+}
+
+int tpm2_seal(
+                const char *device,
+                uint32_t pcr_mask,
+                void **ret_secret,
+                size_t *ret_secret_size,
+                void **ret_blob,
+                size_t *ret_blob_size,
+                void **ret_pcr_hash,
+                size_t *ret_pcr_hash_size) {
+
+        _cleanup_(tpm2_context_destroy) struct tpm2_context c = {};
+        _cleanup_(Esys_Freep) TPM2B_DIGEST *policy_digest = NULL;
+        _cleanup_(Esys_Freep) TPM2B_PRIVATE *private = NULL;
+        _cleanup_(Esys_Freep) TPM2B_PUBLIC *public = NULL;
+        static const TPML_PCR_SELECTION creation_pcr = {};
+        _cleanup_(erase_and_freep) void *secret = NULL;
+        _cleanup_free_ void *blob = NULL, *hash = NULL;
+        TPM2B_SENSITIVE_CREATE hmac_sensitive;
+        ESYS_TR primary = ESYS_TR_NONE;
+        TPM2B_PUBLIC hmac_template;
+        size_t k, blob_size;
+        usec_t start;
+        TSS2_RC rc;
+        int r;
+
+        assert(ret_secret);
+        assert(ret_secret_size);
+        assert(ret_blob);
+        assert(ret_blob_size);
+        assert(ret_pcr_hash);
+        assert(ret_pcr_hash_size);
+
+        assert(pcr_mask < (UINT32_C(1) << TPM2_PCRS_MAX)); /* Support 24 PCR banks */
+
+        /* So here's what we do here: we connect to the TPM2 chip. It persistently contains a "seed" key that
+         * is randomized when the TPM2 is first initialized or reset and remains stable across boots. We
+         * generate a "primary" key pair derived from that (RSA). Given the seed remains fixed this will
+         * result in the same key pair whenever we specify the exact same parameters for it. We then create a
+         * PCR-bound policy session, which calculates a hash on the current PCR values of the indexes we
+         * specify. We then generate a randomized key on the host (which is the key we actually enroll in the
+         * LUKS2 keyslots), which we upload into the TPM2, where it is encrypted with the "primary" key,
+         * taking the PCR policy session into account. We then download the encrypted key from the TPM2
+         * ("sealing") and marshall it into binary form, which is ultimately placed in the LUKS2 JSON header.
+         *
+         * The TPM2 "seed" key and "primary" keys never leave the TPM2 chip (and cannot be extracted at
+         * all). The random key we enroll in LUKS2 we generate on the host using the Linux random device. It
+         * is stored in the LUKS2 JSON only in encrypted form with the "primary" key of the TPM2 chip, thus
+         * binding the unlocking to the TPM2 chip. */
+
+        start = now(CLOCK_MONOTONIC);
+
+        r = tpm2_init(device, &c);
+        if (r < 0)
+                return r;
+
+        r = tpm2_make_primary(c.esys_context, &primary);
+        if (r < 0)
+                return r;
+
+        r = tpm2_make_pcr_session(c.esys_context, pcr_mask, NULL, &policy_digest);
+        if (r < 0)
+                goto finish;
+
+        /* We use a keyed hash object (i.e. HMAC) to store the secret key we want to use for unlocking the
+         * LUKS2 volume with. We don't ever use for HMAC/keyed hash operations however, we just use it
+         * because it's a key type that is universally supported and suitable for symmetric binary blobs. */
+        hmac_template = (TPM2B_PUBLIC) {
+                .size = sizeof(TPMT_PUBLIC),
+                .publicArea = {
+                        .type = TPM2_ALG_KEYEDHASH,
+                        .nameAlg = TPM2_ALG_SHA256,
+                        .objectAttributes = TPMA_OBJECT_FIXEDTPM | TPMA_OBJECT_FIXEDPARENT,
+                        .parameters = {
+                                .keyedHashDetail = {
+                                        .scheme.scheme = TPM2_ALG_NULL,
+                                },
+                        },
+                        .unique = {
+                                .keyedHash = {
+                                        .size = 32,
+                                },
+                        },
+                        .authPolicy = *policy_digest,
+                },
+        };
+
+        hmac_sensitive = (TPM2B_SENSITIVE_CREATE) {
+                .size = sizeof(hmac_sensitive.sensitive),
+                .sensitive.data.size = 32,
+        };
+        assert(sizeof(hmac_sensitive.sensitive.data.buffer) >= hmac_sensitive.sensitive.data.size);
+
+        (void) tpm2_credit_random(c.esys_context);
+
+        log_debug("Generating secret key data.");
+
+        r = genuine_random_bytes(hmac_sensitive.sensitive.data.buffer, hmac_sensitive.sensitive.data.size, RANDOM_BLOCK);
+        if (r < 0) {
+                log_error_errno(r, "Failed to generate secret key: %m");
+                goto finish;
+        }
+
+        log_debug("Creating HMAC key.");
+
+        rc = sym_Esys_Create(
+                        c.esys_context,
+                        primary,
+                        ESYS_TR_PASSWORD,
+                        ESYS_TR_NONE,
+                        ESYS_TR_NONE,
+                        &hmac_sensitive,
+                        &hmac_template,
+                        NULL,
+                        &creation_pcr,
+                        &private,
+                        &public,
+                        NULL,
+                        NULL,
+                        NULL);
+        if (rc != TSS2_RC_SUCCESS) {
+                r = log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE),
+                                    "Failed to generate HMAC key in TPM: %s", sym_Tss2_RC_Decode(rc));
+                goto finish;
+        }
+
+        secret = memdup(hmac_sensitive.sensitive.data.buffer, hmac_sensitive.sensitive.data.size);
+        explicit_bzero_safe(hmac_sensitive.sensitive.data.buffer, hmac_sensitive.sensitive.data.size);
+        if (!secret) {
+                r = log_oom();
+                goto finish;
+        }
+
+        log_debug("Marshalling private and public part of HMAC key.");
+
+        k = ALIGN8(sizeof(*private)) + ALIGN8(sizeof(*public)); /* Some roughly sensible start value */
+        for (;;) {
+                _cleanup_free_ void *buf = NULL;
+                size_t offset = 0;
+
+                buf = malloc(k);
+                if (!buf) {
+                        r = log_oom();
+                        goto finish;
+                }
+
+                rc = sym_Tss2_MU_TPM2B_PRIVATE_Marshal(private, buf, k, &offset);
+                if (rc == TSS2_RC_SUCCESS) {
+                        rc = sym_Tss2_MU_TPM2B_PUBLIC_Marshal(public, buf, k, &offset);
+                        if (rc == TSS2_RC_SUCCESS) {
+                                blob = TAKE_PTR(buf);
+                                blob_size = offset;
+                                break;
+                        }
+                }
+                if (rc != TSS2_MU_RC_INSUFFICIENT_BUFFER) {
+                        r = log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE),
+                                            "Failed to marshal private/public key: %s", sym_Tss2_RC_Decode(rc));
+                        goto finish;
+                }
+
+                if (k > SIZE_MAX / 2) {
+                        r = log_oom();
+                        goto finish;
+                }
+
+                k *= 2;
+        }
+
+        hash = memdup(policy_digest->buffer, policy_digest->size);
+        if (!hash)
+                return log_oom();
+
+        if (DEBUG_LOGGING) {
+                char buf[FORMAT_TIMESPAN_MAX];
+                log_debug("Completed TPM2 key sealing in %s.", format_timespan(buf, sizeof(buf), now(CLOCK_MONOTONIC) - start, 1));
+        }
+
+        *ret_secret = TAKE_PTR(secret);
+        *ret_secret_size = hmac_sensitive.sensitive.data.size;
+        *ret_blob = TAKE_PTR(blob);
+        *ret_blob_size = blob_size;
+        *ret_pcr_hash = TAKE_PTR(hash);
+        *ret_pcr_hash_size = policy_digest->size;
+
+        r = 0;
+
+finish:
+        primary = flush_context_verbose(c.esys_context, primary);
+        return r;
+}
+
+int tpm2_unseal(
+                const char *device,
+                uint32_t pcr_mask,
+                const void *blob,
+                size_t blob_size,
+                const void *known_policy_hash,
+                size_t known_policy_hash_size,
+                void **ret_secret,
+                size_t *ret_secret_size) {
+
+        _cleanup_(tpm2_context_destroy) struct tpm2_context c = {};
+        ESYS_TR primary = ESYS_TR_NONE, session = ESYS_TR_NONE, hmac_key = ESYS_TR_NONE;
+        _cleanup_(Esys_Freep) TPM2B_SENSITIVE_DATA* unsealed = NULL;
+        _cleanup_(Esys_Freep) TPM2B_DIGEST *policy_digest = NULL;
+        _cleanup_(erase_and_freep) char *secret = NULL;
+        TPM2B_PRIVATE private = {};
+        TPM2B_PUBLIC public = {};
+        size_t offset = 0;
+        TSS2_RC rc;
+        usec_t start;
+        int r;
+
+        assert(blob);
+        assert(blob_size > 0);
+        assert(known_policy_hash_size == 0 || known_policy_hash);
+        assert(ret_secret);
+        assert(ret_secret_size);
+
+        assert(pcr_mask < (UINT32_C(1) << TPM2_PCRS_MAX)); /* Support 24 PCR banks */
+
+        /* So here's what we do here: We connect to the TPM2 chip. As we do when sealing we generate a
+         * "primary" key on the TPM2 chip, with the same parameters as well as a PCR-bound policy
+         * session. Given we pass the same parameters, this will result in the same "primary" key, and same
+         * policy hash (the latter of course, only if the PCR values didn't change in between). We unmarshal
+         * the encrypted key we stored in the LUKS2 JSON token header and upload it into the TPM2, where it
+         * is decrypted if the seed and the PCR policy were right ("unsealing"). We then download the result,
+         * and use it to unlock the LUKS2 volume. */
+
+        start = now(CLOCK_MONOTONIC);
+
+        log_debug("Unmarshalling private part of HMAC key.");
+
+        rc = sym_Tss2_MU_TPM2B_PRIVATE_Unmarshal(blob, blob_size, &offset, &private);
+        if (rc != TSS2_RC_SUCCESS)
+                return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE),
+                                       "Failed to unmarshal private key: %s", sym_Tss2_RC_Decode(rc));
+
+        log_debug("Unmarshalling public part of HMAC key.");
+
+        rc = sym_Tss2_MU_TPM2B_PUBLIC_Unmarshal(blob, blob_size, &offset, &public);
+        if (rc != TSS2_RC_SUCCESS)
+                return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE),
+                                       "Failed to unmarshal public key: %s", sym_Tss2_RC_Decode(rc));
+
+        r = tpm2_init(device, &c);
+        if (r < 0)
+                return r;
+
+        r = tpm2_make_pcr_session(c.esys_context, pcr_mask, &session, &policy_digest);
+        if (r < 0)
+                goto finish;
+
+        /* If we know the policy hash to expect, and it doesn't match, we can shortcut things here, and not
+         * wait until the TPM2 tells us to go away. */
+        if (known_policy_hash_size > 0 &&
+            memcmp_nn(policy_digest->buffer, policy_digest->size, known_policy_hash, known_policy_hash_size) != 0)
+                return log_error_errno(SYNTHETIC_ERRNO(EPERM),
+                                       "Current policy digest does not match stored policy digest, cancelling TPM2 authentication attempt.");
+
+        r = tpm2_make_primary(c.esys_context, &primary);
+        if (r < 0)
+                return r;
+
+        log_debug("Loading HMAC key into TPM.");
+
+        rc = sym_Esys_Load(
+                        c.esys_context,
+                        primary,
+                        ESYS_TR_PASSWORD,
+                        ESYS_TR_NONE,
+                        ESYS_TR_NONE,
+                        &private,
+                        &public,
+                        &hmac_key);
+        if (rc != TSS2_RC_SUCCESS) {
+                r = log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE),
+                                    "Failed to load HMAC key in TPM: %s", sym_Tss2_RC_Decode(rc));
+                goto finish;
+        }
+
+        log_debug("Unsealing HMAC key.");
+
+        rc = sym_Esys_Unseal(
+                        c.esys_context,
+                        hmac_key,
+                        session,
+                        ESYS_TR_NONE,
+                        ESYS_TR_NONE,
+                        &unsealed);
+        if (rc != TSS2_RC_SUCCESS) {
+                r = log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE),
+                                    "Failed to unseal HMAC key in TPM: %s", sym_Tss2_RC_Decode(rc));
+                goto finish;
+        }
+
+        secret = memdup(unsealed->buffer, unsealed->size);
+        explicit_bzero_safe(unsealed->buffer, unsealed->size);
+        if (!secret) {
+                r = log_oom();
+                goto finish;
+        }
+
+        if (DEBUG_LOGGING) {
+                char buf[FORMAT_TIMESPAN_MAX];
+                log_debug("Completed TPM2 key unsealing in %s.", format_timespan(buf, sizeof(buf), now(CLOCK_MONOTONIC) - start, 1));
+        }
+
+        *ret_secret = TAKE_PTR(secret);
+        *ret_secret_size = unsealed->size;
+
+        r = 0;
+
+finish:
+        primary = flush_context_verbose(c.esys_context, primary);
+        session = flush_context_verbose(c.esys_context, session);
+        hmac_key = flush_context_verbose(c.esys_context, hmac_key);
+        return r;
+}
+
+#endif
+
+int tpm2_list_devices(void) {
+#if HAVE_TPM2
+        _cleanup_(table_unrefp) Table *t = NULL;
+        _cleanup_(closedirp) DIR *d = NULL;
+        int r;
+
+        r = dlopen_tpm2();
+        if (r < 0)
+                return log_error_errno(r, "TPM2 support is not installed.");
+
+        t = table_new("path", "device", "driver");
+        if (!t)
+                return log_oom();
+
+        d = opendir("/sys/class/tpmrm");
+        if (!d) {
+                log_full_errno(errno == ENOENT ? LOG_DEBUG : LOG_ERR, errno, "Failed to open /sys/class/tpmrm: %m");
+                if (errno != ENOENT)
+                        return -errno;
+        } else {
+                for (;;) {
+                        _cleanup_free_ char *device_path = NULL, *device = NULL, *driver_path = NULL, *driver = NULL, *node = NULL;
+                        struct dirent *de;
+
+                        de = readdir_no_dot(d);
+                        if (!de)
+                                break;
+
+                        device_path = path_join("/sys/class/tpmrm", de->d_name, "device");
+                        if (!device_path)
+                                return log_oom();
+
+                        r = readlink_malloc(device_path, &device);
+                        if (r < 0)
+                                log_debug_errno(r, "Failed to read device symlink %s, ignoring: %m", device_path);
+                        else {
+                                driver_path = path_join(device_path, "driver");
+                                if (!driver_path)
+                                        return log_oom();
+
+                                r = readlink_malloc(driver_path, &driver);
+                                if (r < 0)
+                                        log_debug_errno(r, "Failed to read driver symlink %s, ignoring: %m", driver_path);
+                        }
+
+                        node = path_join("/dev", de->d_name);
+                        if (!node)
+                                return log_oom();
+
+                        r = table_add_many(
+                                        t,
+                                        TABLE_PATH, node,
+                                        TABLE_STRING, device ? last_path_component(device) : NULL,
+                                        TABLE_STRING, driver ? last_path_component(driver) : NULL);
+                        if (r < 0)
+                                return table_log_add_error(r);
+                }
+        }
+
+        if (table_get_rows(t) <= 1) {
+                log_info("No suitable TPM2 devices found.");
+                return 0;
+        }
+
+        r = table_print(t, stdout);
+        if (r < 0)
+                return log_error_errno(r, "Failed to show device table: %m");
+
+        return 0;
+#else
+        return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP),
+                               "TPM2 not supported on this build.");
+#endif
+}
+
+int tpm2_find_device_auto(
+                int log_level, /* log level when no device is found */
+                char **ret) {
+#if HAVE_TPM2
+        _cleanup_(closedirp) DIR *d = NULL;
+        int r;
+
+        r = dlopen_tpm2();
+        if (r < 0)
+                return log_error_errno(r, "TPM2 support is not installed.");
+
+        d = opendir("/sys/class/tpmrm");
+        if (!d) {
+                log_full_errno(errno == ENOENT ? LOG_DEBUG : LOG_ERR, errno,
+                               "Failed to open /sys/class/tpmrm: %m");
+                if (errno != ENOENT)
+                        return -errno;
+        } else {
+                _cleanup_free_ char *node = NULL;
+
+                for (;;) {
+                        struct dirent *de;
+
+                        de = readdir_no_dot(d);
+                        if (!de)
+                                break;
+
+                        if (node)
+                                return log_error_errno(SYNTHETIC_ERRNO(ENOTUNIQ),
+                                                       "More than one TPM2 (tpmrm) device found.");
+
+                        node = path_join("/dev", de->d_name);
+                        if (!node)
+                                return log_oom();
+                }
+
+                if (node) {
+                        *ret = TAKE_PTR(node);
+                        return 0;
+                }
+        }
+
+        return log_full_errno(log_level, SYNTHETIC_ERRNO(ENODEV), "No TPM2 (tpmrm) device found.");
+#else
+        return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP),
+                               "TPM2 not supported on this build.");
+#endif
+}
+
+int tpm2_parse_pcrs(const char *s, uint32_t *ret) {
+        const char *p = s;
+        uint32_t mask = 0;
+        int r;
+
+        /* Parses a comma-separated list of PCR indexes */
+
+        for (;;) {
+                _cleanup_free_ char *pcr = NULL;
+                unsigned n;
+
+                r = extract_first_word(&p, &pcr, ",", EXTRACT_DONT_COALESCE_SEPARATORS);
+                if (r == 0)
+                        break;
+                if (r < 0)
+                        return log_error_errno(r, "Failed to parse PCR list: %s", s);
+
+                r = safe_atou(pcr, &n);
+                if (r < 0)
+                        return log_error_errno(r, "Failed to parse PCR number: %s", pcr);
+                if (n >= TPM2_PCRS_MAX)
+                        return log_error_errno(SYNTHETIC_ERRNO(ERANGE),
+                                               "PCR number out of range (valid range 0…23): %u", n);
+
+                mask |= UINT32_C(1) << n;
+        }
+
+        *ret = mask;
+        return 0;
+}
+
+int tpm2_make_luks2_json(
+                int keyslot,
+                uint32_t pcr_mask,
+                const void *blob,
+                size_t blob_size,
+                const void *policy_hash,
+                size_t policy_hash_size,
+                JsonVariant **ret) {
+
+        _cleanup_(json_variant_unrefp) JsonVariant *v = NULL, *a = NULL;
+        _cleanup_free_ char *keyslot_as_string = NULL;
+        JsonVariant* pcr_array[TPM2_PCRS_MAX];
+        unsigned n_pcrs = 0;
+        int r;
+
+        assert(blob || blob_size == 0);
+        assert(policy_hash || policy_hash_size == 0);
+
+        if (asprintf(&keyslot_as_string, "%i", keyslot) < 0)
+                return -ENOMEM;
+
+        for (unsigned i = 0; i < ELEMENTSOF(pcr_array); i++) {
+                if ((pcr_mask & (UINT32_C(1) << i)) == 0)
+                        continue;
+
+                r = json_variant_new_integer(pcr_array + n_pcrs, i);
+                if (r < 0) {
+                        json_variant_unref_many(pcr_array, n_pcrs);
+                        return -ENOMEM;
+                }
+
+                n_pcrs++;
+        }
+
+        r = json_variant_new_array(&a, pcr_array, n_pcrs);
+        json_variant_unref_many(pcr_array, n_pcrs);
+        if (r < 0)
+                return -ENOMEM;
+
+        r = json_build(&v,
+                       JSON_BUILD_OBJECT(
+                                       JSON_BUILD_PAIR("type", JSON_BUILD_STRING("systemd-tpm2")),
+                                       JSON_BUILD_PAIR("keyslots", JSON_BUILD_ARRAY(JSON_BUILD_STRING(keyslot_as_string))),
+                                       JSON_BUILD_PAIR("tpm2-blob", JSON_BUILD_BASE64(blob, blob_size)),
+                                       JSON_BUILD_PAIR("tpm2-pcrs", JSON_BUILD_VARIANT(a)),
+                                       JSON_BUILD_PAIR("tpm2-policy-hash", JSON_BUILD_HEX(policy_hash, policy_hash_size))));
+        if (r < 0)
+                return r;
+
+        if (ret)
+                *ret = TAKE_PTR(v);
+
+        return keyslot;
+}
diff --git a/src/shared/tpm2-util.h b/src/shared/tpm2-util.h
new file mode 100644 (file)
index 0000000..82cd186
--- /dev/null
@@ -0,0 +1,51 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+#pragma once
+
+#include "json.h"
+#include "macro.h"
+
+#if HAVE_TPM2
+
+#include <tss2/tss2_esys.h>
+#include <tss2/tss2_mu.h>
+#include <tss2/tss2_rc.h>
+
+extern TSS2_RC (*sym_Esys_Create)(ESYS_CONTEXT *esysContext, ESYS_TR parentHandle, ESYS_TR shandle1, ESYS_TR shandle2, ESYS_TR shandle3, const TPM2B_SENSITIVE_CREATE *inSensitive, const TPM2B_PUBLIC *inPublic, const TPM2B_DATA *outsideInfo, const TPML_PCR_SELECTION *creationPCR, TPM2B_PRIVATE **outPrivate, TPM2B_PUBLIC **outPublic, TPM2B_CREATION_DATA **creationData, TPM2B_DIGEST **creationHash, TPMT_TK_CREATION **creationTicket);
+extern TSS2_RC (*sym_Esys_CreatePrimary)(ESYS_CONTEXT *esysContext, ESYS_TR primaryHandle, ESYS_TR shandle1, ESYS_TR shandle2, ESYS_TR shandle3, const TPM2B_SENSITIVE_CREATE *inSensitive, const TPM2B_PUBLIC *inPublic, const TPM2B_DATA *outsideInfo, const TPML_PCR_SELECTION *creationPCR, ESYS_TR *objectHandle, TPM2B_PUBLIC **outPublic, TPM2B_CREATION_DATA **creationData, TPM2B_DIGEST **creationHash, TPMT_TK_CREATION **creationTicket);
+extern void (*sym_Esys_Finalize)(ESYS_CONTEXT **context);
+extern TSS2_RC (*sym_Esys_FlushContext)(ESYS_CONTEXT *esysContext, ESYS_TR flushHandle);
+extern void (*sym_Esys_Free)(void *ptr);
+extern TSS2_RC (*sym_Esys_GetRandom)(ESYS_CONTEXT *esysContext, ESYS_TR shandle1, ESYS_TR shandle2, ESYS_TR shandle3, UINT16 bytesRequested, TPM2B_DIGEST **randomBytes);
+extern TSS2_RC (*sym_Esys_Initialize)(ESYS_CONTEXT **esys_context,  TSS2_TCTI_CONTEXT *tcti, TSS2_ABI_VERSION *abiVersion);
+extern TSS2_RC (*sym_Esys_Load)(ESYS_CONTEXT *esysContext, ESYS_TR parentHandle, ESYS_TR shandle1, ESYS_TR shandle2, ESYS_TR shandle3, const TPM2B_PRIVATE *inPrivate, const TPM2B_PUBLIC *inPublic, ESYS_TR *objectHandle);
+extern TSS2_RC (*sym_Esys_PolicyGetDigest)(ESYS_CONTEXT *esysContext, ESYS_TR policySession, ESYS_TR shandle1, ESYS_TR shandle2, ESYS_TR shandle3, TPM2B_DIGEST **policyDigest);
+extern TSS2_RC (*sym_Esys_PolicyPCR)(ESYS_CONTEXT *esysContext, ESYS_TR policySession, ESYS_TR shandle1, ESYS_TR shandle2, ESYS_TR shandle3, const TPM2B_DIGEST *pcrDigest, const TPML_PCR_SELECTION *pcrs);
+extern TSS2_RC (*sym_Esys_StartAuthSession)(ESYS_CONTEXT *esysContext, ESYS_TR tpmKey, ESYS_TR bind, ESYS_TR shandle1, ESYS_TR shandle2, ESYS_TR shandle3, const TPM2B_NONCE *nonceCaller, TPM2_SE sessionType, const TPMT_SYM_DEF *symmetric, TPMI_ALG_HASH authHash, ESYS_TR *sessionHandle);
+extern TSS2_RC (*sym_Esys_Startup)(ESYS_CONTEXT *esysContext, TPM2_SU startupType);
+extern TSS2_RC (*sym_Esys_Unseal)(ESYS_CONTEXT *esysContext, ESYS_TR itemHandle, ESYS_TR shandle1, ESYS_TR shandle2, ESYS_TR shandle3, TPM2B_SENSITIVE_DATA **outData);
+
+extern const char* (*sym_Tss2_RC_Decode)(TSS2_RC rc);
+
+extern TSS2_RC (*sym_Tss2_MU_TPM2B_PRIVATE_Marshal)(TPM2B_PRIVATE const *src, uint8_t buffer[], size_t buffer_size, size_t *offset);
+extern TSS2_RC (*sym_Tss2_MU_TPM2B_PRIVATE_Unmarshal)(uint8_t const buffer[], size_t buffer_size, size_t *offset, TPM2B_PRIVATE  *dest);
+extern TSS2_RC (*sym_Tss2_MU_TPM2B_PUBLIC_Marshal)(TPM2B_PUBLIC const *src, uint8_t buffer[], size_t buffer_size, size_t *offset);
+extern TSS2_RC (*sym_Tss2_MU_TPM2B_PUBLIC_Unmarshal)(uint8_t const buffer[], size_t buffer_size, size_t *offset, TPM2B_PUBLIC *dest);
+
+int dlopen_tpm2(void);
+
+int tpm2_seal(const char *device, uint32_t pcr_mask, void **ret_secret, size_t *ret_secret_size, void **ret_blob, size_t *ret_blob_size, void **ret_pcr_hash, size_t *ret_pcr_hash_size);
+int tpm2_unseal(const char *device, uint32_t pcr_mask, const void *blob, size_t blob_size, const void *pcr_hash, size_t pcr_hash_size, void **ret_secret, size_t *ret_secret_size);
+
+#endif
+
+int tpm2_list_devices(void);
+int tpm2_find_device_auto(int log_level, char **ret);
+
+int tpm2_parse_pcrs(const char *s, uint32_t *ret);
+
+int tpm2_make_luks2_json(int keyslot, uint32_t pcr_mask, const void *blob, size_t blob_size, const void *policy_hash, size_t policy_hash_size, JsonVariant **ret);
+
+#define TPM2_PCRS_MAX 24
+
+/* Default to PCR 7 only */
+#define TPM2_PCR_MASK_DEFAULT (UINT32_C(1) << 7)