From 3a35d6cdd29f0303b9fffff2f34461b2be0cb1c7 Mon Sep 17 00:00:00 2001 From: Dan Streetman Date: Sun, 1 Jan 2023 20:19:12 -0500 Subject: [PATCH 1/2] tpm2: add tpm2_get_capability(), tpm2_cache_capabilities(), tpm2_capability_pcrs() This adds a function to query specific capabilities from the TPM. That is then used in a function to query the allocation of PCRs in the TPM, i.e. which PCR banks and indexes are available, and caches the PCR allocation when the TPM context is created. --- src/shared/tpm2-util.c | 159 ++++++++++++++++++++++++++++------------- src/shared/tpm2-util.h | 3 + 2 files changed, 113 insertions(+), 49 deletions(-) diff --git a/src/shared/tpm2-util.c b/src/shared/tpm2-util.c index 680cc726c3..73c7a3faf0 100644 --- a/src/shared/tpm2-util.c +++ b/src/shared/tpm2-util.c @@ -133,6 +133,94 @@ static inline void Esys_Freep(void *p) { sym_Esys_Free(*(void**) p); } +/* Get a specific TPM capability (or capabilities). + * + * Returns 0 if there are no more capability properties of the requested type, or 1 if there are more, or < 0 + * on any error. Both 0 and 1 indicate this completed successfully, but do not indicate how many capability + * properties were provided in 'ret_capability_data'. To find the number of provided properties, check the + * specific type's 'count' field (e.g. for TPM2_CAP_ALGS, check ret_capability_data->algorithms.count). + * + * This calls TPM2_GetCapability() and does not alter the provided data, so it is important to understand how + * that TPM function works. It is recommended to check the TCG TPM specification Part 3 ("Commands") section + * on TPM2_GetCapability() for full details, but a short summary is: if this returns 0, all available + * properties have been provided in ret_capability_data, or no properties were available. If this returns 1, + * there are between 1 and "count" properties provided in ret_capability_data, and there are more available. + * Note that this may provide less than "count" properties even if the TPM has more available. Also, each + * capability category may have more specific requirements than described here; see the spec for exact + * details. */ +static int tpm2_get_capability( + Tpm2Context *c, + TPM2_CAP capability, + uint32_t property, + uint32_t count, + TPMU_CAPABILITIES *ret_capability_data) { + + _cleanup_(Esys_Freep) TPMS_CAPABILITY_DATA *capabilities = NULL; + TPMI_YES_NO more; + TSS2_RC rc; + + assert(c); + + log_debug("Getting TPM2 capability 0x%04" PRIx32 " property 0x%04" PRIx32 " count %" PRIu32 ".", + capability, property, count); + + rc = sym_Esys_GetCapability( + c->esys_context, + ESYS_TR_NONE, + ESYS_TR_NONE, + ESYS_TR_NONE, + capability, + property, + count, + &more, + &capabilities); + if (rc != TSS2_RC_SUCCESS) + return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), + "Failed to get TPM2 capability 0x%04" PRIx32 " property 0x%04" PRIx32 ": %s", + capability, property, sym_Tss2_RC_Decode(rc)); + + if (capabilities->capability != capability) + return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), + "TPM provided wrong capability: 0x%04" PRIx32 " instead of 0x%04" PRIx32 ".", + capabilities->capability, capability); + + if (ret_capability_data) + *ret_capability_data = capabilities->data; + + return more == TPM2_YES; +} + +static int tpm2_cache_capabilities(Tpm2Context *c) { + TPMU_CAPABILITIES capability; + int r; + + assert(c); + + /* Cache the PCR capabilities, which are safe to cache, as the only way they can change is + * TPM2_PCR_Allocate(), which changes the allocation after the next _TPM_Init(). If the TPM is + * reinitialized while we are using it, all our context and sessions will be invalid, so we can + * safely assume the TPM PCR allocation will not change while we are using it. */ + r = tpm2_get_capability( + c, + TPM2_CAP_PCRS, + /* property= */ 0, + /* count= */ 1, + &capability); + if (r < 0) + return r; + if (r == 1) + /* This should never happen. Part 3 ("Commands") of the TCG TPM2 spec in the section for + * TPM2_GetCapability states: "TPM_CAP_PCRS – Returns the current allocation of PCR in a + * TPML_PCR_SELECTION. The property parameter shall be zero. The TPM will always respond to + * this command with the full PCR allocation and moreData will be NO." */ + log_warning("TPM bug: reported multiple PCR sets; using only first set."); + c->capability_pcrs = capability.assignedPCR; + + return 0; +} + +#define tpm2_capability_pcrs(c) ((c)->capability_pcrs) + static Tpm2Context *tpm2_context_free(Tpm2Context *c) { if (!c) return NULL; @@ -255,6 +343,10 @@ int tpm2_context_new(const char *device, Tpm2Context **ret_context) { return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), "Failed to start up TPM: %s", sym_Tss2_RC_Decode(rc)); + r = tpm2_cache_capabilities(context); + if (r < 0) + return r; + *ret_context = TAKE_PTR(context); return 0; @@ -1180,48 +1272,33 @@ static int tpm2_get_best_pcr_bank( uint32_t pcr_mask, TPMI_ALG_HASH *ret) { - _cleanup_(Esys_Freep) TPMS_CAPABILITY_DATA *pcap = NULL; + TPML_PCR_SELECTION pcrs; TPMI_ALG_HASH supported_hash = 0, hash_with_valid_pcr = 0; - TPMI_YES_NO more; - TSS2_RC rc; int r; assert(c); + assert(ret); - rc = sym_Esys_GetCapability( - c->esys_context, - ESYS_TR_NONE, - ESYS_TR_NONE, - ESYS_TR_NONE, - TPM2_CAP_PCRS, - 0, - 1, - &more, - &pcap); - if (rc != TSS2_RC_SUCCESS) - return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), - "Failed to determine TPM2 PCR bank capabilities: %s", sym_Tss2_RC_Decode(rc)); - - assert(pcap->capability == TPM2_CAP_PCRS); - - for (size_t i = 0; i < pcap->data.assignedPCR.count; i++) { + pcrs = tpm2_capability_pcrs(c); + FOREACH_TPMS_PCR_SELECTION_IN_TPML_PCR_SELECTION(selection, &pcrs) { + TPMI_ALG_HASH hash = selection->hash; int good; /* For now we are only interested in the SHA1 and SHA256 banks */ - if (!IN_SET(pcap->data.assignedPCR.pcrSelections[i].hash, TPM2_ALG_SHA256, TPM2_ALG_SHA1)) + if (!IN_SET(hash, TPM2_ALG_SHA256, TPM2_ALG_SHA1)) continue; - r = tpm2_bank_has24(pcap->data.assignedPCR.pcrSelections + i); + r = tpm2_bank_has24(selection); if (r < 0) return r; if (!r) continue; - good = tpm2_pcr_mask_good(c, pcap->data.assignedPCR.pcrSelections[i].hash, pcr_mask); + good = tpm2_pcr_mask_good(c, hash, pcr_mask); if (good < 0) return good; - if (pcap->data.assignedPCR.pcrSelections[i].hash == TPM2_ALG_SHA256) { + if (hash == TPM2_ALG_SHA256) { supported_hash = TPM2_ALG_SHA256; if (good) { /* Great, SHA256 is supported and has initialized PCR values, we are done. */ @@ -1229,7 +1306,7 @@ static int tpm2_get_best_pcr_bank( break; } } else { - assert(pcap->data.assignedPCR.pcrSelections[i].hash == TPM2_ALG_SHA1); + assert(hash == TPM2_ALG_SHA1); if (supported_hash == 0) supported_hash = TPM2_ALG_SHA1; @@ -1278,42 +1355,26 @@ int tpm2_get_good_pcr_banks( TPMI_ALG_HASH **ret) { _cleanup_free_ TPMI_ALG_HASH *good_banks = NULL, *fallback_banks = NULL; - _cleanup_(Esys_Freep) TPMS_CAPABILITY_DATA *pcap = NULL; + TPML_PCR_SELECTION pcrs; size_t n_good_banks = 0, n_fallback_banks = 0; - TPMI_YES_NO more; - TSS2_RC rc; int r; assert(c); assert(ret); - rc = sym_Esys_GetCapability( - c->esys_context, - ESYS_TR_NONE, - ESYS_TR_NONE, - ESYS_TR_NONE, - TPM2_CAP_PCRS, - 0, - 1, - &more, - &pcap); - if (rc != TSS2_RC_SUCCESS) - return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), - "Failed to determine TPM2 PCR bank capabilities: %s", sym_Tss2_RC_Decode(rc)); - - assert(pcap->capability == TPM2_CAP_PCRS); - - for (size_t i = 0; i < pcap->data.assignedPCR.count; i++) { + pcrs = tpm2_capability_pcrs(c); + FOREACH_TPMS_PCR_SELECTION_IN_TPML_PCR_SELECTION(selection, &pcrs) { + TPMI_ALG_HASH hash = selection->hash; /* Let's see if this bank is superficially OK, i.e. has at least 24 enabled registers */ - r = tpm2_bank_has24(pcap->data.assignedPCR.pcrSelections + i); + r = tpm2_bank_has24(selection); if (r < 0) return r; if (!r) continue; /* Let's now see if this bank has any of the selected PCRs actually initialized */ - r = tpm2_pcr_mask_good(c, pcap->data.assignedPCR.pcrSelections[i].hash, pcr_mask); + r = tpm2_pcr_mask_good(c, hash, pcr_mask); if (r < 0) return r; @@ -1324,12 +1385,12 @@ int tpm2_get_good_pcr_banks( if (!GREEDY_REALLOC(good_banks, n_good_banks+1)) return log_oom(); - good_banks[n_good_banks++] = pcap->data.assignedPCR.pcrSelections[i].hash; + good_banks[n_good_banks++] = hash; } else { if (!GREEDY_REALLOC(fallback_banks, n_fallback_banks+1)) return log_oom(); - fallback_banks[n_fallback_banks++] = pcap->data.assignedPCR.pcrSelections[i].hash; + fallback_banks[n_fallback_banks++] = hash; } } diff --git a/src/shared/tpm2-util.h b/src/shared/tpm2-util.h index 964471098f..2d46377356 100644 --- a/src/shared/tpm2-util.h +++ b/src/shared/tpm2-util.h @@ -67,6 +67,9 @@ typedef struct { void *tcti_dl; TSS2_TCTI_CONTEXT *tcti_context; ESYS_CONTEXT *esys_context; + + /* Some selected cached capabilities of the TPM */ + TPML_PCR_SELECTION capability_pcrs; } Tpm2Context; int tpm2_context_new(const char *device, Tpm2Context **ret_context); From a47060bb34c912ea9909fcf617f7b553488b5daf Mon Sep 17 00:00:00 2001 From: Dan Streetman Date: Fri, 17 Feb 2023 12:59:18 -0500 Subject: [PATCH 2/2] tpm2: verify symmetric parms in tpm2_context_new() This adds tpm2_get_capability_algs(), tpm2_supports_alg(), and tpm2_test_parms(). These functions allow verifying that the TPM supports specific algs and parameters. When creating a new context, this checks if the TPM supports the symmetric algs we use. If the TPM does not support the symmetric algs and parameters we require, we log and return error. --- src/shared/tpm2-util.c | 119 ++++++++++++++++++++++++++++++++++++----- src/shared/tpm2-util.h | 4 ++ src/test/test-tpm2.c | 38 +++++++++++++ 3 files changed, 149 insertions(+), 12 deletions(-) diff --git a/src/shared/tpm2-util.c b/src/shared/tpm2-util.c index 73c7a3faf0..b5c9e6f2f8 100644 --- a/src/shared/tpm2-util.c +++ b/src/shared/tpm2-util.c @@ -52,6 +52,7 @@ static TSS2_RC (*sym_Esys_PolicyPCR)(ESYS_CONTEXT *esysContext, ESYS_TR policySe static TSS2_RC (*sym_Esys_ReadPublic)(ESYS_CONTEXT *esysContext, ESYS_TR objectHandle, ESYS_TR shandle1, ESYS_TR shandle2, ESYS_TR shandle3, TPM2B_PUBLIC **outPublic, TPM2B_NAME **name, TPM2B_NAME **qualifiedName) = NULL; static 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; static TSS2_RC (*sym_Esys_Startup)(ESYS_CONTEXT *esysContext, TPM2_SU startupType) = NULL; +static TSS2_RC (*sym_Esys_TestParms)(ESYS_CONTEXT *esysContext, ESYS_TR shandle1, ESYS_TR shandle2, ESYS_TR shandle3, const TPMT_PUBLIC_PARMS *parameters) = NULL; static TSS2_RC (*sym_Esys_TR_Deserialize)(ESYS_CONTEXT *esys_context, uint8_t const *buffer, size_t buffer_size, ESYS_TR *esys_handle) = NULL; static TSS2_RC (*sym_Esys_TR_FromTPMPublic)(ESYS_CONTEXT *esysContext, TPM2_HANDLE tpm_handle, ESYS_TR optionalSession1, ESYS_TR optionalSession2, ESYS_TR optionalSession3, ESYS_TR *object) = NULL; static TSS2_RC (*sym_Esys_TR_GetName)(ESYS_CONTEXT *esysContext, ESYS_TR handle, TPM2B_NAME **name) = NULL; @@ -98,6 +99,7 @@ int dlopen_tpm2(void) { DLSYM_ARG(Esys_ReadPublic), DLSYM_ARG(Esys_StartAuthSession), DLSYM_ARG(Esys_Startup), + DLSYM_ARG(Esys_TestParms), DLSYM_ARG(Esys_TR_Deserialize), DLSYM_ARG(Esys_TR_FromTPMPublic), DLSYM_ARG(Esys_TR_GetName), @@ -221,6 +223,87 @@ static int tpm2_cache_capabilities(Tpm2Context *c) { #define tpm2_capability_pcrs(c) ((c)->capability_pcrs) +/* Get the TPMA_ALGORITHM for a TPM2_ALG_ID. + * + * Returns 1 if the TPM supports the algorithm and the TPMA_ALGORITHM is provided, or 0 if the TPM does not + * support the algorithm, or < 0 for any errors. */ +static int tpm2_get_capability_alg(Tpm2Context *c, TPM2_ALG_ID alg, TPMA_ALGORITHM *ret) { + TPMU_CAPABILITIES capability; + int r; + + assert(c); + + /* The spec explicitly states the TPM2_ALG_ID should be cast to uint32_t. */ + r = tpm2_get_capability(c, TPM2_CAP_ALGS, (uint32_t) alg, 1, &capability); + if (r < 0) + return r; + + TPML_ALG_PROPERTY algorithms = capability.algorithms; + if (algorithms.count == 0 || algorithms.algProperties[0].alg != alg) { + log_debug("TPM does not support alg 0x%02" PRIx16 ".", alg); + return 0; + } + + if (ret) + *ret = algorithms.algProperties[0].algProperties; + + return 1; +} + +/* Returns 1 if the TPM supports the alg, 0 if the TPM does not support the alg, or < 0 for any error. */ +int tpm2_supports_alg(Tpm2Context *c, TPM2_ALG_ID alg) { + return tpm2_get_capability_alg(c, alg, NULL); +} + +/* Returns 1 if the TPM supports the parms, or 0 if the TPM does not support the parms. */ +bool tpm2_test_parms(Tpm2Context *c, TPMI_ALG_PUBLIC alg, const TPMU_PUBLIC_PARMS *parms) { + TSS2_RC rc; + + assert(c); + assert(parms); + + TPMT_PUBLIC_PARMS parameters = { + .type = alg, + .parameters = *parms, + }; + + rc = sym_Esys_TestParms(c->esys_context, ESYS_TR_NONE, ESYS_TR_NONE, ESYS_TR_NONE, ¶meters); + if (rc != TSS2_RC_SUCCESS) + /* The spec says if the parms are not supported the TPM returns "...the appropriate + * unmarshaling error if a parameter is not valid". Since the spec (currently) defines 15 + * unmarshaling errors, instead of checking for them all here, let's just assume any error + * indicates unsupported parms, and log the specific error text. */ + log_debug("TPM does not support tested parms: %s", sym_Tss2_RC_Decode(rc)); + + return rc == TSS2_RC_SUCCESS; +} + +static inline bool tpm2_supports_tpmt_sym_def_object(Tpm2Context *c, const TPMT_SYM_DEF_OBJECT *parameters) { + assert(c); + assert(parameters); + + TPMU_PUBLIC_PARMS parms = { + .symDetail.sym = *parameters, + }; + + return tpm2_test_parms(c, TPM2_ALG_SYMCIPHER, &parms); +} + +static inline bool tpm2_supports_tpmt_sym_def(Tpm2Context *c, const TPMT_SYM_DEF *parameters) { + assert(c); + assert(parameters); + + /* Unfortunately, TPMT_SYM_DEF and TPMT_SYM_DEF_OBEJECT are separately defined, even though they are + * functionally identical. */ + TPMT_SYM_DEF_OBJECT object = { + .algorithm = parameters->algorithm, + .keyBits = parameters->keyBits, + .mode = parameters->mode, + }; + + return tpm2_supports_tpmt_sym_def_object(c, &object); +} + static Tpm2Context *tpm2_context_free(Tpm2Context *c) { if (!c) return NULL; @@ -236,6 +319,12 @@ static Tpm2Context *tpm2_context_free(Tpm2Context *c) { DEFINE_TRIVIAL_REF_UNREF_FUNC(Tpm2Context, tpm2_context, tpm2_context_free); +static const TPMT_SYM_DEF SESSION_TEMPLATE_SYM_AES_128_CFB = { + .algorithm = TPM2_ALG_AES, + .keyBits.aes = 128, + .mode.aes = TPM2_ALG_CFB, /* The spec requires sessions to use CFB. */ +}; + int tpm2_context_new(const char *device, Tpm2Context **ret_context) { _cleanup_tpm2_context_ Tpm2Context *context = NULL; TSS2_RC rc; @@ -347,6 +436,22 @@ int tpm2_context_new(const char *device, Tpm2Context **ret_context) { if (r < 0) return r; + /* We require AES and CFB support for session encryption. */ + r = tpm2_supports_alg(context, TPM2_ALG_AES); + if (r < 0) + return r; + if (r == 0) + return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), "TPM does not support AES."); + + r = tpm2_supports_alg(context, TPM2_ALG_CFB); + if (r < 0) + return r; + if (r == 0) + return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), "TPM does not support CFB."); + + if (!tpm2_supports_tpmt_sym_def(context, &SESSION_TEMPLATE_SYM_AES_128_CFB)) + return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), "TPM does not support AES-128-CFB."); + *ret_context = TAKE_PTR(context); return 0; @@ -1581,11 +1686,6 @@ static int tpm2_make_encryption_session( const Tpm2Handle *bind_key, Tpm2Handle **ret_session) { - static const TPMT_SYM_DEF symmetric = { - .algorithm = TPM2_ALG_AES, - .keyBits.aes = 128, - .mode.aes = TPM2_ALG_CFB, - }; const TPMA_SESSION sessionAttributes = TPMA_SESSION_DECRYPT | TPMA_SESSION_ENCRYPT | TPMA_SESSION_CONTINUESESSION; TSS2_RC rc; @@ -1613,7 +1713,7 @@ static int tpm2_make_encryption_session( ESYS_TR_NONE, NULL, TPM2_SE_HMAC, - &symmetric, + &SESSION_TEMPLATE_SYM_AES_128_CFB, TPM2_ALG_SHA256, &session->esys_handle); if (rc != TSS2_RC_SUCCESS) @@ -1642,11 +1742,6 @@ static int tpm2_make_policy_session( bool trial, Tpm2Handle **ret_session) { - static const TPMT_SYM_DEF symmetric = { - .algorithm = TPM2_ALG_AES, - .keyBits.aes = 128, - .mode.aes = TPM2_ALG_CFB, - }; TPM2_SE session_type = trial ? TPM2_SE_TRIAL : TPM2_SE_POLICY; TSS2_RC rc; int r; @@ -1676,7 +1771,7 @@ static int tpm2_make_policy_session( ESYS_TR_NONE, NULL, session_type, - &symmetric, + &SESSION_TEMPLATE_SYM_AES_128_CFB, TPM2_ALG_SHA256, &session->esys_handle); if (rc != TSS2_RC_SUCCESS) diff --git a/src/shared/tpm2-util.h b/src/shared/tpm2-util.h index 2d46377356..de8bbf1cbe 100644 --- a/src/shared/tpm2-util.h +++ b/src/shared/tpm2-util.h @@ -92,6 +92,10 @@ Tpm2Handle *tpm2_handle_free(Tpm2Handle *handle); DEFINE_TRIVIAL_CLEANUP_FUNC(Tpm2Handle*, tpm2_handle_free); #define _cleanup_tpm2_handle_ _cleanup_(tpm2_handle_freep) +int tpm2_supports_alg(Tpm2Context *c, TPM2_ALG_ID alg); + +bool tpm2_test_parms(Tpm2Context *c, TPMI_ALG_PUBLIC alg, const TPMU_PUBLIC_PARMS *parms); + int tpm2_get_good_pcr_banks(Tpm2Context *c, uint32_t pcr_mask, TPMI_ALG_HASH **ret_banks); int tpm2_get_good_pcr_banks_strv(Tpm2Context *c, uint32_t pcr_mask, char ***ret); diff --git a/src/test/test-tpm2.c b/src/test/test-tpm2.c index e878ab33f2..a785b28699 100644 --- a/src/test/test-tpm2.c +++ b/src/test/test-tpm2.c @@ -760,6 +760,44 @@ TEST(calculate_policy_pcr) { assert_se(digest_check(&d, "7481fd1b116078eb3ac2456e4ad542c9b46b9b8eb891335771ca8e7c8f8e4415")); } +TEST(tpm_required_tests) { + int r; + + _cleanup_tpm2_context_ Tpm2Context *c = NULL; + r = tpm2_context_new(NULL, &c); + if (r < 0) { + log_tests_skipped("Could not find TPM"); + return; + } + + TPMU_PUBLIC_PARMS parms = { + .symDetail.sym = { + .algorithm = TPM2_ALG_AES, + .keyBits.aes = 128, + .mode.aes = TPM2_ALG_CFB, + }, + }; + + /* Test with invalid parms */ + assert_se(!tpm2_test_parms(c, TPM2_ALG_CFB, &parms)); + + TPMU_PUBLIC_PARMS invalid_parms = parms; + invalid_parms.symDetail.sym.keyBits.aes = 1; + assert_se(!tpm2_test_parms(c, TPM2_ALG_SYMCIPHER, &invalid_parms)); + + /* Test with valid parms */ + assert_se(tpm2_test_parms(c, TPM2_ALG_SYMCIPHER, &parms)); + + /* Test invalid algs */ + assert_se(tpm2_supports_alg(c, TPM2_ALG_ERROR) == 0); + assert_se(tpm2_supports_alg(c, TPM2_ALG_LAST + 1) == 0); + + /* Test valid algs */ + assert_se(tpm2_supports_alg(c, TPM2_ALG_RSA) == 1); + assert_se(tpm2_supports_alg(c, TPM2_ALG_AES) == 1); + assert_se(tpm2_supports_alg(c, TPM2_ALG_CFB) == 1); +} + #endif /* HAVE_TPM2 */ DEFINE_TEST_MAIN(LOG_DEBUG);