1
0
mirror of https://github.com/systemd/systemd.git synced 2024-10-27 18:55:40 +03:00

Merge pull request #27712 from ddstreet/tpm2_simplify_srk

Tpm2 simplify srk
This commit is contained in:
Luca Boccassi 2023-06-08 18:52:31 +01:00 committed by GitHub
commit ccc14f0812
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 430 additions and 268 deletions

View File

@ -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(

View File

@ -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;

View File

@ -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;