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 <