From c8a85240316898a6de95c9b2565edd08f8450182 Mon Sep 17 00:00:00 2001 From: Dan Streetman Date: Fri, 17 Feb 2023 12:50:31 -0500 Subject: [PATCH 1/3] tpm2: add tpm2_get_capability_handle(), tpm2_esys_handle_from_tpm_handle() Add tpm2_get_capability_handle() to query if a "TPM handle" (meaning, a location/address in TPM storage) is populated in the TPM, and tpm2_get_capability_handles() to query for a specific number of handles. Add tpm2_esys_handle_from_tpm_handle() to create an "esys handle" (an opaque reference for use with the TPM EAPI that represents a TPM handle address) for an existing TPM handle. Since the TPM handle already exists in the TPM, this also also requires updating the cleanup code for Tpm2Handle objects to close the object (free its resources only from the EAPI code, but leave the handle in the TPM) instead of flush the object (which frees its EAPI resources and removes it from the TPM). --- src/shared/tpm2-util.c | 244 ++++++++++++++++++++++++++++++++--------- src/shared/tpm2-util.h | 3 +- 2 files changed, 193 insertions(+), 54 deletions(-) diff --git a/src/shared/tpm2-util.c b/src/shared/tpm2-util.c index 6214aff7d9..49e4ea12ef 100644 --- a/src/shared/tpm2-util.c +++ b/src/shared/tpm2-util.c @@ -53,6 +53,7 @@ static TSS2_RC (*sym_Esys_ReadPublic)(ESYS_CONTEXT *esysContext, ESYS_TR objectH 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_Close)(ESYS_CONTEXT *esys_context, ESYS_TR *rsrc_handle) = 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; @@ -100,6 +101,7 @@ int dlopen_tpm2(void) { DLSYM_ARG(Esys_StartAuthSession), DLSYM_ARG(Esys_Startup), DLSYM_ARG(Esys_TestParms), + DLSYM_ARG(Esys_TR_Close), DLSYM_ARG(Esys_TR_Deserialize), DLSYM_ARG(Esys_TR_FromTPMPublic), DLSYM_ARG(Esys_TR_GetName), @@ -255,6 +257,84 @@ int tpm2_supports_alg(Tpm2Context *c, TPM2_ALG_ID alg) { return tpm2_get_capability_alg(c, alg, NULL); } +/* Query the TPM for populated handles. + * + * This provides an array of handle indexes populated in the TPM, starting at the requested handle. The array will + * contain only populated handle addresses (which might not include the requested handle). The number of + * handles will be no more than the 'max' number requested. This will not search past the end of the handle + * range (i.e. handle & 0xff000000). + * + * Returns 0 if all populated handles in the range (starting at the requested handle) were provided (or no + * handles were in the range), or 1 if there are more populated handles in the range, or < 0 on any error. */ +static int tpm2_get_capability_handles( + Tpm2Context *c, + TPM2_HANDLE start, + size_t max, + TPM2_HANDLE **ret_handles, + size_t *ret_n_handles) { + + _cleanup_free_ TPM2_HANDLE *handles = NULL; + size_t n_handles = 0; + TPM2_HANDLE current = start; + int r = 0; + + assert(c); + assert(ret_handles); + assert(ret_n_handles); + + while (max > 0) { + TPMU_CAPABILITIES capability; + r = tpm2_get_capability(c, TPM2_CAP_HANDLES, current, (uint32_t) max, &capability); + if (r < 0) + return r; + + TPML_HANDLE handle_list = capability.handles; + if (handle_list.count == 0) + break; + + assert(handle_list.count <= max); + + if (n_handles > SIZE_MAX - handle_list.count) + return log_oom(); + + if (!GREEDY_REALLOC(handles, n_handles + handle_list.count)) + return log_oom(); + + memcpy_safe(&handles[n_handles], handle_list.handle, sizeof(handles[0]) * handle_list.count); + + max -= handle_list.count; + n_handles += handle_list.count; + + /* Update current to the handle index after the last handle in the list. */ + current = handles[n_handles - 1] + 1; + + if (r == 0) + /* No more handles in this range. */ + break; + } + + *ret_handles = TAKE_PTR(handles); + *ret_n_handles = n_handles; + + return r; +} + +#define TPM2_HANDLE_RANGE(h) ((TPM2_HANDLE)((h) & TPM2_HR_RANGE_MASK)) +#define TPM2_HANDLE_TYPE(h) ((TPM2_HT)(TPM2_HANDLE_RANGE(h) >> TPM2_HR_SHIFT)) + +/* Returns 1 if the handle is populated in the TPM, 0 if not, and < 0 on any error. */ +static int tpm2_get_capability_handle(Tpm2Context *c, TPM2_HANDLE handle) { + _cleanup_free_ TPM2_HANDLE *handles = NULL; + size_t n_handles = 0; + int r; + + r = tpm2_get_capability_handles(c, handle, 1, &handles, &n_handles); + if (r < 0) + return r; + + return n_handles == 0 ? false : handles[0] == handle; +} + /* 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; @@ -457,17 +537,25 @@ int tpm2_context_new(const char *device, Tpm2Context **ret_context) { return 0; } -static void tpm2_handle_flush(ESYS_CONTEXT *esys_context, ESYS_TR esys_handle) { +static void tpm2_handle_cleanup(ESYS_CONTEXT *esys_context, ESYS_TR esys_handle, bool flush) { + TSS2_RC rc; + if (!esys_context || esys_handle == ESYS_TR_NONE) return; - TSS2_RC rc = sym_Esys_FlushContext(esys_context, esys_handle); + /* Closing the handle removes its reference from the esys_context, but leaves the corresponding + * handle in the actual TPM. Flushing the handle removes its reference from the esys_context as well + * as removing its corresponding handle from the actual TPM. */ + if (flush) + rc = sym_Esys_FlushContext(esys_context, esys_handle); + else + rc = sym_Esys_TR_Close(esys_context, &esys_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 flush TPM handle, ignoring: %s", sym_Tss2_RC_Decode(rc)); + log_debug("Failed to %s TPM handle, ignoring: %s", flush ? "flush" : "close", sym_Tss2_RC_Decode(rc)); } Tpm2Handle *tpm2_handle_free(Tpm2Handle *handle) { @@ -475,8 +563,8 @@ Tpm2Handle *tpm2_handle_free(Tpm2Handle *handle) { return NULL; _cleanup_(tpm2_context_unrefp) Tpm2Context *context = (Tpm2Context*)handle->tpm2_context; - if (context && !handle->keep) - tpm2_handle_flush(context->esys_context, handle->esys_handle); + if (context) + tpm2_handle_cleanup(context->esys_context, handle->esys_handle, handle->flush); return mfree(handle); } @@ -493,6 +581,7 @@ int tpm2_handle_new(Tpm2Context *context, Tpm2Handle **ret_handle) { *handle = (Tpm2Handle) { .tpm2_context = tpm2_context_ref(context), .esys_handle = ESYS_TR_NONE, + .flush = true, }; *ret_handle = TAKE_PTR(handle); @@ -500,6 +589,81 @@ int tpm2_handle_new(Tpm2Context *context, Tpm2Handle **ret_handle) { return 0; } +/* Create a Tpm2Handle object that references a pre-existing handle in the TPM, at the TPM2_HANDLE address + * provided. This should be used only for persistent, transient, or NV handles. Returns 1 on success, 0 if + * the requested handle is not present in the TPM, or < 0 on error. */ +static int tpm2_esys_handle_from_tpm_handle( + Tpm2Context *c, + const Tpm2Handle *session, + TPM2_HANDLE tpm_handle, + Tpm2Handle **ret_handle) { + + TSS2_RC rc; + int r; + + assert(c); + assert(tpm_handle > 0); + assert(ret_handle); + + /* Let's restrict this, at least for now, to allow only some handle types. */ + switch (TPM2_HANDLE_TYPE(tpm_handle)) { + case TPM2_HT_PERSISTENT: + case TPM2_HT_NV_INDEX: + case TPM2_HT_TRANSIENT: + break; + case TPM2_HT_PCR: + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "Refusing to create ESYS handle for PCR handle 0x%08" PRIx32 ".", + tpm_handle); + case TPM2_HT_HMAC_SESSION: + case TPM2_HT_POLICY_SESSION: + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "Refusing to create ESYS handle for session handle 0x%08" PRIx32 ".", + tpm_handle); + case TPM2_HT_PERMANENT: /* Permanent handles are defined, e.g. ESYS_TR_RH_OWNER. */ + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "Refusing to create ESYS handle for permanent handle 0x%08" PRIx32 ".", + tpm_handle); + default: + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "Refusing to create ESYS handle for unknown handle 0x%08" PRIx32 ".", + tpm_handle); + } + + r = tpm2_get_capability_handle(c, tpm_handle); + if (r < 0) + return r; + if (r == 0) { + log_debug("TPM handle 0x%08" PRIx32 " not populated.", tpm_handle); + *ret_handle = NULL; + return 0; + } + + _cleanup_(tpm2_handle_freep) Tpm2Handle *handle = NULL; + r = tpm2_handle_new(c, &handle); + if (r < 0) + return r; + + /* Since we didn't create this handle in the TPM (this is only creating an ESYS_TR handle for the + * pre-existing TPM handle), we shouldn't flush (or evict) it on cleanup. */ + handle->flush = false; + + rc = sym_Esys_TR_FromTPMPublic( + c->esys_context, + tpm_handle, + session ? session->esys_handle : ESYS_TR_NONE, + ESYS_TR_NONE, + ESYS_TR_NONE, + &handle->esys_handle); + if (rc != TSS2_RC_SUCCESS) + return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), + "Failed to read public info: %s", sym_Tss2_RC_Decode(rc)); + + *ret_handle = TAKE_PTR(handle); + + return 1; +} + #define TPM2_CREDIT_RANDOM_FLAG_PATH "/run/systemd/tpm-rng-credited" static int tpm2_credit_random(Tpm2Context *c) { @@ -663,60 +827,32 @@ const TPM2B_PUBLIC *tpm2_get_primary_template(Tpm2SRKTemplateFlags flags) { */ static int tpm2_get_srk( Tpm2Context *c, + const Tpm2Handle *session, TPMI_ALG_PUBLIC *ret_alg, - Tpm2Handle *ret_primary) { + Tpm2Handle **ret_handle) { - TPMI_YES_NO more_data; - ESYS_TR primary_tr = ESYS_TR_NONE; - _cleanup_(Esys_Freep) TPMS_CAPABILITY_DATA *cap_data = NULL; + int r; assert(c); - assert(ret_primary); - - TSS2_RC rc = sym_Esys_GetCapability(c->esys_context, - ESYS_TR_NONE, - ESYS_TR_NONE, - ESYS_TR_NONE, - TPM2_CAP_HANDLES, - SRK_HANDLE, - 1, - &more_data, - &cap_data); - if (rc != TSS2_RC_SUCCESS) - return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), - "Failed to enumerate handles searching for SRK: %s", - sym_Tss2_RC_Decode(rc)); - - /* Did Not find SRK, indicate this by returning 0 */ - if (cap_data->data.handles.count == 0 || cap_data->data.handles.handle[0] != SRK_HANDLE) { - ret_primary->esys_handle = ESYS_TR_NONE; + _cleanup_(tpm2_handle_freep) Tpm2Handle *handle = NULL; + r = tpm2_esys_handle_from_tpm_handle(c, session, SRK_HANDLE, &handle); + if (r < 0) + return r; + if (r == 0) { /* SRK not found */ if (ret_alg) - *ret_alg = 0; + *ret_alg = TPM2_ALG_ERROR; + if (ret_handle) + *ret_handle = NULL; return 0; } - log_debug("Found SRK on TPM."); - - /* convert the raw handle to an ESYS_TR */ - TPM2_HANDLE handle = cap_data->data.handles.handle[0]; - rc = sym_Esys_TR_FromTPMPublic(c->esys_context, - handle, - ESYS_TR_NONE, - ESYS_TR_NONE, - ESYS_TR_NONE, - &primary_tr); - if (rc != TSS2_RC_SUCCESS) - return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), - "Failed to convert ray handle to ESYS_TR for SRK: %s", - sym_Tss2_RC_Decode(rc)); - /* Get the algorithm if the caller wants it */ _cleanup_(Esys_Freep) TPM2B_PUBLIC *out_public = NULL; if (ret_alg) { - rc = sym_Esys_ReadPublic( + TSS2_RC rc = sym_Esys_ReadPublic( c->esys_context, - primary_tr, + handle->esys_handle, ESYS_TR_NONE, ESYS_TR_NONE, ESYS_TR_NONE, @@ -729,7 +865,8 @@ static int tpm2_get_srk( sym_Tss2_RC_Decode(rc)); } - ret_primary->esys_handle = primary_tr; + if (ret_handle) + *ret_handle = TAKE_PTR(handle); if (ret_alg) *ret_alg = out_public->publicArea.type; @@ -762,9 +899,6 @@ static int tpm2_make_primary( ts = now(CLOCK_MONOTONIC); _cleanup_(tpm2_handle_freep) Tpm2Handle *primary = NULL; - r = tpm2_handle_new(c, &primary); - if (r < 0) - return r; /* we only need the SRK lock when making the SRK since its not atomic, transient * primary creations don't even matter if they stomp on each other, the TPM will @@ -779,7 +913,7 @@ static int tpm2_make_primary( /* Find existing SRK and use it if present */ if (use_srk_model) { TPMI_ALG_PUBLIC got_alg = TPM2_ALG_NULL; - r = tpm2_get_srk(c, &got_alg, primary); + r = tpm2_get_srk(c, NULL, &got_alg, &primary); if (r < 0) return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), "Failed to establish if SRK is present"); @@ -799,6 +933,10 @@ static int tpm2_make_primary( log_debug("Did not find SRK, generating..."); } + r = tpm2_handle_new(c, &primary); + if (r < 0) + return r; + if (IN_SET(alg, 0, TPM2_ALG_ECC)) { primary_template = tpm2_get_primary_template(base_flags | TPM2_SRK_TEMPLATE_ECC); @@ -869,7 +1007,7 @@ static int tpm2_make_primary( if (rc != TSS2_RC_SUCCESS) return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), "Failed to persist SRK within TPM: %s", sym_Tss2_RC_Decode(rc)); - primary->keep = true; + primary->flush = false; } if (ret_primary) @@ -2853,7 +2991,7 @@ int tpm2_unseal(const char *device, if (r < 0) return r; - primary->keep = true; + primary->flush = false; log_debug("Found existing SRK key to use, deserializing ESYS_TR"); rc = sym_Esys_TR_Deserialize( diff --git a/src/shared/tpm2-util.h b/src/shared/tpm2-util.h index 043be9b190..015514e4bf 100644 --- a/src/shared/tpm2-util.h +++ b/src/shared/tpm2-util.h @@ -80,7 +80,8 @@ DEFINE_TRIVIAL_CLEANUP_FUNC(Tpm2Context*, tpm2_context_unref); typedef struct { Tpm2Context *tpm2_context; ESYS_TR esys_handle; - bool keep; + + bool flush; } Tpm2Handle; #define _tpm2_handle(c, h) { .tpm2_context = (c), .esys_handle = (h), } From 98d6a80942337f07183bc4039ce32dc188f4d4cd Mon Sep 17 00:00:00 2001 From: Dan Streetman Date: Wed, 17 May 2023 20:03:00 -0400 Subject: [PATCH 2/3] tpm2: add tpm2_read_public() --- src/shared/tpm2-util.c | 69 +++++++++++++++++++++++++++--------------- 1 file changed, 45 insertions(+), 24 deletions(-) diff --git a/src/shared/tpm2-util.c b/src/shared/tpm2-util.c index 49e4ea12ef..9094195ea6 100644 --- a/src/shared/tpm2-util.c +++ b/src/shared/tpm2-util.c @@ -724,6 +724,35 @@ static int tpm2_credit_random(Tpm2Context *c) { return 0; } +static int tpm2_read_public( + Tpm2Context *c, + const Tpm2Handle *session, + const Tpm2Handle *handle, + TPM2B_PUBLIC **ret_public, + TPM2B_NAME **ret_name, + TPM2B_NAME **ret_qname) { + + TSS2_RC rc; + + assert(c); + assert(handle); + + rc = sym_Esys_ReadPublic( + c->esys_context, + handle->esys_handle, + session ? session->esys_handle : ESYS_TR_NONE, + ESYS_TR_NONE, + ESYS_TR_NONE, + ret_public, + ret_name, + ret_qname); + if (rc != TSS2_RC_SUCCESS) + return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), + "Failed to read public info: %s", sym_Tss2_RC_Decode(rc)); + + return 0; +} + const TPM2B_PUBLIC *tpm2_get_primary_template(Tpm2SRKTemplateFlags flags) { /* @@ -828,7 +857,9 @@ const TPM2B_PUBLIC *tpm2_get_primary_template(Tpm2SRKTemplateFlags flags) { static int tpm2_get_srk( Tpm2Context *c, const Tpm2Handle *session, - TPMI_ALG_PUBLIC *ret_alg, + TPM2B_PUBLIC **ret_public, + TPM2B_NAME **ret_name, + TPM2B_NAME **ret_qname, Tpm2Handle **ret_handle) { int r; @@ -840,37 +871,26 @@ static int tpm2_get_srk( if (r < 0) return r; if (r == 0) { /* SRK not found */ - if (ret_alg) - *ret_alg = TPM2_ALG_ERROR; + if (ret_public) + *ret_public = NULL; + if (ret_name) + *ret_name = NULL; + if (ret_qname) + *ret_qname = NULL; if (ret_handle) *ret_handle = NULL; return 0; } - /* Get the algorithm if the caller wants it */ - _cleanup_(Esys_Freep) TPM2B_PUBLIC *out_public = NULL; - if (ret_alg) { - TSS2_RC rc = sym_Esys_ReadPublic( - c->esys_context, - handle->esys_handle, - ESYS_TR_NONE, - ESYS_TR_NONE, - ESYS_TR_NONE, - &out_public, - NULL, - NULL); - if (rc != TSS2_RC_SUCCESS) - return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), - "Failed to convert ray handle to ESYS_TR for SRK: %s", - sym_Tss2_RC_Decode(rc)); + if (ret_public || ret_name || ret_qname) { + r = tpm2_read_public(c, session, handle, ret_public, ret_name, ret_qname); + if (r < 0) + return r; } if (ret_handle) *ret_handle = TAKE_PTR(handle); - if (ret_alg) - *ret_alg = out_public->publicArea.type; - return 1; } @@ -912,14 +932,15 @@ static int tpm2_make_primary( /* Find existing SRK and use it if present */ if (use_srk_model) { - TPMI_ALG_PUBLIC got_alg = TPM2_ALG_NULL; - r = tpm2_get_srk(c, NULL, &got_alg, &primary); + _cleanup_(Esys_Freep) TPM2B_PUBLIC *primary_public = NULL; + r = tpm2_get_srk(c, NULL, &primary_public, NULL, NULL, &primary); if (r < 0) return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), "Failed to establish if SRK is present"); if (r == 1) { log_debug("Discovered existing SRK"); + TPMI_ALG_PUBLIC got_alg = primary_public->publicArea.type; if (alg != 0 && alg != got_alg) log_warning("Caller asked for specific algorithm %u, but existing SRK is %u, ignoring", alg, got_alg); From f4f5b3a9de29874bcb2345196eb47ec90d02b67d Mon Sep 17 00:00:00 2001 From: Dan Streetman Date: Fri, 17 Feb 2023 12:59:18 -0500 Subject: [PATCH 3/3] tpm2: add tpm2_get_legacy_template() and tpm2_get_srk_template() Add functions to get either the 'legacy' or standard SRK template, for RSA or ECC. The 'legacy' templates are those used with earlier code, where a transient key was created to use for tpm sealing; the standard SRK is the persistent shared key as defined in TCG guidance docs. This also replaces tpm2_get_primary_template() with the new functions; that function's use of flags is confusing and unnecessary. --- src/shared/tpm2-util.c | 297 +++++++++++++++++++++++++++-------------- src/shared/tpm2-util.h | 9 -- src/test/test-tpm2.c | 90 ------------- 3 files changed, 199 insertions(+), 197 deletions(-) diff --git a/src/shared/tpm2-util.c b/src/shared/tpm2-util.c index 9094195ea6..9694a36d8f 100644 --- a/src/shared/tpm2-util.c +++ b/src/shared/tpm2-util.c @@ -257,6 +257,25 @@ 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 ECC curve, 0 if not, or < 0 for any error. */ +static int tpm2_supports_ecc_curve(Tpm2Context *c, TPM2_ECC_CURVE curve) { + TPMU_CAPABILITIES capability; + int r; + + /* The spec explicitly states the TPM2_ECC_CURVE should be cast to uint32_t. */ + r = tpm2_get_capability(c, TPM2_CAP_ECC_CURVES, (uint32_t) curve, 1, &capability); + if (r < 0) + return r; + + TPML_ECC_CURVE eccCurves = capability.eccCurves; + if (eccCurves.count == 0 || eccCurves.eccCurves[0] != curve) { + log_debug("TPM does not support ECC curve 0x%02" PRIx16 ".", curve); + return 0; + } + + return 1; +} + /* Query the TPM for populated handles. * * This provides an array of handle indexes populated in the TPM, starting at the requested handle. The array will @@ -358,6 +377,13 @@ bool tpm2_test_parms(Tpm2Context *c, TPMI_ALG_PUBLIC alg, const TPMU_PUBLIC_PARM return rc == TSS2_RC_SUCCESS; } +static inline bool tpm2_supports_tpmt_public(Tpm2Context *c, const TPMT_PUBLIC *public) { + assert(c); + assert(public); + + return tpm2_test_parms(c, public->type, &public->parameters); +} + static inline bool tpm2_supports_tpmt_sym_def_object(Tpm2Context *c, const TPMT_SYM_DEF_OBJECT *parameters) { assert(c); assert(parameters); @@ -753,102 +779,168 @@ static int tpm2_read_public( return 0; } -const TPM2B_PUBLIC *tpm2_get_primary_template(Tpm2SRKTemplateFlags flags) { - - /* - * Set up array so flags can be used directly as an input. - * - * Templates for SRK come from the spec: - * - https://trustedcomputinggroup.org/wp-content/uploads/TCG-TPM-v2.0-Provisioning-Guidance-Published-v1r1.pdf - * - * However, note their is some lore here. On Linux, the SRK has it's unique field set to size 0 and - * on Windows the SRK has their unique data set to keyLen in bytes of zeros. - */ - assert(flags >= 0); - assert(flags <= _TPM2_SRK_TEMPLATE_MAX); - - static const TPM2B_PUBLIC templ[_TPM2_SRK_TEMPLATE_MAX + 1] = { - /* index 0 RSA old */ - [0] = { - .publicArea = { - .type = TPM2_ALG_RSA, - .nameAlg = TPM2_ALG_SHA256, - .objectAttributes = TPMA_OBJECT_RESTRICTED|TPMA_OBJECT_DECRYPT|TPMA_OBJECT_FIXEDTPM|TPMA_OBJECT_FIXEDPARENT|TPMA_OBJECT_SENSITIVEDATAORIGIN|TPMA_OBJECT_USERWITHAUTH, - .parameters.rsaDetail = { - .symmetric = { - .algorithm = TPM2_ALG_AES, - .keyBits.aes = 128, - .mode.aes = TPM2_ALG_CFB, - }, - .scheme.scheme = TPM2_ALG_NULL, - .keyBits = 2048, - }, - }, - }, - [TPM2_SRK_TEMPLATE_ECC] = { - .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, - }, - }, - }, - [TPM2_SRK_TEMPLATE_NEW_STYLE] = { - .publicArea = { - .type = TPM2_ALG_RSA, - .nameAlg = TPM2_ALG_SHA256, - .objectAttributes = TPMA_OBJECT_FIXEDTPM|TPMA_OBJECT_FIXEDPARENT|TPMA_OBJECT_SENSITIVEDATAORIGIN|TPMA_OBJECT_RESTRICTED|TPMA_OBJECT_DECRYPT|TPMA_OBJECT_USERWITHAUTH|TPMA_OBJECT_NODA, - .parameters.rsaDetail = { - .symmetric = { - .algorithm = TPM2_ALG_AES, - .keyBits.aes = 128, - .mode.aes = TPM2_ALG_CFB, - }, - .scheme.scheme = TPM2_ALG_NULL, - .keyBits = 2048, - }, - }, - }, - [TPM2_SRK_TEMPLATE_NEW_STYLE|TPM2_SRK_TEMPLATE_ECC] = { - .publicArea = { - .type = TPM2_ALG_ECC, - .nameAlg = TPM2_ALG_SHA256, - .objectAttributes = TPMA_OBJECT_FIXEDTPM|TPMA_OBJECT_FIXEDPARENT|TPMA_OBJECT_SENSITIVEDATAORIGIN|TPMA_OBJECT_RESTRICTED|TPMA_OBJECT_DECRYPT|TPMA_OBJECT_USERWITHAUTH|TPMA_OBJECT_NODA, - .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, - }, +/* Get one of the legacy primary key templates. + * + * The legacy templates should only be used for older sealed data that did not use the SRK. Instead of a + * persistent SRK, a transient key was created to seal the data and then flushed; and the exact same template + * must be used to recreate the same transient key to unseal the data. The alg parameter must be TPM2_ALG_RSA + * or TPM2_ALG_ECC. This does not check if the alg is actually supported on this TPM. */ +static int tpm2_get_legacy_template(TPMI_ALG_PUBLIC alg, TPMT_PUBLIC *ret_template) { + /* Do not modify. */ + static const TPMT_PUBLIC legacy_ecc = { + .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, }, }; - return &templ[flags]; + /* Do not modify. */ + static const TPMT_PUBLIC legacy_rsa = { + .type = TPM2_ALG_RSA, + .nameAlg = TPM2_ALG_SHA256, + .objectAttributes = TPMA_OBJECT_RESTRICTED|TPMA_OBJECT_DECRYPT|TPMA_OBJECT_FIXEDTPM|TPMA_OBJECT_FIXEDPARENT|TPMA_OBJECT_SENSITIVEDATAORIGIN|TPMA_OBJECT_USERWITHAUTH, + .parameters.rsaDetail = { + .symmetric = { + .algorithm = TPM2_ALG_AES, + .keyBits.aes = 128, + .mode.aes = TPM2_ALG_CFB, + }, + .scheme.scheme = TPM2_ALG_NULL, + .keyBits = 2048, + }, + }; + + assert(ret_template); + + if (alg == TPM2_ALG_ECC) + *ret_template = legacy_ecc; + else if (alg == TPM2_ALG_RSA) + *ret_template = legacy_rsa; + else + return log_debug_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), + "Unsupported legacy SRK alg: 0x%x", alg); + + return 0; } -/* - * Why and what is an SRK? - * TL;DR provides a working space for those without owner auth. The user enrolling - * the disk may not have access to the TPMs owner hierarchy auth, so they need a - * working space. This working space is at the defined address of 0x81000001. - * Details can be found here: - * - https://trustedcomputinggroup.org/wp-content/uploads/TCG-TPM-v2.0-Provisioning-Guidance-Published-v1r1.pdf - */ -#define SRK_HANDLE UINT32_C(0x81000001) +/* Get a Storage Root Key (SRK) template. + * + * The SRK template values are recommended by the "TCG TPM v2.0 Provisioning Guidance" document in section + * 7.5.1 "Storage Primary Key (SRK) Templates", referencing "TCG EK Credential Profile for TPM Family 2.0". + * The EK Credential Profile version 2.0 provides only a single template each for RSA and ECC, while later EK + * Credential Profile versions provide more templates, and keep the original templates as "L-1" (for RSA) and + * "L-2" (for ECC). + * + * https://trustedcomputinggroup.org/resource/tcg-tpm-v2-0-provisioning-guidance + * https://trustedcomputinggroup.org/resource/http-trustedcomputinggroup-org-wp-content-uploads-tcg-ek-credential-profile + * + * These templates are only needed to create a new persistent SRK (or a new transient key that is + * SRK-compatible). Preferably, the TPM should contain a shared SRK located at the reserved shared SRK handle + * (see TPM2_SRK_HANDLE and tpm2_get_srk() below). + * + * The alg must be TPM2_ALG_RSA or TPM2_ALG_ECC. Returns error if the requested template is not supported on + * this TPM. */ +static int tpm2_get_srk_template(Tpm2Context *c, TPMI_ALG_PUBLIC alg, TPMT_PUBLIC *ret_template) { + /* The attributes are the same between ECC and RSA templates. This has the changes specified in the + * Provisioning Guidance document, specifically: + * TPMA_OBJECT_USERWITHAUTH is added. + * TPMA_OBJECT_ADMINWITHPOLICY is removed. + * TPMA_OBJECT_NODA is added. */ + TPMA_OBJECT srk_attributes = + TPMA_OBJECT_DECRYPT | + TPMA_OBJECT_FIXEDPARENT | + TPMA_OBJECT_FIXEDTPM | + TPMA_OBJECT_NODA | + TPMA_OBJECT_RESTRICTED | + TPMA_OBJECT_SENSITIVEDATAORIGIN | + TPMA_OBJECT_USERWITHAUTH; + + /* The symmetric configuration is the same between ECC and RSA templates. */ + TPMT_SYM_DEF_OBJECT srk_symmetric = { + .algorithm = TPM2_ALG_AES, + .keyBits.aes = 128, + .mode.aes = TPM2_ALG_CFB, + }; + + /* Both templates have an empty authPolicy as specified by the Provisioning Guidance document. */ + + /* From the EK Credential Profile template "L-2". */ + TPMT_PUBLIC srk_ecc = { + .type = TPM2_ALG_ECC, + .nameAlg = TPM2_ALG_SHA256, + .objectAttributes = srk_attributes, + .parameters.eccDetail = { + .symmetric = srk_symmetric, + .scheme.scheme = TPM2_ALG_NULL, + .curveID = TPM2_ECC_NIST_P256, + .kdf.scheme = TPM2_ALG_NULL, + }, + }; + + /* From the EK Credential Profile template "L-1". */ + TPMT_PUBLIC srk_rsa = { + .type = TPM2_ALG_RSA, + .nameAlg = TPM2_ALG_SHA256, + .objectAttributes = srk_attributes, + .parameters.rsaDetail = { + .symmetric = srk_symmetric, + .scheme.scheme = TPM2_ALG_NULL, + .keyBits = 2048, + }, + }; + + assert(c); + assert(ret_template); + + if (alg == TPM2_ALG_ECC) { + if (!tpm2_supports_alg(c, TPM2_ALG_ECC)) + return log_debug_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), + "TPM does not support ECC."); + + if (!tpm2_supports_ecc_curve(c, srk_ecc.parameters.eccDetail.curveID)) + return log_debug_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), + "TPM does not support ECC-NIST-P256 curve."); + + if (!tpm2_supports_tpmt_public(c, &srk_ecc)) + return log_debug_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), + "TPM does not support SRK ECC template L-2."); + + *ret_template = srk_ecc; + return 0; + } + + if (alg == TPM2_ALG_RSA) { + if (!tpm2_supports_alg(c, TPM2_ALG_RSA)) + return log_debug_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), + "TPM does not support RSA."); + + if (!tpm2_supports_tpmt_public(c, &srk_rsa)) + return log_debug_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), + "TPM does not support SRK RSA template L-1."); + + *ret_template = srk_rsa; + return 0; + } + + return log_debug_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "Unsupported SRK alg: 0x%x.", alg); +} + +/* The SRK handle is defined in the Provisioning Guidance document (see above) in the table "Reserved Handles + * for TPM Provisioning Fundamental Elements". The SRK is useful because it is "shared", meaning it has no + * authValue nor authPolicy set, and thus may be used by anyone on the system to generate derived keys or + * seal secrets. This is useful if the TPM has an auth (password) set for the 'owner hierarchy', which would + * prevent users from generating primary transient keys, unless they knew the owner hierarchy auth. See + * the Provisioning Guidance document for more details. */ +#define TPM2_SRK_HANDLE UINT32_C(0x81000001) /* * Retrieves the SRK handle if present. Returns 0 if SRK not present, 1 if present @@ -867,7 +959,7 @@ static int tpm2_get_srk( assert(c); _cleanup_(tpm2_handle_freep) Tpm2Handle *handle = NULL; - r = tpm2_esys_handle_from_tpm_handle(c, session, SRK_HANDLE, &handle); + r = tpm2_esys_handle_from_tpm_handle(c, session, TPM2_SRK_HANDLE, &handle); if (r < 0) return r; if (r == 0) { /* SRK not found */ @@ -903,8 +995,7 @@ static int tpm2_make_primary( static const TPM2B_SENSITIVE_CREATE primary_sensitive = {}; static const TPML_PCR_SELECTION creation_pcr = {}; - const TPM2B_PUBLIC *primary_template = NULL; - Tpm2SRKTemplateFlags base_flags = use_srk_model ? TPM2_SRK_TEMPLATE_NEW_STYLE : 0; + TPM2B_PUBLIC primary_template = { .size = sizeof(TPMT_PUBLIC), }; _cleanup_(release_lock_file) LockFile srk_lock = LOCK_FILE_INIT; TSS2_RC rc; usec_t ts; @@ -959,7 +1050,12 @@ static int tpm2_make_primary( return r; if (IN_SET(alg, 0, TPM2_ALG_ECC)) { - primary_template = tpm2_get_primary_template(base_flags | TPM2_SRK_TEMPLATE_ECC); + if (use_srk_model) + r = tpm2_get_srk_template(c, TPM2_ALG_ECC, &primary_template.publicArea); + else + r = tpm2_get_legacy_template(TPM2_ALG_ECC, &primary_template.publicArea); + if (r < 0) + return r; rc = sym_Esys_CreatePrimary( c->esys_context, @@ -968,7 +1064,7 @@ static int tpm2_make_primary( ESYS_TR_NONE, ESYS_TR_NONE, &primary_sensitive, - primary_template, + &primary_template, NULL, &creation_pcr, &primary->esys_handle, @@ -990,7 +1086,12 @@ static int tpm2_make_primary( } if (IN_SET(alg, 0, TPM2_ALG_RSA)) { - primary_template = tpm2_get_primary_template(base_flags); + if (use_srk_model) + r = tpm2_get_srk_template(c, TPM2_ALG_RSA, &primary_template.publicArea); + else + r = tpm2_get_legacy_template(TPM2_ALG_RSA, &primary_template.publicArea); + if (r < 0) + return r; rc = sym_Esys_CreatePrimary( c->esys_context, @@ -999,7 +1100,7 @@ static int tpm2_make_primary( ESYS_TR_NONE, ESYS_TR_NONE, &primary_sensitive, - primary_template, + &primary_template, NULL, &creation_pcr, &primary->esys_handle, @@ -1024,7 +1125,7 @@ static int tpm2_make_primary( if (use_srk_model) { rc = sym_Esys_EvictControl(c->esys_context, ESYS_TR_RH_OWNER, primary->esys_handle, - ESYS_TR_PASSWORD, ESYS_TR_NONE, ESYS_TR_NONE, SRK_HANDLE, &primary->esys_handle); + ESYS_TR_PASSWORD, ESYS_TR_NONE, ESYS_TR_NONE, TPM2_SRK_HANDLE, &primary->esys_handle); if (rc != TSS2_RC_SUCCESS) return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), "Failed to persist SRK within TPM: %s", sym_Tss2_RC_Decode(rc)); diff --git a/src/shared/tpm2-util.h b/src/shared/tpm2-util.h index 015514e4bf..5fa6456379 100644 --- a/src/shared/tpm2-util.h +++ b/src/shared/tpm2-util.h @@ -13,13 +13,6 @@ typedef enum TPM2Flags { TPM2_FLAGS_USE_PIN = 1 << 0, } TPM2Flags; - -typedef enum Tpm2SRKTemplateFlags { - TPM2_SRK_TEMPLATE_ECC = 1 << 0, - TPM2_SRK_TEMPLATE_NEW_STYLE = 1 << 1, - _TPM2_SRK_TEMPLATE_MAX = TPM2_SRK_TEMPLATE_NEW_STYLE|TPM2_SRK_TEMPLATE_ECC, -} Tpm2SRKTemplateFlags; - /* As per https://trustedcomputinggroup.org/wp-content/uploads/TCG_PCClient_PFP_r1p05_v23_pub.pdf a * TPM2 on a Client PC must have at least 24 PCRs. This hardcodes our expectation of 24. */ #define TPM2_PCRS_MAX 24U @@ -119,8 +112,6 @@ char *tpm2_tpml_pcr_selection_to_string(const TPML_PCR_SELECTION *l); size_t tpm2_tpml_pcr_selection_weight(const TPML_PCR_SELECTION *l); #define tpm2_tpml_pcr_selection_is_empty(l) (tpm2_tpml_pcr_selection_weight(l) == 0) -const TPM2B_PUBLIC *tpm2_get_primary_template(Tpm2SRKTemplateFlags flags); - #else /* HAVE_TPM2 */ typedef struct {} Tpm2Context; typedef struct {} Tpm2Handle; diff --git a/src/test/test-tpm2.c b/src/test/test-tpm2.c index e60de5e29e..f014c8f27e 100644 --- a/src/test/test-tpm2.c +++ b/src/test/test-tpm2.c @@ -458,96 +458,6 @@ TEST(tpml_pcr_selection_add_sub) { expected2, expected2_count); } -/* this test includes TPM2 specific data structures */ -TEST(tpm2_get_primary_template) { - - /* - * Verify that if someone changes the template code, they know they're breaking things. - * Templates MUST be changed in a backwards compatible way. - * - */ - static const TPM2B_PUBLIC templ[] = { - /* index 0 RSA old */ - [0] = { - .publicArea = { - .type = TPM2_ALG_RSA, - .nameAlg = TPM2_ALG_SHA256, - .objectAttributes = TPMA_OBJECT_RESTRICTED|TPMA_OBJECT_DECRYPT|TPMA_OBJECT_FIXEDTPM|TPMA_OBJECT_FIXEDPARENT|TPMA_OBJECT_SENSITIVEDATAORIGIN|TPMA_OBJECT_USERWITHAUTH, - .parameters.rsaDetail = { - .symmetric = { - .algorithm = TPM2_ALG_AES, - .keyBits.aes = 128, - .mode.aes = TPM2_ALG_CFB, - }, - .scheme.scheme = TPM2_ALG_NULL, - .keyBits = 2048, - }, - }, - }, - /* Index 1 ECC old */ - [TPM2_SRK_TEMPLATE_ECC] = { - .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, - }, - }, - }, - /* index 2 RSA SRK */ - [TPM2_SRK_TEMPLATE_NEW_STYLE] = { - .publicArea = { - .type = TPM2_ALG_RSA, - .nameAlg = TPM2_ALG_SHA256, - .objectAttributes = TPMA_OBJECT_FIXEDTPM|TPMA_OBJECT_FIXEDPARENT|TPMA_OBJECT_SENSITIVEDATAORIGIN|TPMA_OBJECT_RESTRICTED|TPMA_OBJECT_DECRYPT|TPMA_OBJECT_USERWITHAUTH|TPMA_OBJECT_NODA, - .parameters.rsaDetail = { - .symmetric = { - .algorithm = TPM2_ALG_AES, - .keyBits.aes = 128, - .mode.aes = TPM2_ALG_CFB, - }, - .scheme.scheme = TPM2_ALG_NULL, - .keyBits = 2048, - }, - }, - }, - /* Index 3 ECC SRK */ - [TPM2_SRK_TEMPLATE_NEW_STYLE | TPM2_SRK_TEMPLATE_ECC] = { - .publicArea = { - .type = TPM2_ALG_ECC, - .nameAlg = TPM2_ALG_SHA256, - .objectAttributes = TPMA_OBJECT_FIXEDTPM|TPMA_OBJECT_FIXEDPARENT|TPMA_OBJECT_SENSITIVEDATAORIGIN|TPMA_OBJECT_RESTRICTED|TPMA_OBJECT_DECRYPT|TPMA_OBJECT_USERWITHAUTH|TPMA_OBJECT_NODA, - .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, - }, - }, - }, - }; - - assert_cc(ELEMENTSOF(templ) == _TPM2_SRK_TEMPLATE_MAX + 1); - - for (size_t i = 0; i < ELEMENTSOF(templ); i++) { - /* the index counter lines up with the flags and the expected template received */ - const TPM2B_PUBLIC *got = tpm2_get_primary_template((Tpm2SRKTemplateFlags)i); - assert_se(memcmp(&templ[i], got, sizeof(*got)) == 0); - } -} - static bool digest_check(const TPM2B_DIGEST *digest, const char *expect) { _cleanup_free_ char *h = NULL;