From d9b5841d40996d42a05b7d6f1adf7a7517966262 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Wed, 17 Aug 2022 17:21:57 +0200 Subject: [PATCH] tpm2-util: extend TPM2 policies to optionally check PCR values against signed values 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 | 29 +- src/cryptenroll/cryptenroll-tpm2.h | 2 +- src/cryptsetup/cryptsetup-tokens/luks2-tpm2.c | 9 +- src/cryptsetup/cryptsetup-tpm2.c | 19 +- src/partition/repart.c | 11 +- src/shared/creds-util.c | 15 +- src/shared/openssl-util.h | 8 + src/shared/tpm2-util.c | 480 ++++++++++++++++-- src/shared/tpm2-util.h | 4 +- 9 files changed, 499 insertions(+), 78 deletions(-) diff --git a/src/cryptenroll/cryptenroll-tpm2.c b/src/cryptenroll/cryptenroll-tpm2.c index 01f2e1183c5..ab892d37791 100644 --- a/src/cryptenroll/cryptenroll-tpm2.c +++ b/src/cryptenroll/cryptenroll-tpm2.c @@ -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"); diff --git a/src/cryptenroll/cryptenroll-tpm2.h b/src/cryptenroll/cryptenroll-tpm2.h index 742f49b8d59..36a3097bc29 100644 --- a/src/cryptenroll/cryptenroll-tpm2.h +++ b/src/cryptenroll/cryptenroll-tpm2.h @@ -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), diff --git a/src/cryptsetup/cryptsetup-tokens/luks2-tpm2.c b/src/cryptsetup/cryptsetup-tokens/luks2-tpm2.c index 9f5dd46734f..3d633de3f55 100644 --- a/src/cryptsetup/cryptsetup-tokens/luks2-tpm2.c +++ b/src/cryptsetup/cryptsetup-tokens/luks2-tpm2.c @@ -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); } diff --git a/src/cryptsetup/cryptsetup-tpm2.c b/src/cryptsetup/cryptsetup-tpm2.c index c715c8f232e..c348e73b214 100644 --- a/src/cryptsetup/cryptsetup-tpm2.c +++ b/src/cryptsetup/cryptsetup-tpm2.c @@ -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 diff --git a/src/partition/repart.c b/src/partition/repart.c index 889de712e2c..af7a830377e 100644 --- a/src/partition/repart.c +++ b/src/partition/repart.c @@ -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"); diff --git a/src/shared/creds-util.c b/src/shared/creds-util.c index 03f5fb8c3f7..6f061a1aa3e 100644 --- a/src/shared/creds-util.c +++ b/src/shared/creds-util.c @@ -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) diff --git a/src/shared/openssl-util.h b/src/shared/openssl-util.h index 75ef4ba3d4c..3dda19582bf 100644 --- a/src/shared/openssl-util.h +++ b/src/shared/openssl-util.h @@ -8,9 +8,17 @@ # include # include # include +# include # include # include # include +# 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 +# endif DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(X509*, X509_free, NULL); DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(X509_NAME*, X509_NAME_free, NULL); diff --git a/src/shared/tpm2-util.c b/src/shared/tpm2-util.c index 5ffddf136fa..1546bb02f9b 100644 --- a/src/shared/tpm2-util.c +++ b/src/shared/tpm2-util.c @@ -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, diff --git a/src/shared/tpm2-util.h b/src/shared/tpm2-util.h index cc40027c1bd..7a61b1e797f 100644 --- a/src/shared/tpm2-util.h +++ b/src/shared/tpm2-util.h @@ -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; -- 2.39.2