diff --git a/src/shared/tpm2-util.c b/src/shared/tpm2-util.c index 6214aff7d9..9694a36d8f 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,103 @@ 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 + * 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; @@ -278,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); @@ -457,17 +563,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 +589,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 +607,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 +615,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) { @@ -560,102 +750,197 @@ static int tpm2_credit_random(Tpm2Context *c) { return 0; } -const TPM2B_PUBLIC *tpm2_get_primary_template(Tpm2SRKTemplateFlags flags) { +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) { - /* - * 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); + TSS2_RC rc; - 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, - }, + 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; +} + +/* 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 @@ -663,76 +948,40 @@ const TPM2B_PUBLIC *tpm2_get_primary_template(Tpm2SRKTemplateFlags flags) { */ static int tpm2_get_srk( Tpm2Context *c, - TPMI_ALG_PUBLIC *ret_alg, - Tpm2Handle *ret_primary) { + const Tpm2Handle *session, + TPM2B_PUBLIC **ret_public, + TPM2B_NAME **ret_name, + TPM2B_NAME **ret_qname, + 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; - - if (ret_alg) - *ret_alg = 0; + _cleanup_(tpm2_handle_freep) Tpm2Handle *handle = NULL; + r = tpm2_esys_handle_from_tpm_handle(c, session, TPM2_SRK_HANDLE, &handle); + if (r < 0) + return r; + if (r == 0) { /* SRK not found */ + 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; } - 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( - c->esys_context, - primary_tr, - 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; } - ret_primary->esys_handle = primary_tr; - - if (ret_alg) - *ret_alg = out_public->publicArea.type; + if (ret_handle) + *ret_handle = TAKE_PTR(handle); return 1; } @@ -746,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; @@ -762,9 +1010,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 @@ -778,14 +1023,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, &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); @@ -799,8 +1045,17 @@ 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); + 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, @@ -809,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, @@ -831,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, @@ -840,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, @@ -865,11 +1125,11 @@ 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)); - primary->keep = true; + primary->flush = false; } if (ret_primary) @@ -2853,7 +3113,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..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 @@ -80,7 +73,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), } @@ -118,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;