From: Chris Coulson Date: Tue, 2 Jun 2026 13:48:55 +0000 (+0100) Subject: tpm2-setup: Create and persist an endorsement key X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=5a80137aa449ff0870210eb87077a5ce3ab8680b;p=thirdparty%2Fsystemd.git tpm2-setup: Create and persist an endorsement key This updates systemd-tpm2-setup to create and persist an endorsement key if there isn't one already. For each supported EK template profile, it will read the EK certificate from its NV index if there is one. When there is an EK certificate present, a primary key is created using the corresponding template. If the resulting EKpub matches the public key in the certificate, the created EK is persisted and the process is complete. The low-range templates and the high-range RSA 2048/3072 and ECC NIST P256/P384 storage templates are supported, as detailed in section 5.3 of the "TCG EK Credential Profile For TPM Family 2.0" spec v2.7. High-range templates are preferred because these permit EK usage without requiring knowledge of the authorization value for the endorsement hierarchy, meaning that, like with the SRK, it is possible to restrict the usage of the endorsement hierarchy whilst still permitting use of the persistent EK. The EK is always persisted at handle 0x81010001. This handle is reserved in Table 2 of the "TCG TPM v2.0 Provisioning Guidance" spec v1.0r1.0, although this is only a recommendation. This handle is within the block of handles reserved for endorsement primary keys in the "Registry of Reserved TPM 2.0 Handles and Localities" spec v1.2r1.00. Section 2.3.2 of this specification also makes a suggestion that there should be a relationship between the EK certificate NV index and a corresponding persistent EK handle by using handles at the same offsets within their respective ranges. However, this contradicts the provisioning guidance spec which reserves 0x81010001 when there isn't a certificate at 0x01c00001. For simplicity, I've chosen to use a single handle for the EK regardless of which profile it is created with. The "TCG EK Credential Profile For TPM Family 2.0" spec also provides a way for endorsement keys to be certified with non-standard templates by storing the template in a NV index. This is also supported. The EK creation is not executed with tpm2-setup --early, as there's no need for it to be created so early, unlike with the SRK. I also haven't stored EKpub in /var/lib/systemd like with the SRKpub, as I'm not sure there will be a use case for this yet. A follow-up PR may be needed to add some internal helpers to make use of the persisted EK, as use of low-range EKs requires a policy session. High-range EKs can be used with a HMAC session because they have the userWithAuth attribute set and we are creating them with an empty authorization value. https://trustedcomputinggroup.org/resource/http-trustedcomputinggroup-org-wp-content-uploads-tcg-ek-credential-profile/ https://trustedcomputinggroup.org/resource/tcg-tpm-v2-0-provisioning-guidance/ https://trustedcomputinggroup.org/resource/registry/ --- diff --git a/catalog/systemd.catalog.in b/catalog/systemd.catalog.in index a4d286f9553..f4bf3f2d7a3 100644 --- a/catalog/systemd.catalog.in +++ b/catalog/systemd.catalog.in @@ -825,6 +825,21 @@ Automatic SRK enrollment on TPMs in such scenarios is not supported. In order to unset the PIN/password protection on the owner hierarchy issue a command like the following: 'tpm2_changeauth -c o -p ""'. +-- 8d813b6becf04b859d99760cbb56dff1 +Subject: Authorization failure while attempting to enroll EK into TPM +Defined-By: systemd +Support: %SUPPORT_URL% +Documentation: man:systemd-tpm2-setup.service(8) + +An authorization failure occurred while attempting to enroll an Endorsement Key +(EK) on the Trusted Platform Module (TPM). Most likely this means that a +PIN/Password (authValue) has been set on the Owner and/or Endorsement hierarchy +of the TPM. + +Automatic EK enrollment on TPMs in such scenarios is not supported. In order +to unset the PIN/password protection on the owner or endorsement hierarchy, +issue a command like the following: 'tpm2_changeauth -c -p ""'. + -- 9cf56b8baf9546cf9478783a8de42113 Subject: A foreign process changed a sysctl systemd-networkd manages Defined-By: systemd diff --git a/man/systemd-tpm2-setup.service.xml b/man/systemd-tpm2-setup.service.xml index 95d8da46440..43c28bc6a22 100644 --- a/man/systemd-tpm2-setup.service.xml +++ b/man/systemd-tpm2-setup.service.xml @@ -20,7 +20,7 @@ systemd-tpm2-setup.service systemd-tpm2-setup-early.service systemd-tpm2-setup - Set up the TPM2 Storage Root Key (SRK) and initialize NvPCRs at boot + Set up the TPM2 Storage Root Key (SRK), Endorsement Key (EK), and initialize NvPCRs at boot @@ -33,8 +33,9 @@ systemd-tpm2-setup.service and systemd-tpm2-setup-early.service are services that generate the Storage Root Key - (SRK) if it has not been generated yet, and stores it in the TPM. If NvPCRs (additional PCR registers in - TPM NV Indexes) are defined, these are initialized with the anchoring secret. + (SRK) and Endorsement Key (EK) if they have not been generated yet, and stores them in the TPM. If + NvPCRs (additional PCR registers in TPM NV Indexes) are defined, these are initialized with the + anchoring secret. The services will store the public key of the SRK key pair in a PEM file in /run/systemd/tpm2-srk-public-key.pem and diff --git a/src/shared/crypto-util.c b/src/shared/crypto-util.c index 5b1d6d81366..3e31ebd9b8e 100644 --- a/src/shared/crypto-util.c +++ b/src/shared/crypto-util.c @@ -276,6 +276,7 @@ DLSYM_PROTOTYPE(X509_VERIFY_PARAM_set1_host) = NULL; DLSYM_PROTOTYPE(X509_VERIFY_PARAM_set1_ip) = NULL; DLSYM_PROTOTYPE(X509_VERIFY_PARAM_set_hostflags) = NULL; DLSYM_PROTOTYPE(X509_free) = NULL; +DLSYM_PROTOTYPE(X509_get0_pubkey) = NULL; static DLSYM_PROTOTYPE(X509_get0_serialNumber) = NULL; static DLSYM_PROTOTYPE(X509_get_issuer_name) = NULL; DLSYM_PROTOTYPE(X509_get_pubkey) = NULL; @@ -603,6 +604,7 @@ int dlopen_libcrypto(int log_level) { DLSYM_ARG(X509_VERIFY_PARAM_set1_ip), DLSYM_ARG(X509_VERIFY_PARAM_set_hostflags), DLSYM_ARG(X509_free), + DLSYM_ARG(X509_get0_pubkey), DLSYM_ARG(X509_get0_serialNumber), DLSYM_ARG(X509_get_issuer_name), DLSYM_ARG(X509_get_pubkey), diff --git a/src/shared/crypto-util.h b/src/shared/crypto-util.h index bd7dda0931c..53da4cabe6f 100644 --- a/src/shared/crypto-util.h +++ b/src/shared/crypto-util.h @@ -251,6 +251,7 @@ extern DLSYM_PROTOTYPE(X509_VERIFY_PARAM_set1_host); extern DLSYM_PROTOTYPE(X509_VERIFY_PARAM_set1_ip); extern DLSYM_PROTOTYPE(X509_VERIFY_PARAM_set_hostflags); extern DLSYM_PROTOTYPE(X509_free); +extern DLSYM_PROTOTYPE(X509_get0_pubkey); extern DLSYM_PROTOTYPE(X509_get_pubkey); extern DLSYM_PROTOTYPE(X509_get_subject_name); extern DLSYM_PROTOTYPE(X509_gmtime_adj); diff --git a/src/shared/tpm2-util.c b/src/shared/tpm2-util.c index d1be7d564ce..fdb97d27a88 100644 --- a/src/shared/tpm2-util.c +++ b/src/shared/tpm2-util.c @@ -162,6 +162,7 @@ static DLSYM_PROTOTYPE(Tss2_MU_TPM2B_NV_PUBLIC_Unmarshal) = NULL; static DLSYM_PROTOTYPE(Tss2_MU_TPMS_ECC_POINT_Marshal) = NULL; static DLSYM_PROTOTYPE(Tss2_MU_TPMT_HA_Marshal) = NULL; static DLSYM_PROTOTYPE(Tss2_MU_TPMT_PUBLIC_Marshal) = NULL; +static DLSYM_PROTOTYPE(Tss2_MU_TPMT_PUBLIC_Unmarshal) = NULL; static DLSYM_PROTOTYPE(Tss2_MU_UINT32_Marshal) = NULL; static DLSYM_PROTOTYPE(Tss2_RC_Decode) = NULL; @@ -264,6 +265,7 @@ static int dlopen_tpm2_mu(int log_level) { DLSYM_ARG(Tss2_MU_TPMS_ECC_POINT_Marshal), DLSYM_ARG(Tss2_MU_TPMT_HA_Marshal), DLSYM_ARG(Tss2_MU_TPMT_PUBLIC_Marshal), + DLSYM_ARG(Tss2_MU_TPMT_PUBLIC_Unmarshal), DLSYM_ARG(Tss2_MU_UINT32_Marshal)); } @@ -1488,6 +1490,8 @@ static int tpm2_persist_handle( return 1; } + if ((rc & ~TPM2_RC_N_MASK) == TPM2_RC_BAD_AUTH) + return log_debug_errno(SYNTHETIC_ERRNO(EDEADLK), "Authorization failure while attempting to persist handle."); if (rc != TPM2_RC_NV_DEFINED) return log_debug_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), "Failed to persist handle: %s", sym_Tss2_RC_Decode(rc)); @@ -1780,6 +1784,7 @@ int tpm2_get_or_create_srk( r = tpm2_create_primary( c, session, + ESYS_TR_RH_OWNER, &template, /* sensitive= */ NULL, /* ret_public= */ NULL, @@ -1805,6 +1810,591 @@ int tpm2_get_or_create_srk( return 1; /* > 0 → SRK newly set up */ } +static bool has_expected_ek_cert_attributes(const TPM2B_NV_PUBLIC *nv_pub) { + /* Determine whether the supplied NV index has the expected attributes for an EK certificate. These + * attributes are defined in the "TCG PC Client Platform TPM Profile Specification for TPM 2.0" spec, + * section 4.6.3.1 (Preprovisioned EK Certificates). + * + * TPMA_NV_OWNERREAD isn't required because some TPMs ship EK certs with only TPMA_NV_AUTHREAD. + * + * https://trustedcomputinggroup.org/resource/pc-client-platform-tpm-profile-ptp-specification/ */ + if ((nv_pub->nvPublic.attributes & TPMA_NV_TPM2_NT_MASK) != (TPM2_NT_ORDINARY << TPMA_NV_TPM2_NT_SHIFT)) + return false; + + static const TPMA_NV expected_clear_attrs = + TPMA_NV_AUTHWRITE | + TPMA_NV_OWNERWRITE | + TPMA_NV_GLOBALLOCK | + TPMA_NV_CLEAR_STCLEAR | + TPMA_NV_ORDERLY | + TPMA_NV_READ_STCLEAR; + static const TPMA_NV expected_set_attrs = + TPMA_NV_PLATFORMCREATE | + TPMA_NV_AUTHREAD | + TPMA_NV_NO_DA; + return (((nv_pub->nvPublic.attributes & expected_set_attrs) == expected_set_attrs) && + ((nv_pub->nvPublic.attributes & expected_clear_attrs) == 0)); +} + +/* A mapping of each EK profile to the NV index handle where the corresponding EK certificate is expected to + * be stored. Each EK profile has a dedicated NV index. See the "TCG EK Credential Profile for TPM Family + * 2.0" spec, sections 2.2.2.4 (low range) and 2.2.2.5 (high range). + * + * https://trustedcomputinggroup.org/resource/http-trustedcomputinggroup-org-wp-content-uploads-tcg-ek-credential-profile/ */ +static const TPM2_HANDLE ek_cert_nv_indexes[_TPM2_EK_TEMPLATE_MAX] = { + [TPM2_EK_TEMPLATE_RSA_2048_LEGACY] = 0x01C00002, + [TPM2_EK_TEMPLATE_ECC_NIST_P256_LEGACY] = 0x01C0000A, + [TPM2_EK_TEMPLATE_RSA_2048] = 0x01C00012, + [TPM2_EK_TEMPLATE_ECC_NIST_P256] = 0x01C00014, + [TPM2_EK_TEMPLATE_ECC_NIST_P384] = 0x01C00016, + [TPM2_EK_TEMPLATE_RSA_3072] = 0x01C0001C, +}; + +#if HAVE_OPENSSL + +static const char* const ek_template_names[_TPM2_EK_TEMPLATE_MAX] = { + [TPM2_EK_TEMPLATE_RSA_2048_LEGACY] = "RSA 2048 (L-1)", + [TPM2_EK_TEMPLATE_ECC_NIST_P256_LEGACY] = "ECC NIST P256 (L-2)", + [TPM2_EK_TEMPLATE_RSA_2048] = "RSA 2048 (H-1)", + [TPM2_EK_TEMPLATE_ECC_NIST_P256] = "ECC NIST P256 (H-2)", + [TPM2_EK_TEMPLATE_ECC_NIST_P384] = "ECC NIST P384 (H-3)", + [TPM2_EK_TEMPLATE_RSA_3072] = "RSA 3072 (H-6)", +}; + +/* Read the EK certificate from its NV index for the specified EK profile. This returns 1 if there is a + * valid certificate, 0 if no valid NV index exists, or < 0 if an error occurs. Note that this doesn't check + * that the obtained certificate has the expected public key algorithm. */ +static int tpm2_read_ek_cert( + Tpm2Context *c, + const Tpm2Handle *session, + Tpm2EKTemplateProfile profile, + X509 **ret_cert) { + + int r; + + assert(c); + assert(profile >= 0); + assert(profile < _TPM2_EK_TEMPLATE_MAX); + assert(ret_cert); + + r = dlopen_libcrypto(LOG_DEBUG); + if (r < 0) + return r; + + TPM2_HANDLE cert_index = ek_cert_nv_indexes[profile]; + + _cleanup_(tpm2_handle_freep) Tpm2Handle *cert_handle = NULL; + _cleanup_(Esys_Freep) TPM2B_NV_PUBLIC *cert_nvpub = NULL; + r = tpm2_nv_index_to_handle(c, cert_index, session, &cert_nvpub, /* ret_name= */ NULL, &cert_handle); + if (r < 0) + return r; + if (r == 0) { + *ret_cert = NULL; + return r; + } + + /* Ignore any NV index that doesn't have the expected attributes, to ensure that we don't generate + * erroneous errors if we encounter some arbitrary owner-created index. */ + if (!has_expected_ek_cert_attributes(cert_nvpub)) { + *ret_cert = NULL; + return 0; + } + + _cleanup_(iovec_done) struct iovec cert_data = {}; + r = tpm2_read_nv_index(c, session, cert_index, cert_handle, &cert_data); + if (r < 0) + return r; + + const unsigned char *p = cert_data.iov_base; + _cleanup_(X509_freep) X509 *cert = sym_d2i_X509(NULL, &p, (long) cert_data.iov_len); + if (!cert) + return log_debug_errno(SYNTHETIC_ERRNO(EBADMSG), + "Failed to parse EK certificate for template '%s' from NV index 0x%08" PRIx32 ": %s", + ek_template_names[profile], cert_index, sym_ERR_error_string(sym_ERR_get_error(), NULL)); + + *ret_cert = TAKE_PTR(cert); + return 1; +} + +#endif + +/* Obtain a custom EK template for the specified EK profile. The "TCG EK Credential Profile for TPM Family + * 2.0" spec details how a TPM manufacturer can certify endorsement keys with templates that differ from the + * default ones by storing a template in a NV index. Returns 1 if there is a custom template, 0 if there is + * no custom template, or < 0 if an error occurred. + * + * https://trustedcomputinggroup.org/resource/http-trustedcomputinggroup-org-wp-content-uploads-tcg-ek-credential-profile/ */ +static int tpm2_get_custom_ek_template( + Tpm2Context *c, + const Tpm2Handle *session, + Tpm2EKTemplateProfile profile, + TPMT_PUBLIC *ret_template) { + + int r; + + assert(c); + assert(profile >= 0); + assert(profile < _TPM2_EK_TEMPLATE_MAX); + assert(ret_template); + + TPM2_HANDLE cert_index = ek_cert_nv_indexes[profile]; + + /* Handle 0x01c00012 is the start of the "High Range" EK certs, with those lower than this being the + * legacy "Low Range" ones. Low range certs can have an optional nonce as well as a custom template. + * High range certs can only have a custom template, defined at the next subsequent (odd) NV index. + * See sections 2.2.2.4 and 2.2.2.5 of the "TCG EK Credential Profile for TPM Family 2.0" spec. */ + TPM2_HANDLE template_index = cert_index >= 0x01C00012 ? cert_index + 1 : cert_index + 2; + TPM2_HANDLE nonce_index = cert_index >= 0x01C00012 ? 0 : cert_index + 1; + + /* First check if there is a custom EK template NV index defined. */ + _cleanup_(tpm2_handle_freep) Tpm2Handle *template_handle = NULL; + _cleanup_(Esys_Freep) TPM2B_NV_PUBLIC *template_nvpub = NULL; + r = tpm2_nv_index_to_handle(c, template_index, session, &template_nvpub, /* ret_name= */ NULL, &template_handle); + if (r < 0) + return r; + if (r > 0 && !has_expected_ek_cert_attributes(template_nvpub)) + /* Ignore this NV index because it doesn't have the expected attributes. */ + template_handle = tpm2_handle_free(template_handle); + + /* Now check if there is a nonce NV index defined. Table 1 of the "TCG EK Credential Profile for TPM + * Family 2.0" spec suggests this combination (EKnonce without EKtemplate) must not appear, but + * apparently this configuration did ship with Intel PTT. */ + _cleanup_(tpm2_handle_freep) Tpm2Handle *nonce_handle = NULL; + _cleanup_(Esys_Freep) TPM2B_NV_PUBLIC *nonce_nvpub = NULL; + if (nonce_index != 0) { + r = tpm2_nv_index_to_handle(c, nonce_index, session, &nonce_nvpub, /* ret_name= */ NULL, &nonce_handle); + if (r < 0) + return r; + if (r > 0 && !has_expected_ek_cert_attributes(nonce_nvpub)) + /* Ignore this NV index because it doesn't have the expected attributes. */ + nonce_handle = tpm2_handle_free(nonce_handle); + } + + if (!template_handle && !nonce_handle) { + /* There is no custom template. */ + *ret_template = (TPMT_PUBLIC){}; + return 0; + } + + TPMT_PUBLIC template = {}; + if (template_handle) { + _cleanup_(iovec_done) struct iovec template_data = {}; + r = tpm2_read_nv_index(c, session, template_index, template_handle, &template_data); + if (r < 0) + return r; + + size_t offset = 0; + TSS2_RC rc = sym_Tss2_MU_TPMT_PUBLIC_Unmarshal(template_data.iov_base, template_data.iov_len, &offset, &template); + if (rc != TSS2_RC_SUCCESS) + return log_debug_errno(SYNTHETIC_ERRNO(EBADMSG), "Failed to unmarshal EK template from NV index 0x%08" PRIx32 ": %s", + template_index, sym_Tss2_RC_Decode(rc)); + if (offset != template_data.iov_len) + return log_debug_errno(SYNTHETIC_ERRNO(EBADMSG), + "Garbage at end of custom template data."); + } else + tpm2_get_default_ek_template(profile, &template); + + if (nonce_handle) { + /* We have a nonce. Section 2.2.2.6 of the "TCG EK Credential Profile for TPM Family 2.0" + * spec explains how to construct the template from this. */ + _cleanup_(iovec_done) struct iovec nonce_data = {}; + r = tpm2_read_nv_index(c, session, nonce_index, nonce_handle, &nonce_data); + if (r < 0) + return r; + + zero(template.unique); + + switch (profile) { + case TPM2_EK_TEMPLATE_RSA_2048_LEGACY: + /* For the RSA-2048 (L-1) template, set unique.rsa to the nonce and append zero + * bytes to pad it to a size of 256-bytes. */ + if (nonce_data.iov_len > 256) + return log_debug_errno(SYNTHETIC_ERRNO(EBADMSG), + "Invalid RSA EK nonce length from NV index 0x%08" PRIx32, nonce_index); + + template.unique.rsa.size = 256; + memcpy_safe(template.unique.rsa.buffer, nonce_data.iov_base, nonce_data.iov_len); + break; + case TPM2_EK_TEMPLATE_ECC_NIST_P256_LEGACY: + /* For the ECC NIST P256 (L-2) template, set unique.ecc.x to the nonce and append + * zero bytes to pad it to a size of 32-bytes. Set unique.ecc.y to 32-bytes of + * zeroes. */ + if (nonce_data.iov_len > 32) + return log_debug_errno(SYNTHETIC_ERRNO(EBADMSG), + "Invalid ECC EK nonce length from NV index 0x%08" PRIx32, nonce_index); + + template.unique.ecc.x.size = template.unique.ecc.y.size = 32; + memcpy_safe(template.unique.ecc.x.buffer, nonce_data.iov_base, nonce_data.iov_len); + break; + default: + assert_not_reached(); + } + } + + *ret_template = template; + return 1; +} + +/* This is the default authorization policy for low-range endorsement keys. It is: + * + * TPM2_PolicySecret(TPM_RH_ENDORSEMENT). + * + * See section 5.3 of the "TCG EK Credential Profile for TPM Family 2.0" spec. */ +#define TPM2_EK_AUTH_POLICY_A \ + { .size = TPM2_SHA256_DIGEST_SIZE, \ + .buffer = { \ + 0x83, 0x71, 0x97, 0x67, 0x44, 0x84, 0xB3, 0xF8, \ + 0x1A, 0x90, 0xCC, 0x8D, 0x46, 0xA5, 0xD7, 0x24, \ + 0xFD, 0x52, 0xD7, 0x6E, 0x06, 0x52, 0x0B, 0x64, \ + 0xF2, 0xA1, 0xDA, 0x1B, 0x33, 0x14, 0x69, 0xAA, \ + }, \ + } + +/* This is the default SHA-256 authorization policy for high-range endorsement keys. High-range EKs + * define a policy that permits the privacy administrator to delegate admin usage of the endorsement key via + * TPM2_PolicyAuthorizeNV. There is one NV index defined per name algorithm. These and their public + * attributes are specified in secion A.1 of the "TCG EK Credential Profile for TPM Family 2.0" spec. + * + * This policy is: + * + * TPM2_PolicySecret(TPM_RH_ENDORSEMENT) OR TPM2_PolicyAuthorizeNV(0x01C07F01). */ +#define TPM2_EK_AUTH_POLICY_B_SHA256 \ + { .size = TPM2_SHA256_DIGEST_SIZE, \ + .buffer = { \ + 0xCA, 0x3D, 0x0A, 0x99, 0xA2, 0xB9, 0x39, 0x06, \ + 0xF7, 0xA3, 0x34, 0x24, 0x14, 0xEF, 0xCF, 0xB3, \ + 0xA3, 0x85, 0xD4, 0x4C, 0xD1, 0xFD, 0x45, 0x90, \ + 0x89, 0xD1, 0x9B, 0x50, 0x71, 0xC0, 0xB7, 0xA0, \ + }, \ + } + +/* This is the default SHA-384 authorization policy for high-range endorsement keys. High-range EKs + * define a policy that permits the privacy administrator to delegate admin usage of the endorsement key via + * TPM2_PolicyAuthorizeNV. There is one NV index defined per name algorithm. These and their public + * attributes are specified in secion A.1 of the "TCG EK Credential Profile for TPM Family 2.0" spec. + * + * This policy is: + * + * TPM2_PolicySecret(TPM_RH_ENDORSEMENT) OR TPM2_PolicyAuthorizeNV(0x01C07F02). */ +#define TPM2_EK_AUTH_POLICY_B_SHA384 \ + { .size = TPM2_SHA384_DIGEST_SIZE, \ + .buffer = { \ + 0xB2, 0x6E, 0x7D, 0x28, 0xD1, 0x1A, 0x50, 0xBC, \ + 0x53, 0xD8, 0x82, 0xBC, 0xF5, 0xFD, 0x3A, 0x1A, \ + 0x07, 0x41, 0x48, 0xBB, 0x35, 0xD3, 0xB4, 0xE4, \ + 0xCB, 0x1C, 0x0A, 0xD9, 0xBD, 0xE4, 0x19, 0xCA, \ + 0xCB, 0x47, 0xBA, 0x09, 0x69, 0x96, 0x46, 0x15, \ + 0x0F, 0x9F, 0xC0, 0x00, 0xF3, 0xF8, 0x0E, 0x12 \ + }, \ + } + +/* Get the default Endorsement Key (EK) template for the specified EK profile. + * + * EK template values are defined in the "TCG EK Credential Profile for TPM Family 2.0" spec in sections + * 5.3 (low-range) and 5.4 (high-range). There are multiple templates for both RSA and ECC keys, with varying + * key sizes or curves. The high-range also defines templates for storage and signing keys, although we only + * support storage EKs. There are templates for PQC algorithms, although these aren't supported here yet. The + * supported templates are the storage RSA and ECC templates required by the "TCG PC Client Platform TPM + * Profile (PTP)" spec. + * + * https://trustedcomputinggroup.org/resource/http-trustedcomputinggroup-org-wp-content-uploads-tcg-ek-credential-profile + * https://trustedcomputinggroup.org/resource/pc-client-platform-tpm-profile-ptp-specification/ */ +void tpm2_get_default_ek_template(Tpm2EKTemplateProfile profile, TPMT_PUBLIC *ret_template) { + assert(profile >= 0); + assert(profile < _TPM2_EK_TEMPLATE_MAX); + assert(ret_template); + + static const TPMA_OBJECT attributes_a = + TPMA_OBJECT_FIXEDTPM | + TPMA_OBJECT_FIXEDPARENT | + TPMA_OBJECT_SENSITIVEDATAORIGIN | + TPMA_OBJECT_ADMINWITHPOLICY | + TPMA_OBJECT_RESTRICTED | + TPMA_OBJECT_DECRYPT; + + static const TPMA_OBJECT attributes_b = attributes_a | TPMA_OBJECT_USERWITHAUTH; + + static const struct { + TPMI_ALG_PUBLIC alg; + TPMI_ALG_HASH name_alg; + TPMA_OBJECT attrs; + TPM2B_DIGEST auth_policy; + union { + TPMI_RSA_KEY_BITS rsa_key_bits; + TPMI_ECC_CURVE ecc_curve_id; + } asym; + TPMU_PUBLIC_ID unique; + TPMI_AES_KEY_BITS sym_key_bits; + } template_params[_TPM2_EK_TEMPLATE_MAX] = { + [TPM2_EK_TEMPLATE_RSA_2048_LEGACY] = { + .alg = TPM2_ALG_RSA, + .name_alg = TPM2_ALG_SHA256, + .attrs = attributes_a, + .auth_policy = TPM2_EK_AUTH_POLICY_A, + .asym.rsa_key_bits = 2048, + .unique.rsa.size = 256, + .sym_key_bits = 128, + }, + [TPM2_EK_TEMPLATE_ECC_NIST_P256_LEGACY] = { + .alg = TPM2_ALG_ECC, + .name_alg = TPM2_ALG_SHA256, + .attrs = attributes_a, + .auth_policy = TPM2_EK_AUTH_POLICY_A, + .asym.ecc_curve_id = TPM2_ECC_NIST_P256, + .unique.ecc.x.size = 32, + .unique.ecc.y.size = 32, + .sym_key_bits = 128, + }, + [TPM2_EK_TEMPLATE_RSA_2048] = { + .alg = TPM2_ALG_RSA, + .name_alg = TPM2_ALG_SHA256, + .attrs = attributes_b, + .auth_policy = TPM2_EK_AUTH_POLICY_B_SHA256, + .asym.rsa_key_bits = 2048, + .unique.rsa.size = 0, + .sym_key_bits = 128, + }, + [TPM2_EK_TEMPLATE_ECC_NIST_P256] = { + .alg = TPM2_ALG_ECC, + .name_alg = TPM2_ALG_SHA256, + .attrs = attributes_b, + .auth_policy = TPM2_EK_AUTH_POLICY_B_SHA256, + .asym.ecc_curve_id = TPM2_ECC_NIST_P256, + .unique.ecc.x.size = 0, + .unique.ecc.y.size = 0, + .sym_key_bits = 128, + }, + [TPM2_EK_TEMPLATE_ECC_NIST_P384] = { + .alg = TPM2_ALG_ECC, + .name_alg = TPM2_ALG_SHA384, + .attrs = attributes_b, + .auth_policy = TPM2_EK_AUTH_POLICY_B_SHA384, + .asym.ecc_curve_id = TPM2_ECC_NIST_P384, + .unique.ecc.x.size = 0, + .unique.ecc.y.size = 0, + .sym_key_bits = 256, + }, + [TPM2_EK_TEMPLATE_RSA_3072] = { + .alg = TPM2_ALG_RSA, + .name_alg = TPM2_ALG_SHA384, + .attrs = attributes_b, + .auth_policy = TPM2_EK_AUTH_POLICY_B_SHA384, + .asym.rsa_key_bits = 3072, + .unique.rsa.size = 0, + .sym_key_bits = 256, + }, + }; + + const typeof(template_params[0]) *params = &template_params[profile]; + + TPMT_PUBLIC template = { + .type = params->alg, + .nameAlg = params->name_alg, + .objectAttributes = params->attrs, + .authPolicy = params->auth_policy, + .parameters.asymDetail = (TPMS_ASYM_PARMS){ + .symmetric = (TPMT_SYM_DEF_OBJECT){ + .algorithm = TPM2_ALG_AES, + .keyBits.aes = params->sym_key_bits, + .mode.aes = TPM2_ALG_CFB, + }, + .scheme.scheme = TPM2_ALG_NULL, + }, + .unique = params->unique, + }; + + switch (template.type) { + case TPM2_ALG_RSA: + template.parameters.rsaDetail.keyBits = params->asym.rsa_key_bits; + break; + case TPM2_ALG_ECC: + template.parameters.eccDetail.curveID = params->asym.ecc_curve_id; + template.parameters.eccDetail.kdf.scheme = TPM2_ALG_NULL; + break; + default: + assert_not_reached(); + } + + *ret_template = template; +} + +/* Get the Endorsement Key (EK) template for the specified EK profile. + * + * EK template values are defined in the "TCG EK Credential Profile for TPM Family 2.0" spec in sections + * 5.3 (low-range) and 5.4 (high-range). There are multiple templates for both RSA and ECC keys, with varying + * key sizes or curves. The high-range also defines templates for storage and signing keys, although we only + * support storage EKs. There are templates for PQC algorithms, although these aren't supported here yet. The + * supported templates are the storage RSA and ECC templates required by the "TCG PC Client Platform TPM + * Profile (PTP)" spec. + * + * This will return a custom template for the specified profile if one is defined, else it will return the + * default template. Returns 0 on success, or < 0 if an error occurred. + * + * https://trustedcomputinggroup.org/resource/http-trustedcomputinggroup-org-wp-content-uploads-tcg-ek-credential-profile + * https://trustedcomputinggroup.org/resource/pc-client-platform-tpm-profile-ptp-specification/ */ +int tpm2_get_ek_template( + Tpm2Context *c, + const Tpm2Handle *session, + Tpm2EKTemplateProfile profile, + TPMT_PUBLIC *ret_template) { + + int r; + + r = tpm2_get_custom_ek_template(c, session, profile, ret_template); + if (r < 0) + return r; + if (r == 0) + tpm2_get_default_ek_template(profile, ret_template); + + return 0; +} + +/* Get the EK. Returns 1 if found, 0 if there is no EK, or < 0 on error. Also see + * tpm2_get_or_create_ek() below. */ +int tpm2_get_ek( + Tpm2Context *c, + const Tpm2Handle *session, + TPM2B_PUBLIC **ret_public, + TPM2B_NAME **ret_name, + TPM2B_NAME **ret_qname, + Tpm2Handle **ret_handle) { + + return tpm2_object_index_to_handle(c, TPM2_EK_HANDLE, session, ret_public, ret_name, ret_qname, ret_handle); +} + +/* Get the EK, creating one if needed. This tries each of the supported templates in order of preference, + * and will persist and return an EK created with the first template that has an EK certificate, and where + * the created EK matches the certificate's public key. + * + * Returns 1 if a new EK was created and persisted, 0 if an EK already exists, or < 0 on error. */ +int tpm2_get_or_create_ek( + Tpm2Context *c, + const Tpm2Handle *session, + TPM2B_PUBLIC **ret_public, + TPM2B_NAME **ret_name, + TPM2B_NAME **ret_qname, + Tpm2Handle **ret_handle) { + +#if HAVE_OPENSSL + int r; + + r = dlopen_libcrypto(LOG_DEBUG); + if (r < 0) + return r; + + r = tpm2_get_ek(c, session, ret_public, ret_name, ret_qname, ret_handle); + if (r < 0) + return r; + if (r == 1) + return 0; /* 0 → EK already set up */ + + /* No EK, create and persist one */ + static const Tpm2EKTemplateProfile profile_preferences[] = { + TPM2_EK_TEMPLATE_ECC_NIST_P384, + TPM2_EK_TEMPLATE_ECC_NIST_P256, + TPM2_EK_TEMPLATE_RSA_3072, + TPM2_EK_TEMPLATE_RSA_2048, + TPM2_EK_TEMPLATE_ECC_NIST_P256_LEGACY, + TPM2_EK_TEMPLATE_RSA_2048_LEGACY, + }; + + FOREACH_ELEMENT(p, profile_preferences) { + Tpm2EKTemplateProfile profile = *p; + + log_debug("Trying to create EK with template '%s'", ek_template_names[profile]); + + /* Check if there is a certificate for this profile. */ + _cleanup_(X509_freep) X509 *cert = NULL; + r = tpm2_read_ek_cert(c, session, profile, &cert); + if (r == -EBADMSG) + continue; /* Certificate populated but invalid. */ + if (r < 0) + return r; + if (r == 0) + continue; /* No certificate populated. */ + + EVP_PKEY *cert_pkey = sym_X509_get0_pubkey(cert); + if (!cert_pkey) { + log_debug("Skipping EK creation with template '%s': cannot get EKcert public key", ek_template_names[profile]); + continue; + } + + /* Obtain the template for this profile. */ + TPM2B_PUBLIC template = { + .size = sizeof(TPMT_PUBLIC), + }; + r = tpm2_get_ek_template(c, session, profile, &template.publicArea); + if (r == -EBADMSG) + continue; /* Custom template or nonce is invalid. */ + if (r < 0) + return r; + + /* Check that this profile is supported. */ + if (!tpm2_supports_alg(c, template.publicArea.type)) { + log_debug("Skipping EK creation with template '%s': unsupported object type", ek_template_names[profile]); + continue; + } + if (!tpm2_supports_tpmt_public(c, &template.publicArea)) { + log_debug("Skipping EK creation with template '%s': unsupported template", ek_template_names[profile]); + continue; + } + if (template.publicArea.type == TPM2_ALG_ECC && !tpm2_supports_ecc_curve(c, template.publicArea.parameters.eccDetail.curveID)) { + log_debug("Skipping EK creation with template '%s': unsupported curve", ek_template_names[profile]); + continue; + } + + /* Create the transient EK now. */ + _cleanup_(tpm2_handle_freep) Tpm2Handle *transient_handle = NULL; + r = tpm2_create_primary( + c, + session, + ESYS_TR_RH_ENDORSEMENT, + &template, + /* sensitive= */ NULL, + /* ret_public= */ NULL, + &transient_handle); + if (r < 0) + return r; + + _cleanup_(Esys_Freep) TPM2B_PUBLIC *public = NULL; + r = tpm2_read_public(c, session, transient_handle, &public, /* ret_name= */ NULL, /* ret_qname= */ NULL); + if (r < 0) + return r; + + /* Check that the created EK has a public key that matches the one in the certificate. */ + _cleanup_(EVP_PKEY_freep) EVP_PKEY *tpm_pkey = NULL; + r = tpm2_tpm2b_public_to_openssl_pkey(public, &tpm_pkey); + if (r < 0) + return r; + + if (sym_EVP_PKEY_eq(tpm_pkey, cert_pkey) != 1) { + log_debug("EK created with template '%s' does not match EKcert public key", ek_template_names[profile]); + continue; + } + + /* We've created a valid EK that is consistent with the supplied certificate, so persist this + * one and return it. */ + r = tpm2_persist_handle(c, transient_handle, session, TPM2_EK_HANDLE, /* ret_persistent_handle= */ NULL); + if (r < 0) + return r; + + r = tpm2_get_ek(c, session, ret_public, ret_name, ret_qname, ret_handle); + if (r < 0) + return r; + if (r == 0) + /* This should never happen. */ + return log_debug_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), "EK we just persisted couldn't be found."); + + return 1; /* > 0 → EK newly set up */ + } + + return log_debug_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), + "No usable EK certificate found for any supported template."); +#else + return log_debug_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "OpenSSL support is disabled."); +#endif +} + /* Utility functions for TPMS_PCR_SELECTION. */ /* Convert a TPMS_PCR_SELECTION object to a mask. */ @@ -2482,6 +3072,7 @@ static int tpm2_get_policy_digest( int tpm2_create_primary( Tpm2Context *c, const Tpm2Handle *session, + ESYS_TR hierarchy, const TPM2B_PUBLIC *template, const TPM2B_SENSITIVE_CREATE *sensitive, TPM2B_PUBLIC **ret_public, @@ -2506,7 +3097,7 @@ int tpm2_create_primary( _cleanup_(Esys_Freep) TPM2B_PUBLIC *public = NULL; rc = sym_Esys_CreatePrimary( c->esys_context, - ESYS_TR_RH_OWNER, + hierarchy, session ? session->esys_handle : ESYS_TR_PASSWORD, ESYS_TR_NONE, ESYS_TR_NONE, @@ -2519,7 +3110,7 @@ int tpm2_create_primary( /* creationData= */ NULL, /* creationHash= */ NULL, /* creationTicket= */ NULL); - if (rc == TPM2_RC_BAD_AUTH) + if ((rc & ~TPM2_RC_N_MASK) == TPM2_RC_BAD_AUTH) return log_debug_errno(SYNTHETIC_ERRNO(EDEADLK), "Authorization failure while attempting to create primary key."); if (rc != TSS2_RC_SUCCESS) return log_debug_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), @@ -5893,6 +6484,7 @@ int tpm2_seal(Tpm2Context *c, r = tpm2_create_primary( c, /* session= */ NULL, + ESYS_TR_RH_OWNER, &template, /* sensitive= */ NULL, /* ret_public= */ NULL, @@ -6058,6 +6650,7 @@ int tpm2_unseal(Tpm2Context *c, r = tpm2_create_primary( c, /* session= */ NULL, + ESYS_TR_RH_OWNER, &template, /* sensitive= */ NULL, /* ret_public= */ NULL, diff --git a/src/shared/tpm2-util.h b/src/shared/tpm2-util.h index 43461215477..042d5d7d182 100644 --- a/src/shared/tpm2-util.h +++ b/src/shared/tpm2-util.h @@ -26,6 +26,11 @@ typedef enum TPM2Flags { * the Provisioning Guidance document for more details. */ #define TPM2_SRK_HANDLE UINT32_C(0x81000001) +/* The EK handle is defined in the Provisioning Guidance document (see above) in the table "Reserved Handles + * for TPM Provisioning Fundamental Elements". The NV indices are from the "TCG EK Credential Profile for + * TPM Family 2.0" specification (low range). */ +#define TPM2_EK_HANDLE UINT32_C(0x81010001) + /* The TPM specification limits sealed data to MAX_SYM_DATA. Unfortunately, tpm2-tss incorrectly * defines this value as 256; the TPM specification Part 2 ("Structures") section * "TPMU_SENSITIVE_CREATE" states "For interoperability, MAX_SYM_DATA should be 128." */ @@ -141,7 +146,7 @@ int tpm2_tpml_pcr_selection_from_pcr_values(const Tpm2PCRValue *pcr_values, size int tpm2_make_encryption_session(Tpm2Context *c, const Tpm2Handle *primary, const Tpm2Handle *bind_key, Tpm2Handle **ret_session); -int tpm2_create_primary(Tpm2Context *c, const Tpm2Handle *session, const TPM2B_PUBLIC *template, const TPM2B_SENSITIVE_CREATE *sensitive, TPM2B_PUBLIC **ret_public, Tpm2Handle **ret_handle); +int tpm2_create_primary(Tpm2Context *c, const Tpm2Handle *session, ESYS_TR hierarchy, const TPM2B_PUBLIC *template, const TPM2B_SENSITIVE_CREATE *sensitive, TPM2B_PUBLIC **ret_public, Tpm2Handle **ret_handle); int tpm2_create(Tpm2Context *c, const Tpm2Handle *parent, const Tpm2Handle *session, const TPMT_PUBLIC *template, const TPMS_SENSITIVE_CREATE *sensitive, TPM2B_PUBLIC **ret_public, TPM2B_PRIVATE **ret_private); int tpm2_load(Tpm2Context *c, const Tpm2Handle *parent, const Tpm2Handle *session, const TPM2B_PUBLIC *public, const TPM2B_PRIVATE *private, Tpm2Handle **ret_handle); int tpm2_marshal_public(const TPM2B_PUBLIC *public, void **ret, size_t *ret_size); @@ -346,6 +351,24 @@ int tpm2_get_best_srk_template(Tpm2Context *c, TPMT_PUBLIC *ret_template); int tpm2_get_srk(Tpm2Context *c, const Tpm2Handle *session, TPM2B_PUBLIC **ret_public, TPM2B_NAME **ret_name, TPM2B_NAME **ret_qname, Tpm2Handle **ret_handle); int tpm2_get_or_create_srk(Tpm2Context *c, const Tpm2Handle *session, TPM2B_PUBLIC **ret_public, TPM2B_NAME **ret_name, TPM2B_NAME **ret_qname, Tpm2Handle **ret_handle); +typedef enum Tpm2EKTemplateProfile { + TPM2_EK_TEMPLATE_RSA_2048_LEGACY, /* RSA 2048 storage (L-1) */ + TPM2_EK_TEMPLATE_ECC_NIST_P256_LEGACY, /* ECC NIST P256 storage (L-2) */ + TPM2_EK_TEMPLATE_RSA_2048, /* RSA 2048 storage (H-1) */ + TPM2_EK_TEMPLATE_ECC_NIST_P256, /* ECC NIST P256 storage (H-2) */ + TPM2_EK_TEMPLATE_ECC_NIST_P384, /* ECC NIST P384 storage (H-3) */ + TPM2_EK_TEMPLATE_RSA_3072, /* RSA 3072 storage (H-6) */ + + _TPM2_EK_TEMPLATE_MAX, + _TPM2_EK_TEMPLATE_INVALID = -EINVAL, +} Tpm2EKTemplateProfile; + +void tpm2_get_default_ek_template(Tpm2EKTemplateProfile profile, TPMT_PUBLIC *ret_template); +int tpm2_get_ek_template(Tpm2Context *c, const Tpm2Handle *session, Tpm2EKTemplateProfile profile, TPMT_PUBLIC *ret_template); + +int tpm2_get_ek(Tpm2Context *c, const Tpm2Handle *session, TPM2B_PUBLIC **ret_public, TPM2B_NAME **ret_name, TPM2B_NAME **ret_qname, Tpm2Handle **ret_handle); +int tpm2_get_or_create_ek(Tpm2Context *c, const Tpm2Handle *session, TPM2B_PUBLIC **ret_public, TPM2B_NAME **ret_name, TPM2B_NAME **ret_qname, Tpm2Handle **ret_handle); + int tpm2_seal(Tpm2Context *c, uint32_t seal_key_handle, const TPM2B_DIGEST policy_hash[], size_t n_policy, const char *pin, struct iovec *ret_secret, struct iovec **ret_blobs, size_t *ret_n_blobs, uint16_t *ret_primary_alg, struct iovec *ret_srk); int tpm2_unseal(Tpm2Context *c, uint32_t hash_pcr_mask, uint16_t pcr_bank, const struct iovec *pubkey, uint32_t pubkey_pcr_mask, sd_json_variant *signature, const char *pin, const Tpm2PCRLockPolicy *pcrlock_policy, uint16_t primary_alg, const struct iovec blobs[], size_t n_blobs, const struct iovec known_policy_hash[], size_t n_known_policy_hash, const struct iovec *srk, struct iovec *ret_secret); diff --git a/src/systemd/sd-messages.h b/src/systemd/sd-messages.h index bb6a17d9b6e..25cf03e18ed 100644 --- a/src/systemd/sd-messages.h +++ b/src/systemd/sd-messages.h @@ -288,6 +288,8 @@ _SD_BEGIN_DECLARATIONS; #define SD_MESSAGE_SRK_ENROLLMENT_NEEDS_AUTHORIZATION SD_ID128_MAKE(ad,70,89,f9,28,ac,4f,7e,a0,0c,07,45,7d,47,ba,8a) #define SD_MESSAGE_SRK_ENROLLMENT_NEEDS_AUTHORIZATION_STR SD_ID128_MAKE_STR(ad,70,89,f9,28,ac,4f,7e,a0,0c,07,45,7d,47,ba,8a) +#define SD_MESSAGE_EK_ENROLLMENT_NEEDS_AUTHORIZATION SD_ID128_MAKE(8d,81,3b,6b,ec,f0,4b,85,9d,99,76,0c,bb,56,df,f1) +#define SD_MESSAGE_EK_ENROLLMENT_NEEDS_AUTHORIZATION_STR SD_ID128_MAKE_STR(8d,81,3b,6b,ec,f0,4b,85,9d,99,76,0c,bb,56,df,f1) #define SD_MESSAGE_TPM2_CLEAR_REQUESTED SD_ID128_MAKE(43,81,88,86,1e,0b,42,7a,9d,63,8a,90,48,7a,0c,a6) #define SD_MESSAGE_TPM2_CLEAR_REQUESTED_STR SD_ID128_MAKE_STR(43,81,88,86,1e,0b,42,7a,9d,63,8a,90,48,7a,0c,a6) diff --git a/src/test/test-tpm2.c b/src/test/test-tpm2.c index 19ee7b5a723..863092b9ce3 100644 --- a/src/test/test-tpm2.c +++ b/src/test/test-tpm2.c @@ -1122,6 +1122,107 @@ static void check_best_srk_template(Tpm2Context *c) { check_srk_ecc_template(&template); } +static void check_ek_template_a(TPMT_PUBLIC *template) { + ASSERT_EQ(template->nameAlg, TPM2_ALG_SHA256); + ASSERT_EQ(template->objectAttributes, TPMA_OBJECT_FIXEDTPM | TPMA_OBJECT_FIXEDPARENT | TPMA_OBJECT_SENSITIVEDATAORIGIN | TPMA_OBJECT_ADMINWITHPOLICY | TPMA_OBJECT_RESTRICTED | TPMA_OBJECT_DECRYPT); + ASSERT_TRUE(digest_check(&template->authPolicy, "837197674484B3F81A90CC8D46A5D724FD52D76E06520B64F2A1DA1B331469AA")); + ASSERT_EQ(template->parameters.asymDetail.symmetric.algorithm, TPM2_ALG_AES); + ASSERT_EQ(template->parameters.asymDetail.symmetric.keyBits.sym, 128); + ASSERT_EQ(template->parameters.asymDetail.symmetric.mode.sym, TPM2_ALG_CFB); +} + +static void check_ek_rsa_template_a(TPMT_PUBLIC *template) { + ASSERT_EQ(template->type, TPM2_ALG_RSA); + + check_ek_template_a(template); + + ASSERT_EQ(template->parameters.rsaDetail.scheme.scheme, TPM2_ALG_NULL); + ASSERT_EQ(template->parameters.rsaDetail.keyBits, 2048); +} + +static void check_ek_ecc_template_a(TPMT_PUBLIC *template) { + ASSERT_EQ(template->type, TPM2_ALG_ECC); + + check_ek_template_a(template); + + ASSERT_EQ(template->parameters.eccDetail.scheme.scheme, TPM2_ALG_NULL); + ASSERT_EQ(template->parameters.eccDetail.kdf.scheme, TPM2_ALG_NULL); + ASSERT_EQ(template->parameters.eccDetail.curveID, TPM2_ECC_NIST_P256); +} + +static void check_ek_template_b(TPMT_PUBLIC *template, TPMI_ALG_HASH expect_name_alg) { + ASSERT_EQ(template->nameAlg, expect_name_alg); + ASSERT_EQ(template->objectAttributes, TPMA_OBJECT_FIXEDTPM | TPMA_OBJECT_FIXEDPARENT | TPMA_OBJECT_SENSITIVEDATAORIGIN | TPMA_OBJECT_ADMINWITHPOLICY | TPMA_OBJECT_RESTRICTED | TPMA_OBJECT_DECRYPT | TPMA_OBJECT_USERWITHAUTH); + + switch (expect_name_alg) { + case TPM2_ALG_SHA256: + ASSERT_TRUE(digest_check(&template->authPolicy, "CA3D0A99A2B93906F7A3342414EFCFB3A385D44CD1FD459089D19B5071C0B7A0")); + break; + case TPM2_ALG_SHA384: + ASSERT_TRUE(digest_check(&template->authPolicy, "B26E7D28D11A50BC53D882BCF5FD3A1A074148BB35D3B4E4CB1C0AD9BDE419CACB47BA09699646150F9FC000F3F80E12")); + break; + default: + assert_not_reached(); + } + + ASSERT_EQ(template->parameters.asymDetail.symmetric.algorithm, TPM2_ALG_AES); + ASSERT_EQ(template->parameters.asymDetail.symmetric.keyBits.sym, expect_name_alg == TPM2_ALG_SHA256 ? 128 : 256); + ASSERT_EQ(template->parameters.asymDetail.symmetric.mode.sym, TPM2_ALG_CFB); +} + +static void check_ek_rsa_template_b( + TPMT_PUBLIC *template, + TPMI_ALG_HASH expect_name_alg, + uint16_t expect_key_bits) { + ASSERT_EQ(template->type, TPM2_ALG_RSA); + + check_ek_template_b(template, expect_name_alg); + + ASSERT_EQ(template->parameters.rsaDetail.scheme.scheme, TPM2_ALG_NULL); + ASSERT_EQ(template->parameters.rsaDetail.keyBits, expect_key_bits); +} + +static void check_ek_ecc_template_b( + TPMT_PUBLIC *template, + TPMI_ALG_HASH expect_name_alg, + TPMI_ECC_CURVE expect_curve_id) { + ASSERT_EQ(template->type, TPM2_ALG_ECC); + + check_ek_template_b(template, expect_name_alg); + + ASSERT_EQ(template->parameters.eccDetail.scheme.scheme, TPM2_ALG_NULL); + ASSERT_EQ(template->parameters.eccDetail.kdf.scheme, TPM2_ALG_NULL); + ASSERT_EQ(template->parameters.eccDetail.curveID, expect_curve_id); +} + +TEST(tpm2_get_default_ek_template) { + TPMT_PUBLIC template; + + tpm2_get_default_ek_template(TPM2_EK_TEMPLATE_RSA_2048_LEGACY, &template); + check_ek_rsa_template_a(&template); + memset(&template, 0, sizeof(template)); + + tpm2_get_default_ek_template(TPM2_EK_TEMPLATE_ECC_NIST_P256_LEGACY, &template); + check_ek_ecc_template_a(&template); + memset(&template, 0, sizeof(template)); + + tpm2_get_default_ek_template(TPM2_EK_TEMPLATE_RSA_2048, &template); + check_ek_rsa_template_b(&template, TPM2_ALG_SHA256, 2048); + memset(&template, 0, sizeof(template)); + + tpm2_get_default_ek_template(TPM2_EK_TEMPLATE_ECC_NIST_P256, &template); + check_ek_ecc_template_b(&template, TPM2_ALG_SHA256, TPM2_ECC_NIST_P256); + memset(&template, 0, sizeof(template)); + + tpm2_get_default_ek_template(TPM2_EK_TEMPLATE_ECC_NIST_P384, &template); + check_ek_ecc_template_b(&template, TPM2_ALG_SHA384, TPM2_ECC_NIST_P384); + memset(&template, 0, sizeof(template)); + + tpm2_get_default_ek_template(TPM2_EK_TEMPLATE_RSA_3072, &template); + check_ek_rsa_template_b(&template, TPM2_ALG_SHA384, 3072); + memset(&template, 0, sizeof(template)); +} + static void check_test_parms(Tpm2Context *c) { assert(c); @@ -1272,7 +1373,7 @@ static int check_calculate_seal(Tpm2Context *c) { _cleanup_free_ TPM2B_PUBLIC *public = NULL; _cleanup_(tpm2_handle_freep) Tpm2Handle *handle = NULL; - assert_se(tpm2_create_primary(c, NULL, &template, NULL, &public, &handle) >= 0); + assert_se(tpm2_create_primary(c, NULL, ESYS_TR_RH_OWNER, &template, NULL, &public, &handle) >= 0); /* Once our minimum libtss2-esys version is 2.4.0 or later, this can assume * tpm2_index_from_handle() should always work. */ @@ -1357,6 +1458,7 @@ static void check_seal_unseal(Tpm2Context *c) { assert_se(tpm2_create_primary( c, /* session= */ NULL, + ESYS_TR_RH_OWNER, &public, /* sensitive= */ NULL, /* ret_public= */ NULL, @@ -1450,6 +1552,106 @@ static void check_nv_index_read(Tpm2Context *c) { ASSERT_OK_ZERO(tpm2_undefine_nv_index(c, /* session= */ NULL, nv_index, nv_handle)); } +static void check_get_ek_template(Tpm2Context *c) { + assert(c); + + TEST_LOG_FUNC(); + + /* Note that this assumes that there aren't any custom templates, and doesn't test the support for + * custom templates. Testing this requires the use of the TPM simulator for platform hierarchy + * access. */ + + TPMT_PUBLIC template; + + ASSERT_OK_ZERO(tpm2_get_ek_template(c, /* session= */ NULL, TPM2_EK_TEMPLATE_RSA_2048_LEGACY, &template)); + check_ek_rsa_template_a(&template); + memset(&template, 0, sizeof(template)); + + ASSERT_OK_ZERO(tpm2_get_ek_template(c, /* session= */ NULL, TPM2_EK_TEMPLATE_ECC_NIST_P256_LEGACY, &template)); + check_ek_ecc_template_a(&template); + memset(&template, 0, sizeof(template)); + + ASSERT_OK_ZERO(tpm2_get_ek_template(c, /* session= */ NULL, TPM2_EK_TEMPLATE_RSA_2048, &template)); + check_ek_rsa_template_b(&template, TPM2_ALG_SHA256, 2048); + memset(&template, 0, sizeof(template)); + + ASSERT_OK_ZERO(tpm2_get_ek_template(c, /* session= */ NULL, TPM2_EK_TEMPLATE_ECC_NIST_P256, &template)); + check_ek_ecc_template_b(&template, TPM2_ALG_SHA256, TPM2_ECC_NIST_P256); + memset(&template, 0, sizeof(template)); + + ASSERT_OK_ZERO(tpm2_get_ek_template(c, /* session= */ NULL, TPM2_EK_TEMPLATE_ECC_NIST_P384, &template)); + check_ek_ecc_template_b(&template, TPM2_ALG_SHA384, TPM2_ECC_NIST_P384); + memset(&template, 0, sizeof(template)); + + ASSERT_OK_ZERO(tpm2_get_ek_template(c, /* session= */ NULL, TPM2_EK_TEMPLATE_RSA_3072, &template)); + check_ek_rsa_template_b(&template, TPM2_ALG_SHA384, 3072); + memset(&template, 0, sizeof(template)); +} + +static void check_get_or_create_ek(Tpm2Context *c) { + int r; + + assert(c); + + TEST_LOG_FUNC(); + + /* This test relies on the existance of an EKcert for a supported profile. Don't fail the test if + * there isn't one. */ + _cleanup_free_ TPM2B_PUBLIC *public = NULL; + _cleanup_free_ TPM2B_NAME *name = NULL, *qname = NULL; + _cleanup_(tpm2_handle_freep) Tpm2Handle *handle = NULL; + r = tpm2_get_or_create_ek(c, /* session= */ NULL, &public, &name, &qname, &handle); + ASSERT_OK_OR(r, -EOPNOTSUPP); + if (r < 0) + return; + + ASSERT_NOT_NULL(public); + ASSERT_NOT_NULL(name); + ASSERT_NOT_NULL(qname); + ASSERT_NOT_NULL(handle); + + ASSERT_TRUE(IN_SET(public->publicArea.type, TPM2_ALG_RSA, TPM2_ALG_ECC)); + + if ((public->publicArea.objectAttributes & TPMA_OBJECT_USERWITHAUTH) == 0) { + /* Test against the low-range expectations. */ + switch (public->publicArea.type) { + case TPM2_ALG_RSA: + check_ek_rsa_template_a(&public->publicArea); + break; + case TPM2_ALG_ECC: + check_ek_ecc_template_a(&public->publicArea); + break; + default: + assert_not_reached(); + } + } else { + /* Test against the high-range expectations. */ + switch (public->publicArea.type) { + case TPM2_ALG_RSA: + check_ek_rsa_template_b(&public->publicArea, public->publicArea.nameAlg, public->publicArea.parameters.rsaDetail.keyBits); + break; + case TPM2_ALG_ECC: + check_ek_ecc_template_b(&public->publicArea, public->publicArea.nameAlg, public->publicArea.parameters.eccDetail.curveID); + break; + default: + assert_not_reached(); + } + } + + _cleanup_free_ TPM2B_PUBLIC *public2 = NULL; + _cleanup_free_ TPM2B_NAME *name2 = NULL, *qname2 = NULL; + _cleanup_(tpm2_handle_freep) Tpm2Handle *handle2 = NULL; + ASSERT_OK_POSITIVE(tpm2_get_ek(c, /* session= */ NULL, &public2, &name2, &qname2, &handle2)); + ASSERT_NOT_NULL(public2); + ASSERT_NOT_NULL(name2); + ASSERT_NOT_NULL(qname2); + ASSERT_NOT_NULL(handle2); + + ASSERT_EQ(memcmp_nn(public, sizeof(*public), public2, sizeof(*public2)), 0); + ASSERT_EQ(memcmp_nn(name->name, name->size, name2->name, name2->size), 0); + ASSERT_EQ(memcmp_nn(qname->name, qname->size, qname2->name, qname2->size), 0); +} + TEST_RET(tests_which_require_tpm) { _cleanup_(tpm2_context_unrefp) Tpm2Context *c = NULL; int r = 0; @@ -1464,6 +1666,8 @@ TEST_RET(tests_which_require_tpm) { check_get_or_create_srk(c); check_seal_unseal(c); check_nv_index_read(c); + check_get_ek_template(c); + check_get_or_create_ek(c); #if HAVE_OPENSSL r = check_calculate_seal(c); diff --git a/src/tpm2-setup/tpm2-setup.c b/src/tpm2-setup/tpm2-setup.c index 20bb3f7011a..bb87cd902f7 100644 --- a/src/tpm2-setup/tpm2-setup.c +++ b/src/tpm2-setup/tpm2-setup.c @@ -55,7 +55,7 @@ static int help(void) { return r; printf("%s [OPTIONS...]\n" - "\n%sSet up the TPM2 Storage Root Key (SRK), and initialize NvPCRs.%s\n" + "\n%sSet up the TPM2 Storage Root Key (SRK) and Endorsement Key (EK), and initialize NvPCRs.%s\n" "\n%sOptions:%s\n", program_invocation_short_name, ansi_highlight(), @@ -555,6 +555,36 @@ static int setup_nvpcr(void) { return ret; } +static int setup_ek(void) { + _cleanup_(tpm2_context_unrefp) Tpm2Context *c = NULL; + int r; + + /* We don't need to create an EK during the early phase. */ + if (arg_early) { + log_debug("Skipping EK setup in early boot phase."); + return 0; + } + + r = tpm2_context_new_or_warn(arg_tpm2_device, &c); + if (r < 0) + return r; + + r = tpm2_get_or_create_ek(c, /* session= */ NULL, /* ret_public= */ NULL, /* ret_name= */ NULL, /* ret_qname= */ NULL, /* ret_handle= */ NULL); + if (r == -EDEADLK) { + log_struct_errno(LOG_INFO, r, + LOG_MESSAGE("Insufficient permissions to access TPM, not generating EK."), + LOG_MESSAGE_ID(SD_MESSAGE_EK_ENROLLMENT_NEEDS_AUTHORIZATION_STR)); + return EX_PROTOCOL; /* Special return value which means "Insufficient permissions to access TPM, + * cannot generate EK". This isn't really an error when called at boot. */ + } + if (r == -EOPNOTSUPP) + return EX_UNAVAILABLE; /* eg, no valid EK certificate. */ + if (r < 0) + return r; + + return 0; +} + static int run(int argc, char *argv[]) { int r; @@ -579,15 +609,20 @@ static int run(int argc, char *argv[]) { umask(0022); - /* Execute both jobs, and then return unlisted errors preferably, and listed errors + /* Execute all jobs, and then return unlisted errors preferably, and listed errors * (i.e. EX_UNAVAILABLE, EX_CANTCREAT, EX_PROTOCOL) otherwise. */ r = setup_srk(); - int k = setup_nvpcr(); + int j = setup_nvpcr(); + int k = setup_ek(); if (r < 0) return r; + if (j < 0) + return j; if (k < 0) return k; - return r != EXIT_SUCCESS ? r : k; + if (r != EXIT_SUCCESS) + return r; + return j != EXIT_SUCCESS ? j : k; } DEFINE_MAIN_FUNCTION_WITH_POSITIVE_FAILURE(run); diff --git a/test/units/TEST-70-TPM2.nvpcr.sh b/test/units/TEST-70-TPM2.nvpcr.sh index 7787fb54ced..2c1c7b0e3ef 100755 --- a/test/units/TEST-70-TPM2.nvpcr.sh +++ b/test/units/TEST-70-TPM2.nvpcr.sh @@ -8,6 +8,7 @@ set -o pipefail export SYSTEMD_LOG_LEVEL=debug SD_PCREXTEND="/usr/lib/systemd/systemd-pcrextend" +SD_TPM2SETUP="/usr/lib/systemd/systemd-tpm2-setup" if [[ ! -x "${SD_PCREXTEND:?}" ]] || ! tpm_has_pcr sha256 11; then echo "$SD_PCREXTEND or PCR sysfs files not found, skipping PCR extension tests" @@ -27,6 +28,15 @@ at_exit() { trap at_exit EXIT +# systemd-tpm2-setup returns EX_UNAVAILABLE rather than 0 when it cannot set something up but this +# is still considered success. This happens at the moment because there is no EK certificate in +# QEMU guests. +run_tpm2_setup() { + local rc=0 + "$SD_TPM2SETUP" || rc=$? + [[ "$rc" -eq 0 || "$rc" -eq 69 ]] +} + # Temporarily override sd-pcrextend's sanity checks export SYSTEMD_FORCE_MEASURE=1 @@ -35,7 +45,7 @@ mkdir -p /run/nvpcr cat >/run/nvpcr/test.nvpcr </run/nvpcr/zzz.nvpcr <