]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
tpm2-util: extend TPM2 policies to optionally check PCR values against signed values
authorLennart Poettering <lennart@poettering.net>
Wed, 17 Aug 2022 15:21:57 +0000 (17:21 +0200)
committerLennart Poettering <lennart@poettering.net>
Thu, 8 Sep 2022 14:34:27 +0000 (16:34 +0200)
Traditionally, TPM2 PCR policies are bound against literal PCR values,
which makes them hard to work with when updating software that is
measured into PCRs: each update will change the PCR values, and thus
break TPM2 policies of existing objects.

Let's improve the situation: let's allow signed PCR policies. Secrets
and other TPM2 objects can be associated with a public key that signs a
PCR policy. Thus, if the signed policy and the public key is presented,
access to the TPM2 object can be granted. This allows a less brittle
handling of updates: for example, whenever a kernel image is updated a
new signed PCR policy can be shipped along with it, signed by a private
key owned by the kernel vendor (ideally: same private key that is used
to sign the kernel image itself). TPM2 objects can then be bound to the
associated public key, thus allowing objects that can only be unlocked
by kernels of the same vendor. This makes it very easy to update kernels
without affecting locked secrets.

This does not hook up any of the consuming code (just passes NULL/0
everywhere). This is for later commits.

src/cryptenroll/cryptenroll-tpm2.c
src/cryptenroll/cryptenroll-tpm2.h
src/cryptsetup/cryptsetup-tokens/luks2-tpm2.c
src/cryptsetup/cryptsetup-tpm2.c
src/partition/repart.c
src/shared/creds-util.c
src/shared/openssl-util.h
src/shared/tpm2-util.c
src/shared/tpm2-util.h

index 01f2e1183c5e9693a467221fa6dae6d4785190f0..ab892d3779122500eb7b699503805fbb08734284 100644 (file)
@@ -130,7 +130,7 @@ int enroll_tpm2(struct crypt_device *cd,
                 const void *volume_key,
                 size_t volume_key_size,
                 const char *device,
-                uint32_t pcr_mask,
+                uint32_t hash_pcr_mask,
                 bool use_pin) {
 
         _cleanup_(erase_and_freep) void *secret = NULL, *secret2 = NULL;
@@ -147,7 +147,7 @@ int enroll_tpm2(struct crypt_device *cd,
         assert(cd);
         assert(volume_key);
         assert(volume_key_size > 0);
-        assert(TPM2_PCR_MASK_VALID(pcr_mask));
+        assert(TPM2_PCR_MASK_VALID(hash_pcr_mask));
 
         assert_se(node = crypt_get_device_name(cd));
 
@@ -157,7 +157,16 @@ int enroll_tpm2(struct crypt_device *cd,
                         return r;
         }
 
-        r = tpm2_seal(device, pcr_mask, pin_str, &secret, &secret_size, &blob, &blob_size, &hash, &hash_size, &pcr_bank, &primary_alg);
+        r = tpm2_seal(device,
+                      hash_pcr_mask,
+                      /* pubkey= */ NULL, /* pubkey_size= */ 0,
+                      /* pubkey_pcr_mask= */ 0,
+                      pin_str,
+                      &secret, &secret_size,
+                      &blob, &blob_size,
+                      &hash, &hash_size,
+                      &pcr_bank,
+                      &primary_alg);
         if (r < 0)
                 return r;
 
@@ -174,7 +183,17 @@ int enroll_tpm2(struct crypt_device *cd,
 
         /* 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, pcr_bank, primary_alg, blob, blob_size, hash, hash_size, pin_str, &secret2, &secret2_size);
+        r = tpm2_unseal(device,
+                        hash_pcr_mask,
+                        pcr_bank,
+                        /* pubkey= */ NULL, /* pubkey_size= */ 0,
+                        /* pubkey_pcr_mask= */ 0,
+                        /* signature= */ NULL,
+                        pin_str,
+                        primary_alg,
+                        blob, blob_size,
+                        hash, hash_size,
+                        &secret2, &secret2_size);
         if (r < 0)
                 return r;
 
@@ -200,7 +219,7 @@ int enroll_tpm2(struct crypt_device *cd,
         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, pcr_bank, primary_alg, blob, blob_size, hash, hash_size, flags, &v);
+        r = tpm2_make_luks2_json(keyslot, hash_pcr_mask, pcr_bank, primary_alg, blob, blob_size, hash, hash_size, flags, &v);
         if (r < 0)
                 return log_error_errno(r, "Failed to prepare TPM2 JSON token object: %m");
 
index 742f49b8d5927eac9c6cb614aee4368695cbe209..36a3097bc290be7d62d62dcfb2fdbe72157881a9 100644 (file)
@@ -7,7 +7,7 @@
 #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, bool use_pin);
+int enroll_tpm2(struct crypt_device *cd, const void *volume_key, size_t volume_key_size, const char *device, uint32_t hash_pcr_mask, bool use_pin);
 #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, bool use_pin) {
         return log_debug_errno(SYNTHETIC_ERRNO(EOPNOTSUPP),
index 9f5dd46734f48ef04025b83f9fd7831b96e21c41..3d633de3f55c0dd6198f91f97182627571196948 100644 (file)
@@ -47,10 +47,15 @@ int acquire_luks2_key(
 
         return tpm2_unseal(
                         device,
-                        pcr_mask, pcr_bank,
+                        pcr_mask,
+                        pcr_bank,
+                        /* pubkey= */ NULL, /* pubkey_size= */ 0,
+                        /* pubkey_pcr_mask= */ 0,
+                        /* signature_json= */ NULL,
+                        pin,
                         primary_alg,
                         key_data, key_data_size,
-                        policy_hash, policy_hash_size, pin,
+                        policy_hash, policy_hash_size,
                         ret_decrypted_key, ret_decrypted_key_size);
 }
 
index c715c8f232e7fccc44c47a17f78b20ea9153445d..c348e73b2143ec4a0027605b2af034c261de080d 100644 (file)
@@ -55,7 +55,7 @@ static int get_pin(usec_t until, AskPasswordFlags ask_password_flags, bool headl
 int acquire_tpm2_key(
                 const char *volume_name,
                 const char *device,
-                uint32_t pcr_mask,
+                uint32_t hash_pcr_mask,
                 uint16_t pcr_bank,
                 uint16_t primary_alg,
                 const char *key_file,
@@ -114,14 +114,17 @@ int acquire_tpm2_key(
         if (!(flags & TPM2_FLAGS_USE_PIN))
                 return tpm2_unseal(
                                 device,
-                                pcr_mask,
+                                hash_pcr_mask,
                                 pcr_bank,
+                                /* pubkey= */ NULL, /* pubkey_size= */ 0,
+                                /* pubkey_pcr_mask= */ 0,
+                                /* signature= */ NULL,
+                                /* pin= */ NULL,
                                 primary_alg,
                                 blob,
                                 blob_size,
                                 policy_hash,
                                 policy_hash_size,
-                                NULL,
                                 ret_decrypted_key,
                                 ret_decrypted_key_size);
 
@@ -135,16 +138,18 @@ int acquire_tpm2_key(
                 if (r < 0)
                         return r;
 
-                r = tpm2_unseal(
-                                device,
-                                pcr_mask,
+                r = tpm2_unseal(device,
+                                hash_pcr_mask,
                                 pcr_bank,
+                                /* pubkey= */ NULL, /* pubkey_size= */ 0,
+                                /* pubkey_pcr_mask= */ 0,
+                                /* signature= */ NULL,
+                                pin_str,
                                 primary_alg,
                                 blob,
                                 blob_size,
                                 policy_hash,
                                 policy_hash_size,
-                                pin_str,
                                 ret_decrypted_key,
                                 ret_decrypted_key_size);
                 /* We get this error in case there is an authentication policy mismatch. This should
index 889de712e2c8da6ae1a93916d05bb28a906d151f..af7a830377e3c3700a13404f21efeaf3b34b0951 100644 (file)
@@ -2919,7 +2919,16 @@ static int partition_encrypt(
                 uint16_t pcr_bank, primary_alg;
                 int keyslot;
 
-                r = tpm2_seal(arg_tpm2_device, arg_tpm2_pcr_mask, NULL, &secret, &secret_size, &blob, &blob_size, &hash, &hash_size, &pcr_bank, &primary_alg);
+                r = tpm2_seal(arg_tpm2_device,
+                              arg_tpm2_pcr_mask,
+                              /* pubkey= */ NULL, /* pubkey_size= */ 0,
+                              /* pubkey_pcr_mask= */ 0,
+                              /* pin= */ NULL,
+                              &secret, &secret_size,
+                              &blob, &blob_size,
+                              &hash, &hash_size,
+                              &pcr_bank,
+                              &primary_alg);
                 if (r < 0)
                         return log_error_errno(r, "Failed to seal to TPM2: %m");
 
index 03f5fb8c3f7534ed5627e7790df8200bd037c115..6f061a1aa3e979aa80de83914c5bce19fa1dfc11 100644 (file)
@@ -518,7 +518,7 @@ int encrypt_credential_and_warn(
                 usec_t timestamp,
                 usec_t not_after,
                 const char *tpm2_device,
-                uint32_t tpm2_pcr_mask,
+                uint32_t tpm2_hash_pcr_mask,
                 const void *input,
                 size_t input_size,
                 void **ret,
@@ -608,8 +608,10 @@ int encrypt_credential_and_warn(
 
         if (try_tpm2) {
                 r = tpm2_seal(tpm2_device,
-                              tpm2_pcr_mask,
-                              NULL,
+                              tpm2_hash_pcr_mask,
+                              /* pubkey= */ NULL, /* pubkey_size= */ 0,
+                              /* pubkey_pcr_mask= */ 0,
+                              /* pin= */ NULL,
                               &tpm2_key,
                               &tpm2_key_size,
                               &tpm2_blob,
@@ -716,7 +718,7 @@ int encrypt_credential_and_warn(
                 struct tpm2_credential_header *t;
 
                 t = (struct tpm2_credential_header*) ((uint8_t*) output + p);
-                t->pcr_mask = htole64(tpm2_pcr_mask);
+                t->pcr_mask = htole64(tpm2_hash_pcr_mask);
                 t->pcr_bank = htole16(tpm2_pcr_bank);
                 t->primary_alg = htole16(tpm2_primary_alg);
                 t->blob_size = htole32(tpm2_blob_size);
@@ -902,12 +904,15 @@ int decrypt_credential_and_warn(
                 r = tpm2_unseal(tpm2_device,
                                 le64toh(t->pcr_mask),
                                 le16toh(t->pcr_bank),
+                                /* pubkey= */ NULL, /* pubkey_size= */ 0,
+                                /* pubkey_pcr_mask= */ 0,
+                                /* signature= */ NULL,
+                                /* pin= */ NULL,
                                 le16toh(t->primary_alg),
                                 t->policy_hash_and_blob,
                                 le32toh(t->blob_size),
                                 t->policy_hash_and_blob + le32toh(t->blob_size),
                                 le32toh(t->policy_hash_size),
-                                NULL,
                                 &tpm2_key,
                                 &tpm2_key_size);
                 if (r < 0)
index 75ef4ba3d4c9534f5ef7fc1f340f793c8f7931d0..3dda19582bfc60d20dc8007852fb53f19fa85049 100644 (file)
@@ -8,9 +8,17 @@
 #  include <openssl/bn.h>
 #  include <openssl/err.h>
 #  include <openssl/evp.h>
+#  include <openssl/opensslv.h>
 #  include <openssl/pkcs7.h>
 #  include <openssl/ssl.h>
 #  include <openssl/x509v3.h>
+#  ifndef OPENSSL_VERSION_MAJOR
+/* OPENSSL_VERSION_MAJOR macro was added in OpenSSL 3. Thus, if it doesn't exist,  we must be before OpenSSL 3. */
+#    define OPENSSL_VERSION_MAJOR 1
+#  endif
+#  if OPENSSL_VERSION_MAJOR >= 3
+#    include <openssl/core_names.h>
+#  endif
 
 DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(X509*, X509_free, NULL);
 DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(X509_NAME*, X509_NAME_free, NULL);
index 5ffddf136fa77d6a4a9613a3448abcbe17a381f2..1546bb02f9b4c53642cc5e9eeb6dd1bca72ef9b4 100644 (file)
@@ -16,6 +16,7 @@
 #include "fs-util.h"
 #include "hexdecoct.h"
 #include "memory-util.h"
+#include "openssl-util.h"
 #include "random-util.h"
 #include "sha256.h"
 #include "time-util.h"
@@ -708,13 +709,197 @@ static int tpm2_make_encryption_session(
         return 0;
 }
 
-static int tpm2_make_pcr_session(
+static int openssl_pubkey_to_tpm2_pubkey(EVP_PKEY *input, TPM2B_PUBLIC *output) {
+#if OPENSSL_VERSION_MAJOR >= 3
+        _cleanup_(BN_freep) BIGNUM *n = NULL, *e = NULL;
+#else
+        const BIGNUM *n = NULL, *e = NULL;
+        const RSA *rsa = NULL;
+#endif
+        int n_bytes, e_bytes;
+
+        assert(input);
+        assert(output);
+
+        /* Converts an OpenSSL public key to a structure that the TPM chip can process. */
+
+        if (EVP_PKEY_base_id(input) != EVP_PKEY_RSA)
+                return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Provided public key is not an RSA key.");
+
+#if OPENSSL_VERSION_MAJOR >= 3
+        if (!EVP_PKEY_get_bn_param(input, OSSL_PKEY_PARAM_RSA_N, &n))
+                return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to get RSA modulus from public key.");
+#else
+        rsa = EVP_PKEY_get0_RSA(input);
+        if (!rsa)
+                return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to extract RSA key from public key.");
+
+        n = RSA_get0_n(rsa);
+        if (!n)
+                return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to get RSA modulus from public key.");
+#endif
+
+        n_bytes = BN_num_bytes(n);
+        assert_se(n_bytes > 0);
+        if ((size_t) n_bytes > sizeof_field(TPM2B_PUBLIC, publicArea.unique.rsa.buffer))
+                return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "RSA modulus too large for TPM2 public key object.");
+
+#if OPENSSL_VERSION_MAJOR >= 3
+        if (!EVP_PKEY_get_bn_param(input, OSSL_PKEY_PARAM_RSA_E, &e))
+                return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to get RSA exponent from public key.");
+#else
+        e = RSA_get0_e(rsa);
+        if (!e)
+                return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to get RSA exponent from public key.");
+#endif
+
+        e_bytes = BN_num_bytes(e);
+        assert_se(e_bytes > 0);
+        if ((size_t) e_bytes > sizeof_field(TPM2B_PUBLIC, publicArea.parameters.rsaDetail.exponent))
+                return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "RSA exponent too large for TPM2 public key object.");
+
+        *output = (TPM2B_PUBLIC) {
+                .size = sizeof(TPMT_PUBLIC),
+                .publicArea = {
+                        .type = TPM2_ALG_RSA,
+                        .nameAlg = TPM2_ALG_SHA256,
+                        .objectAttributes = TPMA_OBJECT_DECRYPT | TPMA_OBJECT_SIGN_ENCRYPT | TPMA_OBJECT_USERWITHAUTH,
+                        .parameters.rsaDetail = {
+                                .scheme = {
+                                        .scheme = TPM2_ALG_NULL,
+                                        .details.anySig.hashAlg = TPM2_ALG_NULL,
+                                },
+                                .symmetric = {
+                                        .algorithm = TPM2_ALG_NULL,
+                                        .mode.sym = TPM2_ALG_NULL,
+                                },
+                                .keyBits = n_bytes * 8,
+                                /* .exponent will be filled in below. */
+                        },
+                        .unique = {
+                                .rsa.size = n_bytes,
+                                /* .rsa.buffer will be filled in below. */
+                        },
+                },
+        };
+
+        if (BN_bn2bin(n, output->publicArea.unique.rsa.buffer) <= 0)
+                return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to convert RSA modulus.");
+
+        if (BN_bn2bin(e, (unsigned char*) &output->publicArea.parameters.rsaDetail.exponent) <= 0)
+                return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to convert RSA exponent.");
+
+        return 0;
+}
+
+static int find_signature(
+                JsonVariant *v,
+                uint16_t pcr_bank,
+                uint32_t pcr_mask,
+                EVP_PKEY *pk,
+                const void *policy,
+                size_t policy_size,
+                void *ret_signature,
+                size_t *ret_signature_size) {
+
+        _cleanup_free_ void *fp = NULL;
+        JsonVariant *b, *i;
+        size_t fp_size;
+        const char *k;
+        int r;
+
+        /* Searches for a signature blob in the specified JSON object. Search keys are PCR bank, PCR mask,
+         * public key, and policy digest. */
+
+        if (!json_variant_is_object(v))
+                return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Signature is not a JSON object.");
+
+        k = tpm2_pcr_bank_to_string(pcr_bank);
+        if (!k)
+                return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "Don't know PCR bank %" PRIu16, pcr_bank);
+
+        /* First, find field by bank */
+        b = json_variant_by_key(v, k);
+        if (!b)
+                return log_error_errno(SYNTHETIC_ERRNO(ENXIO), "Signature lacks data for PCR bank '%s'.", k);
+
+        if (!json_variant_is_array(b))
+                return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Bank data is not a JSON array.");
+
+        /* Now iterate through all signatures known for this bank */
+        JSON_VARIANT_ARRAY_FOREACH(i, b) {
+                _cleanup_free_ void *fpj_data = NULL, *polj_data = NULL;
+                JsonVariant *maskj, *fpj, *sigj, *polj;
+                size_t fpj_size, polj_size;
+                uint32_t parsed_mask;
+
+                if (!json_variant_is_object(i))
+                        return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Bank data element is not a JSON object");
+
+                /* Check if the PCR mask matches our expectations */
+                maskj = json_variant_by_key(i, "pcrs");
+                if (!maskj)
+                        continue;
+
+                r = tpm2_parse_pcr_json_array(maskj, &parsed_mask);
+                if (r < 0)
+                        return log_error_errno(r, "Failed to parse JSON PCR mask");
+
+                if (parsed_mask != pcr_mask)
+                        continue; /* Not for this PCR mask */
+
+                /* Then check if this is for the public key we operate with */
+                fpj = json_variant_by_key(i, "pkfp");
+                if (!fpj)
+                        continue;
+
+                r = json_variant_unhex(fpj, &fpj_data, &fpj_size);
+                if (r < 0)
+                        return log_error_errno(r, "Failed to decode fingerprint in JSON data: %m");
+
+                if (!fp) {
+                        r = pubkey_fingerprint(pk, EVP_sha256(), &fp, &fp_size);
+                        if (r < 0)
+                                return log_error_errno(r, "Failed to calculate public key fingerprint: %m");
+                }
+
+                if (memcmp_nn(fp, fp_size, fpj_data, fpj_size) != 0)
+                        continue; /* Not for this public key */
+
+                /* Finally, check if this is for the PCR policy we expect this to be */
+                polj = json_variant_by_key(i, "pol");
+                if (!polj)
+                        continue;
+
+                r = json_variant_unhex(polj, &polj_data, &polj_size);
+                if (r < 0)
+                        return log_error_errno(r, "Failed to decode policy hash JSON data: %m");
+
+                if (memcmp_nn(policy, policy_size, polj_data, polj_size) != 0)
+                        continue;
+
+                /* This entry matches all our expectations, now return the signature included in it */
+                sigj = json_variant_by_key(i, "sig");
+                if (!sigj)
+                        continue;
+
+                return json_variant_unbase64(sigj, ret_signature, ret_signature_size);
+        }
+
+        return log_error_errno(SYNTHETIC_ERRNO(ENXIO), "Couldn't find signature for this PCR bank, PCR index and public key.");
+}
+
+static int tpm2_make_policy_session(
                 ESYS_CONTEXT *c,
                 ESYS_TR primary,
                 ESYS_TR parent_session,
                 TPM2_SE session_type,
-                uint32_t pcr_mask,
+                uint32_t hash_pcr_mask,
                 uint16_t pcr_bank, /* If UINT16_MAX, pick best bank automatically, otherwise specify bank explicitly. */
+                const void *pubkey,
+                size_t pubkey_size,
+                uint32_t pubkey_pcr_mask,
+                JsonVariant *signature_json,
                 bool use_pin,
                 ESYS_TR *ret_session,
                 TPM2B_DIGEST **ret_policy_digest,
@@ -726,33 +911,44 @@ static int tpm2_make_pcr_session(
                 .mode.aes = TPM2_ALG_CFB,
         };
         _cleanup_(Esys_Freep) TPM2B_DIGEST *policy_digest = NULL;
-        TPML_PCR_SELECTION pcr_selection;
-        ESYS_TR session = ESYS_TR_NONE;
+        ESYS_TR session = ESYS_TR_NONE, pubkey_handle = ESYS_TR_NONE;
+        _cleanup_(EVP_PKEY_freep) EVP_PKEY *pk = NULL;
         TSS2_RC rc;
         int r;
 
         assert(c);
+        assert(pubkey || pubkey_size == 0);
+        assert(pubkey_pcr_mask == 0 || pubkey_size > 0);
 
         log_debug("Starting authentication session.");
 
-        if (pcr_bank != UINT16_MAX) {
-                r = tpm2_pcr_mask_good(c, pcr_bank, pcr_mask);
-                if (r < 0)
-                        return r;
-                if (r == 0)
-                        log_notice("Selected TPM2 PCRs are not initialized on this system, most likely due to a firmware issue. PCR policy is effectively not enforced. Proceeding anyway.");
+        if ((hash_pcr_mask | pubkey_pcr_mask) != 0) {
+                /* We are told to configure a PCR policy of some form, let's determine/validate the PCR bank to use. */
 
-                tpm2_pcr_mask_to_selection(pcr_mask, pcr_bank, &pcr_selection);
-        } else {
-                TPMI_ALG_HASH h;
+                if (pcr_bank != UINT16_MAX) {
+                        r = tpm2_pcr_mask_good(c, pcr_bank, hash_pcr_mask|pubkey_pcr_mask);
+                        if (r < 0)
+                                return r;
+                        if (r == 0)
+                                log_warning("Selected TPM2 PCRs are not initialized on this system, most likely due to a firmware issue. PCR policy is effectively not enforced. Proceeding anyway.");
+                } else {
+                        /* No bank configured, pick automatically. Some TPM2 devices only can do SHA1. If we
+                         * detect that use that, but preferably use SHA256 */
+                        r = tpm2_get_best_pcr_bank(c, hash_pcr_mask|pubkey_pcr_mask, &pcr_bank);
+                        if (r < 0)
+                                return r;
+                }
+        }
 
-                /* No bank configured, pick automatically. Some TPM2 devices only can do SHA1. If we detect
-                 * that use that, but preferably use SHA256 */
-                r = tpm2_get_best_pcr_bank(c, pcr_mask, &h);
-                if (r < 0)
-                        return r;
+        if (pubkey_size > 0) {
+                /* If a pubkey is specified, load it to validate it, even if the PCR mask for this is actually zero, and we are thus not going to use it. */
+                _cleanup_fclose_ FILE *f = fmemopen((void*) pubkey, pubkey_size, "r");
+                if (!f)
+                        return log_oom();
 
-                tpm2_pcr_mask_to_selection(pcr_mask, h, &pcr_selection);
+                pk = PEM_read_PUBKEY(f, NULL, NULL, NULL);
+                if (!pk)
+                        return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to parse PEM public key.");
         }
 
         rc = sym_Esys_StartAuthSession(
@@ -771,23 +967,180 @@ static int tpm2_make_pcr_session(
                 return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE),
                                        "Failed to open session in TPM: %s", sym_Tss2_RC_Decode(rc));
 
-        log_debug("Configuring PCR policy.");
+        if (pubkey_pcr_mask != 0) {
+                log_debug("Configuring public key based 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;
+                /* First: load public key into the TPM */
+                TPM2B_PUBLIC pubkey_tpm2;
+                r = openssl_pubkey_to_tpm2_pubkey(pk, &pubkey_tpm2);
+                if (r < 0)
+                        goto finish;
+
+                rc = sym_Esys_LoadExternal(
+                                c,
+                                ESYS_TR_NONE,
+                                ESYS_TR_NONE,
+                                ESYS_TR_NONE,
+                                NULL,
+                                &pubkey_tpm2,
+                                TPM2_RH_OWNER,
+                                &pubkey_handle);
+                if (rc != TSS2_RC_SUCCESS) {
+                        r = log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE),
+                                            "Failed to load public key into TPM: %s", sym_Tss2_RC_Decode(rc));
+                        goto finish;
+                }
+
+                /* Acquire the "name" of what we just loaded */
+                _cleanup_(Esys_Freep) TPM2B_NAME *pubkey_name = NULL;
+                rc = sym_Esys_TR_GetName(
+                                c,
+                                pubkey_handle,
+                                &pubkey_name);
+                if (rc != TSS2_RC_SUCCESS) {
+                        r = log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE),
+                                            "Failed to get name of public key from TPM: %s", sym_Tss2_RC_Decode(rc));
+                        goto finish;
+                }
+
+                /* Put together the PCR policy we want to use */
+                TPML_PCR_SELECTION pcr_selection;
+                tpm2_pcr_mask_to_selection(pubkey_pcr_mask, pcr_bank, &pcr_selection);
+                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;
+                }
+
+                /* Get the policy hash of the PCR policy */
+                _cleanup_(Esys_Freep) TPM2B_DIGEST *approved_policy = NULL;
+                rc = sym_Esys_PolicyGetDigest(
+                                c,
+                                session,
+                                ESYS_TR_NONE,
+                                ESYS_TR_NONE,
+                                ESYS_TR_NONE,
+                                &approved_policy);
+                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;
+                }
+
+                /* When we are unlocking and have a signature, let's pass it to the TPM */
+                _cleanup_(Esys_Freep) TPMT_TK_VERIFIED *check_ticket_buffer = NULL;
+                const TPMT_TK_VERIFIED *check_ticket;
+                if (signature_json) {
+                        _cleanup_free_ void *signature_raw = NULL;
+                        size_t signature_size;
+
+                        r = find_signature(
+                                        signature_json,
+                                        pcr_bank,
+                                        pubkey_pcr_mask,
+                                        pk,
+                                        approved_policy->buffer,
+                                        approved_policy->size,
+                                        &signature_raw,
+                                        &signature_size);
+                        if (r < 0)
+                                goto finish;
+
+                        /* TPM2_VerifySignature() will only verify the RSA part of the RSA+SHA256 signature,
+                         * hence we need to do the SHA256 part outselves, first */
+                        TPM2B_DIGEST signature_hash = {
+                                .size = SHA256_DIGEST_SIZE,
+                        };
+                        assert(sizeof(signature_hash.buffer) >= SHA256_DIGEST_SIZE);
+                        sha256_direct(approved_policy->buffer, approved_policy->size, signature_hash.buffer);
+
+                        TPMT_SIGNATURE policy_signature = {
+                                .sigAlg = TPM2_ALG_RSASSA,
+                                .signature.rsassa = {
+                                        .hash = TPM2_ALG_SHA256,
+                                        .sig.size = signature_size,
+                                },
+                        };
+                        if (signature_size > sizeof(policy_signature.signature.rsassa.sig.buffer)) {
+                                r = log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), "Signature larger than buffer.");
+                                goto finish;
+                        }
+                        memcpy(policy_signature.signature.rsassa.sig.buffer, signature_raw, signature_size);
+
+                        rc = sym_Esys_VerifySignature(
+                                        c,
+                                        pubkey_handle,
+                                        ESYS_TR_NONE,
+                                        ESYS_TR_NONE,
+                                        ESYS_TR_NONE,
+                                        &signature_hash,
+                                        &policy_signature,
+                                        &check_ticket_buffer);
+                        if (rc != TSS2_RC_SUCCESS) {
+                                r = log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE),
+                                                    "Failed to validate signature in TPM: %s", sym_Tss2_RC_Decode(rc));
+                                goto finish;
+                        }
+
+                        check_ticket = check_ticket_buffer;
+                } else {
+                        /* When enrolling, we pass a NULL ticket */
+                        static const TPMT_TK_VERIFIED check_ticket_null = {
+                                .tag = TPM2_ST_VERIFIED,
+                                .hierarchy = TPM2_RH_OWNER,
+                        };
+
+                        check_ticket = &check_ticket_null;
+                }
+
+                rc = sym_Esys_PolicyAuthorize(
+                                c,
+                                session,
+                                ESYS_TR_NONE,
+                                ESYS_TR_NONE,
+                                ESYS_TR_NONE,
+                                approved_policy,
+                                /* policyRef= */ &(const TPM2B_NONCE) {},
+                                pubkey_name,
+                                check_ticket);
+                if (rc != TSS2_RC_SUCCESS) {
+                        r = log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE),
+                                            "Failed to push Authorize policy into TPM: %s", sym_Tss2_RC_Decode(rc));
+                        goto finish;
+                }
+        }
+
+        if (hash_pcr_mask != 0) {
+                log_debug("Configuring hash-based PCR policy.");
+
+                TPML_PCR_SELECTION pcr_selection;
+                tpm2_pcr_mask_to_selection(hash_pcr_mask, pcr_bank, &pcr_selection);
+                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 (use_pin) {
+                log_debug("Configuring PIN policy.");
+
                 rc = sym_Esys_PolicyAuthValue(
                                 c,
                                 session,
@@ -841,27 +1194,30 @@ static int tpm2_make_pcr_session(
                 *ret_policy_digest = TAKE_PTR(policy_digest);
 
         if (ret_pcr_bank)
-                *ret_pcr_bank = pcr_selection.pcrSelections[0].hash;
+                *ret_pcr_bank = pcr_bank;
 
         r = 0;
 
 finish:
         session = tpm2_flush_context_verbose(c, session);
+        pubkey_handle = tpm2_flush_context_verbose(c, pubkey_handle);
         return r;
 }
 
-int tpm2_seal(
-                const char *device,
-                uint32_t pcr_mask,
-                const char *pin,
-                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,
-                uint16_t *ret_pcr_bank,
-                uint16_t *ret_primary_alg) {
+int tpm2_seal(const char *device,
+              uint32_t hash_pcr_mask,
+              const void *pubkey,
+              const size_t pubkey_size,
+              uint32_t pubkey_pcr_mask,
+              const char *pin,
+              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,
+              uint16_t *ret_pcr_bank,
+              uint16_t *ret_primary_alg) {
 
         _cleanup_(tpm2_context_destroy) struct tpm2_context c = {};
         _cleanup_(Esys_Freep) TPM2B_DIGEST *policy_digest = NULL;
@@ -880,6 +1236,8 @@ int tpm2_seal(
         TSS2_RC rc;
         int r;
 
+        assert(pubkey || pubkey_size == 0);
+
         assert(ret_secret);
         assert(ret_secret_size);
         assert(ret_blob);
@@ -888,7 +1246,8 @@ int tpm2_seal(
         assert(ret_pcr_hash_size);
         assert(ret_pcr_bank);
 
-        assert(TPM2_PCR_MASK_VALID(pcr_mask));
+        assert(TPM2_PCR_MASK_VALID(hash_pcr_mask));
+        assert(TPM2_PCR_MASK_VALID(pubkey_pcr_mask));
 
         /* 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
@@ -921,13 +1280,16 @@ int tpm2_seal(
         if (r < 0)
                 goto finish;
 
-        r = tpm2_make_pcr_session(
+        r = tpm2_make_policy_session(
                         c.esys_context,
                         primary,
                         session,
                         TPM2_SE_TRIAL,
-                        pcr_mask,
+                        hash_pcr_mask,
                         /* pcr_bank= */ UINT16_MAX,
+                        pubkey, pubkey_size,
+                        pubkey_pcr_mask,
+                        /* signature_json= */ NULL,
                         !!pin,
                         /* ret_session= */ NULL,
                         &policy_digest,
@@ -1060,16 +1422,19 @@ finish:
         return r;
 }
 
-int tpm2_unseal(
-                const char *device,
-                uint32_t pcr_mask,
+int tpm2_unseal(const char *device,
+                uint32_t hash_pcr_mask,
                 uint16_t pcr_bank,
+                const void *pubkey,
+                size_t pubkey_size,
+                uint32_t pubkey_pcr_mask,
+                JsonVariant *signature,
+                const char *pin,
                 uint16_t primary_alg,
                 const void *blob,
                 size_t blob_size,
                 const void *known_policy_hash,
                 size_t known_policy_hash_size,
-                const char *pin,
                 void **ret_secret,
                 size_t *ret_secret_size) {
 
@@ -1089,10 +1454,12 @@ int tpm2_unseal(
         assert(blob);
         assert(blob_size > 0);
         assert(known_policy_hash_size == 0 || known_policy_hash);
+        assert(pubkey_size == 0 || pubkey);
         assert(ret_secret);
         assert(ret_secret_size);
 
-        assert(TPM2_PCR_MASK_VALID(pcr_mask));
+        assert(TPM2_PCR_MASK_VALID(hash_pcr_mask));
+        assert(TPM2_PCR_MASK_VALID(pubkey_pcr_mask));
 
         r = dlopen_tpm2();
         if (r < 0)
@@ -1166,13 +1533,16 @@ int tpm2_unseal(
         if (r < 0)
                 goto finish;
 
-        r = tpm2_make_pcr_session(
+        r = tpm2_make_policy_session(
                         c.esys_context,
                         primary,
                         hmac_session,
                         TPM2_SE_POLICY,
-                        pcr_mask,
+                        hash_pcr_mask,
                         pcr_bank,
+                        pubkey, pubkey_size,
+                        pubkey_pcr_mask,
+                        signature,
                         !!pin,
                         &session,
                         &policy_digest,
index cc40027c1bd9c69bbecf09644512c75f5acb732a..7a61b1e797fd0d886c16ffa49591f83700525f75 100644 (file)
@@ -48,8 +48,8 @@ extern TSS2_RC (*sym_Tss2_MU_TPM2B_PUBLIC_Unmarshal)(uint8_t const buffer[], siz
 
 int dlopen_tpm2(void);
 
-int tpm2_seal(const char *device, uint32_t pcr_mask, const char *pin, 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, uint16_t *ret_pcr_bank, uint16_t *ret_primary_alg);
-int tpm2_unseal(const char *device, uint32_t pcr_mask, uint16_t pcr_bank, uint16_t primary_alg, const void *blob, size_t blob_size, const void *pcr_hash, size_t pcr_hash_size, const char *pin, void **ret_secret, size_t *ret_secret_size);
+int tpm2_seal(const char *device, uint32_t hash_pcr_mask, const void *pubkey, size_t pubkey_size, uint32_t pubkey_pcr_mask, const char *pin, 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, uint16_t *ret_pcr_bank, uint16_t *ret_primary_alg);
+int tpm2_unseal(const char *device, uint32_t hash_pcr_mask, uint16_t pcr_bank, const void *pubkey, size_t pubkey_size, uint32_t pubkey_pcr_mask, JsonVariant *signature, const char *pin, uint16_t primary_alg, const void *blob, size_t blob_size, const void *policy_hash, size_t policy_hash_size, void **ret_secret, size_t *ret_secret_size);
 
 struct tpm2_context {
         void *tcti_dl;