e5f65c3fc6
Resolves: RHEL-1086,RHEL-11591,RHEL-16182,RHEL-19483,RHEL-7026
409 lines
18 KiB
Diff
409 lines
18 KiB
Diff
From 88bac2c3213ab4a5036732ff9bc4f5dc2b4287b7 Mon Sep 17 00:00:00 2001
|
|
From: Dan Streetman <ddstreet@ieee.org>
|
|
Date: Fri, 17 Feb 2023 12:50:31 -0500
|
|
Subject: [PATCH] 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).
|
|
|
|
(cherry picked from commit c8a85240316898a6de95c9b2565edd08f8450182)
|
|
|
|
Related: RHEL-16182
|
|
---
|
|
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 bf36b4de95..3278863f4d 100644
|
|
--- a/src/shared/tpm2-util.c
|
|
+++ b/src/shared/tpm2-util.c
|
|
@@ -50,6 +50,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;
|
|
@@ -97,6 +98,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),
|
|
@@ -252,6 +254,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;
|
|
@@ -454,17 +534,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) {
|
|
@@ -472,8 +560,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);
|
|
}
|
|
@@ -490,6 +578,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);
|
|
@@ -497,6 +586,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) {
|
|
@@ -660,60 +824,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,
|
|
@@ -726,7 +862,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;
|
|
@@ -759,9 +896,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
|
|
@@ -776,7 +910,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");
|
|
@@ -796,6 +930,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);
|
|
|
|
@@ -866,7 +1004,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)
|
|
@@ -2846,7 +2984,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 a03bee148b..26d25f7ee7 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), }
|