diff --git a/man/systemd-cryptenroll.xml b/man/systemd-cryptenroll.xml index cd01791acf74b..836538be4c83b 100644 --- a/man/systemd-cryptenroll.xml +++ b/man/systemd-cryptenroll.xml @@ -411,6 +411,28 @@ + + HANDLE + + Configures which parent key to use for sealing, using the TPM handle (index) of the + key. This is used to "seal" (encrypt) a secret and must be used later to "unseal" (decrypt) the + secret. Expects a hexadecimal 32bit integer, optionally prefixed with + 0x. Allowable values are any handle index in the persistent + (0x81000000-0x81ffffff) or transient + (0x80000000-0x80ffffff) ranges. Since transient handles are + lost after a TPM reset, and may be flushed during TPM context switching, they should not be used + except for very specific use cases, e.g. testing. + + The default is the Storage Root Key (SRK) handle index 0x81000001. A value + of 0 will use the default. For the SRK handle, a new key will be created and stored in the TPM if one + does not already exist; for any other handle, the key must already exist in the TPM at the specified + handle index. + + This should not be changed unless you know what you are doing. + + + + PCR diff --git a/src/cryptenroll/cryptenroll-tpm2.c b/src/cryptenroll/cryptenroll-tpm2.c index ca80058c66100..631aeea3b5840 100644 --- a/src/cryptenroll/cryptenroll-tpm2.c +++ b/src/cryptenroll/cryptenroll-tpm2.c @@ -133,6 +133,7 @@ int enroll_tpm2(struct crypt_device *cd, const void *volume_key, size_t volume_key_size, const char *device, + uint32_t seal_key_handle, Tpm2PCRValue *hash_pcr_values, size_t n_hash_pcr_values, const char *pubkey_path, @@ -252,6 +253,7 @@ int enroll_tpm2(struct crypt_device *cd, return r; r = tpm2_seal(tpm2_context, + seal_key_handle, &policy, pin_str, &secret, &secret_size, diff --git a/src/cryptenroll/cryptenroll-tpm2.h b/src/cryptenroll/cryptenroll-tpm2.h index d43a9a8ffe75a..8a57bdda0155e 100644 --- a/src/cryptenroll/cryptenroll-tpm2.h +++ b/src/cryptenroll/cryptenroll-tpm2.h @@ -8,9 +8,9 @@ #include "tpm2-util.h" #if HAVE_TPM2 -int enroll_tpm2(struct crypt_device *cd, const void *volume_key, size_t volume_key_size, const char *device, Tpm2PCRValue *hash_pcrs, size_t n_hash_pcrs, const char *pubkey_path, uint32_t pubkey_pcr_mask, const char *signature_path, bool use_pin); +int enroll_tpm2(struct crypt_device *cd, const void *volume_key, size_t volume_key_size, const char *device, uint32_t seal_key_handle, Tpm2PCRValue *hash_pcrs, size_t n_hash_pcrs, const char *pubkey_path, uint32_t pubkey_pcr_mask, const char *signature_path, bool use_pin); #else -static inline int enroll_tpm2(struct crypt_device *cd, const void *volume_key, size_t volume_key_size, const char *device, Tpm2PCRValue *hash_pcrs, size_t n_hash_pcrs, const char *pubkey_path, uint32_t pubkey_pcr_mask, const char *signature_path, bool use_pin) { +static inline int enroll_tpm2(struct crypt_device *cd, const void *volume_key, size_t volume_key_size, const char *device, uint32_t seal_key_handle, Tpm2PCRValue *hash_pcrs, size_t n_hash_pcrs, const char *pubkey_path, uint32_t pubkey_pcr_mask, const char *signature_path, bool use_pin) { return log_debug_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "TPM2 key enrollment not supported."); } diff --git a/src/cryptenroll/cryptenroll.c b/src/cryptenroll/cryptenroll.c index 45a058f42660c..6c90c7d843fd7 100644 --- a/src/cryptenroll/cryptenroll.c +++ b/src/cryptenroll/cryptenroll.c @@ -36,6 +36,7 @@ static char *arg_unlock_fido2_device = NULL; static char *arg_pkcs11_token_uri = NULL; static char *arg_fido2_device = NULL; static char *arg_tpm2_device = NULL; +static uint32_t arg_tpm2_seal_key_handle = 0; static Tpm2PCRValue *arg_tpm2_hash_pcr_values = NULL; static size_t arg_tpm2_n_hash_pcr_values = 0; static bool arg_tpm2_hash_pcr_values_use_default = true; @@ -127,6 +128,8 @@ static int help(void) { " Whether to require user verification to unlock the volume\n" " --tpm2-device=PATH\n" " Enroll a TPM2 device\n" + " --tpm2-seal-key-handle=HANDLE\n" + " Specify handle of key to use for sealing\n" " --tpm2-pcrs=PCR1+PCR2+PCR3+…\n" " Specify TPM2 PCRs to seal against\n" " --tpm2-public-key=PATH\n" @@ -159,6 +162,7 @@ static int parse_argv(int argc, char *argv[]) { ARG_PKCS11_TOKEN_URI, ARG_FIDO2_DEVICE, ARG_TPM2_DEVICE, + ARG_TPM2_SEAL_KEY_HANDLE, ARG_TPM2_PCRS, ARG_TPM2_PUBLIC_KEY, ARG_TPM2_PUBLIC_KEY_PCRS, @@ -185,6 +189,7 @@ static int parse_argv(int argc, char *argv[]) { { "fido2-with-user-presence", required_argument, NULL, ARG_FIDO2_WITH_UP }, { "fido2-with-user-verification", required_argument, NULL, ARG_FIDO2_WITH_UV }, { "tpm2-device", required_argument, NULL, ARG_TPM2_DEVICE }, + { "tpm2-seal-key-handle", required_argument, NULL, ARG_TPM2_SEAL_KEY_HANDLE }, { "tpm2-pcrs", required_argument, NULL, ARG_TPM2_PCRS }, { "tpm2-public-key", required_argument, NULL, ARG_TPM2_PUBLIC_KEY }, { "tpm2-public-key-pcrs", required_argument, NULL, ARG_TPM2_PUBLIC_KEY_PCRS }, @@ -357,6 +362,13 @@ static int parse_argv(int argc, char *argv[]) { break; } + case ARG_TPM2_SEAL_KEY_HANDLE: + r = safe_atou32_full(optarg, 16, &arg_tpm2_seal_key_handle); + if (r < 0) + return log_error_errno(r, "Could not parse TPM2 seal key handle index '%s': %m", optarg); + + break; + case ARG_TPM2_PCRS: arg_tpm2_hash_pcr_values_use_default = false; r = tpm2_parse_pcr_argument_append(optarg, &arg_tpm2_hash_pcr_values, &arg_tpm2_n_hash_pcr_values); @@ -664,7 +676,7 @@ static int run(int argc, char *argv[]) { break; case ENROLL_TPM2: - slot = enroll_tpm2(cd, vk, vks, arg_tpm2_device, arg_tpm2_hash_pcr_values, arg_tpm2_n_hash_pcr_values, arg_tpm2_public_key, arg_tpm2_public_key_pcr_mask, arg_tpm2_signature, arg_tpm2_pin); + slot = enroll_tpm2(cd, vk, vks, arg_tpm2_device, arg_tpm2_seal_key_handle, arg_tpm2_hash_pcr_values, arg_tpm2_n_hash_pcr_values, arg_tpm2_public_key, arg_tpm2_public_key_pcr_mask, arg_tpm2_signature, arg_tpm2_pin); break; case _ENROLL_TYPE_INVALID: diff --git a/src/partition/repart.c b/src/partition/repart.c index c2eec29ca2c1c..91cb87a7b9b81 100644 --- a/src/partition/repart.c +++ b/src/partition/repart.c @@ -3830,6 +3830,7 @@ static int partition_encrypt(Context *context, Partition *p, PartitionTarget *ta return log_error_errno(r, "Could not calculate sealing policy digest: %m"); r = tpm2_seal(tpm2_context, + /* seal_key_handle= */ 0, &policy, /* pin= */ NULL, &secret, &secret_size, diff --git a/src/shared/creds-util.c b/src/shared/creds-util.c index 71aff5ef29383..9791d27b02ef1 100644 --- a/src/shared/creds-util.c +++ b/src/shared/creds-util.c @@ -861,6 +861,7 @@ int encrypt_credential_and_warn( return log_error_errno(r, "Could not calculate sealing policy digest: %m"); r = tpm2_seal(tpm2_context, + /* seal_key_handle= */ 0, &tpm2_policy, /* pin= */ NULL, &tpm2_key, &tpm2_key_size, diff --git a/src/shared/tpm2-util.c b/src/shared/tpm2-util.c index 2bab2d5f9e8e8..8189e9d8c0dcc 100644 --- a/src/shared/tpm2-util.c +++ b/src/shared/tpm2-util.c @@ -699,12 +699,22 @@ static void tpm2_handle_cleanup(ESYS_CONTEXT *esys_context, ESYS_TR esys_handle, if (flush) rc = sym_Esys_FlushContext(esys_context, esys_handle); else - rc = sym_Esys_TR_Close(esys_context, &esys_handle); - if (rc != TSS2_RC_SUCCESS) /* We ignore failures here (besides debug logging), since this is called - * in error paths, where we cannot do anything about failures anymore. And - * when it is called in successful codepaths by this time we already did - * what we wanted to do, and got the results we wanted so there's no - * reason to make this fail more loudly than necessary. */ + /* We can't use Esys_TR_Close() because the tpm2-tss library does not use reference counting + * for handles, and a single Esys_TR_Close() will remove the handle (internal to the tpm2-tss + * library) that might be in use by other code that is using the same ESYS_CONTEXT. This + * directly affects us; for example the src/test/test-tpm2.c test function + * check_seal_unseal() will encounter this issue and will result in a failure when trying to + * cleanup (i.e. Esys_FlushContext) the transient primary key that the test function + * generates. However, not calling Esys_TR_Close() here should be ok, since any leaked handle + * references will be cleaned up when we free our ESYS_CONTEXT. + * + * An upstream bug is open here: https://github.com/tpm2-software/tpm2-tss/issues/2693 */ + rc = TSS2_RC_SUCCESS; // FIXME: restore sym_Esys_TR_Close() use once tpm2-tss is fixed and adopted widely enough + if (rc != TSS2_RC_SUCCESS) + /* We ignore failures here (besides debug logging), since this is called in error paths, + * where we cannot do anything about failures anymore. And when it is called in successful + * codepaths by this time we already did what we wanted to do, and got the results we wanted + * so there's no reason to make this fail more loudly than necessary. */ log_debug("Failed to %s TPM handle, ignoring: %s", flush ? "flush" : "close", sym_Tss2_RC_Decode(rc)); } @@ -758,20 +768,23 @@ int tpm2_index_to_handle( assert(c); - /* Let's restrict this, at least for now, to allow only some handle types. */ + /* Only allow persistent, transient, or NV index handle types. */ switch (TPM2_HANDLE_TYPE(index)) { case TPM2_HT_PERSISTENT: case TPM2_HT_NV_INDEX: case TPM2_HT_TRANSIENT: break; case TPM2_HT_PCR: + /* PCR handles are referenced by their actual index number and do not need a Tpm2Handle */ return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid handle 0x%08" PRIx32 " (in PCR range).", index); case TPM2_HT_HMAC_SESSION: case TPM2_HT_POLICY_SESSION: + /* Session indexes are only used internally by tpm2-tss (or lower code) */ return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid handle 0x%08" PRIx32 " (in session range).", index); - case TPM2_HT_PERMANENT: /* Permanent handles are defined, e.g. ESYS_TR_RH_OWNER. */ + case TPM2_HT_PERMANENT: + /* Permanent handles are defined, e.g. ESYS_TR_RH_OWNER. */ return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid handle 0x%08" PRIx32 " (in permanent range).", index); default: @@ -779,20 +792,26 @@ int tpm2_index_to_handle( "Invalid handle 0x%08" PRIx32 " (in unknown range).", index); } - r = tpm2_get_capability_handle(c, index); - if (r < 0) - return r; - if (r == 0) { - log_debug("TPM handle 0x%08" PRIx32 " not populated.", index); - if (ret_public) - *ret_public = NULL; - if (ret_name) - *ret_name = NULL; - if (ret_qname) - *ret_qname = NULL; - if (ret_handle) - *ret_handle = NULL; - return 0; + /* For transient handles, the kernel tpm "resource manager" (i.e. /dev/tpmrm0) never acknowleges that + * any transient handles exist, even if they actually do. So a failure to find the requested handle + * index, if it's a transient handle, may not actually mean it's not present in the tpm; thus, only + * check GetCapability() if the handle isn't transient. */ + if (TPM2_HANDLE_TYPE(index) != TPM2_HT_TRANSIENT) { // FIXME: once kernel tpmrm is fixed to acknowledge transient handles, check transient handles too + r = tpm2_get_capability_handle(c, index); + if (r < 0) + return r; + if (r == 0) { + log_debug("TPM handle 0x%08" PRIx32 " not populated.", index); + if (ret_public) + *ret_public = NULL; + if (ret_name) + *ret_name = NULL; + if (ret_qname) + *ret_qname = NULL; + if (ret_handle) + *ret_handle = NULL; + return 0; + } } _cleanup_(tpm2_handle_freep) Tpm2Handle *handle = NULL; @@ -1080,7 +1099,7 @@ static int tpm2_get_legacy_template(TPMI_ALG_PUBLIC alg, TPMT_PUBLIC *ret_templa * * These templates are only needed to create a new persistent SRK (or a new transient key that is * SRK-compatible). Preferably, the TPM should contain a shared SRK located at the reserved shared SRK handle - * (see TPM2_SRK_HANDLE and tpm2_get_srk() below). + * (see TPM2_SRK_HANDLE in tpm2-util.h, and tpm2_get_srk() below). * * The alg must be TPM2_ALG_RSA or TPM2_ALG_ECC. Returns error if the requested template is not supported on * this TPM. Also see tpm2_get_best_srk_template() below. */ @@ -1179,14 +1198,6 @@ static int tpm2_get_best_srk_template(Tpm2Context *c, TPMT_PUBLIC *ret_template) "TPM does not support either SRK template L-1 (RSA) or L-2 (ECC)."); } -/* The SRK handle is defined in the Provisioning Guidance document (see above) in the table "Reserved Handles - * for TPM Provisioning Fundamental Elements". The SRK is useful because it is "shared", meaning it has no - * authValue nor authPolicy set, and thus may be used by anyone on the system to generate derived keys or - * seal secrets. This is useful if the TPM has an auth (password) set for the 'owner hierarchy', which would - * prevent users from generating primary transient keys, unless they knew the owner hierarchy auth. See - * the Provisioning Guidance document for more details. */ -#define TPM2_SRK_HANDLE UINT32_C(0x81000001) - /* Get the SRK. Returns 1 if SRK is found, 0 if there is no SRK, or < 0 on error. Also see * tpm2_get_or_create_srk() below. */ static int tpm2_get_srk( @@ -3931,6 +3942,7 @@ static int tpm2_deserialize( } int tpm2_seal(Tpm2Context *c, + uint32_t seal_key_handle, const TPM2B_DIGEST *policy, const char *pin, void **ret_secret, @@ -4004,18 +4016,42 @@ int tpm2_seal(Tpm2Context *c, _cleanup_(tpm2_handle_freep) Tpm2Handle *primary_handle = NULL; if (ret_srk_buf) { _cleanup_(Esys_Freep) TPM2B_PUBLIC *primary_public = NULL; - r = tpm2_get_or_create_srk( - c, - /* session= */ NULL, - &primary_public, - /* ret_name= */ NULL, - /* ret_qname= */ NULL, - &primary_handle); - if (r < 0) - return r; + + if (IN_SET(seal_key_handle, 0, TPM2_SRK_HANDLE)) { + r = tpm2_get_or_create_srk( + c, + /* session= */ NULL, + &primary_public, + /* ret_name= */ NULL, + /* ret_qname= */ NULL, + &primary_handle); + if (r < 0) + return r; + } else if (IN_SET(TPM2_HANDLE_TYPE(seal_key_handle), TPM2_HT_TRANSIENT, TPM2_HT_PERSISTENT)) { + r = tpm2_index_to_handle( + c, + seal_key_handle, + /* session= */ NULL, + &primary_public, + /* ret_name= */ NULL, + /* ret_qname= */ NULL, + &primary_handle); + if (r < 0) + return r; + if (r == 0) + /* We do NOT automatically create anything other than the SRK */ + return log_debug_errno(SYNTHETIC_ERRNO(ENOENT), + "No handle found at index 0x%" PRIx32, seal_key_handle); + } else + return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), + "Seal key handle 0x%" PRIx32 " is neither transient nor persistent.", + seal_key_handle); primary_alg = primary_public->publicArea.type; } else { + if (seal_key_handle != 0) + log_debug("Using primary alg sealing, but seal key handle also provided; ignoring seal key handle."); + /* TODO: force all callers to provide ret_srk_buf, so we can stop sealing with the legacy templates. */ primary_alg = TPM2_ALG_ECC; diff --git a/src/shared/tpm2-util.h b/src/shared/tpm2-util.h index 3fd2a13f920e6..f593915449ade 100644 --- a/src/shared/tpm2-util.h +++ b/src/shared/tpm2-util.h @@ -20,6 +20,14 @@ typedef enum TPM2Flags { #define TPM2_PCRS_MAX 24U #define TPM2_PCRS_MASK ((UINT32_C(1) << TPM2_PCRS_MAX) - 1) +/* The SRK handle is defined in the Provisioning Guidance document (see above) in the table "Reserved Handles + * for TPM Provisioning Fundamental Elements". The SRK is useful because it is "shared", meaning it has no + * authValue nor authPolicy set, and thus may be used by anyone on the system to generate derived keys or + * seal secrets. This is useful if the TPM has an auth (password) set for the 'owner hierarchy', which would + * prevent users from generating primary transient keys, unless they knew the owner hierarchy auth. See + * the Provisioning Guidance document for more details. */ +#define TPM2_SRK_HANDLE UINT32_C(0x81000001) + static inline bool TPM2_PCR_INDEX_VALID(unsigned pcr) { return pcr < TPM2_PCRS_MAX; } @@ -195,7 +203,7 @@ int tpm2_unmarshal_blob(const void *blob, size_t blob_size, TPM2B_PUBLIC *ret_pu 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); -int tpm2_seal(Tpm2Context *c, const TPM2B_DIGEST *policy, const char *pin, void **ret_secret, size_t *ret_secret_size, void **ret_blob, size_t *ret_blob_size, uint16_t *ret_primary_alg, void **ret_srk_buf, size_t *ret_srk_buf_size); +int tpm2_seal(Tpm2Context *c, uint32_t seal_key_handle, const TPM2B_DIGEST *policy, const char *pin, void **ret_secret, size_t *ret_secret_size, void **ret_blob, size_t *ret_blob_size, uint16_t *ret_primary_alg, void **ret_srk_buf, size_t *ret_srk_buf_size); int tpm2_unseal(Tpm2Context *c, uint32_t hash_pcr_mask, uint16_t pcr_bank, const void *pubkey, size_t pubkey_size, uint32_t pubkey_pcr_mask, JsonVariant *signature, const char *pin, uint16_t primary_alg, const void *blob, size_t blob_size, const void *policy_hash, size_t policy_hash_size, const void *srk_buf, size_t srk_buf_size, void **ret_secret, size_t *ret_secret_size); #if HAVE_OPENSSL diff --git a/src/test/test-tpm2.c b/src/test/test-tpm2.c index 7195bd613bc50..bbf2790a475bf 100644 --- a/src/test/test-tpm2.c +++ b/src/test/test-tpm2.c @@ -1,6 +1,7 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #include "hexdecoct.h" +#include "macro.h" #include "tpm2-util.h" #include "tests.h" @@ -702,21 +703,44 @@ TEST(parse_pcr_argument) { check_parse_pcr_argument_to_mask("debug+24", -EINVAL); } -static void tpm2b_public_rsa_init(TPM2B_PUBLIC *public, const char *rsa_n) { - TPMT_PUBLIC tpmt = { - .type = TPM2_ALG_RSA, - .nameAlg = TPM2_ALG_SHA256, - .objectAttributes = TPMA_OBJECT_RESTRICTED|TPMA_OBJECT_DECRYPT|TPMA_OBJECT_FIXEDTPM|TPMA_OBJECT_FIXEDPARENT|TPMA_OBJECT_SENSITIVEDATAORIGIN|TPMA_OBJECT_USERWITHAUTH, - .parameters.rsaDetail = { - .symmetric = { - .algorithm = TPM2_ALG_AES, - .keyBits.aes = 128, - .mode.aes = TPM2_ALG_CFB, - }, - .scheme.scheme = TPM2_ALG_NULL, - .keyBits = 2048, +static const TPMT_PUBLIC test_rsa_template = { + .type = TPM2_ALG_RSA, + .nameAlg = TPM2_ALG_SHA256, + .objectAttributes = TPMA_OBJECT_RESTRICTED|TPMA_OBJECT_DECRYPT|TPMA_OBJECT_FIXEDTPM|TPMA_OBJECT_FIXEDPARENT|TPMA_OBJECT_SENSITIVEDATAORIGIN|TPMA_OBJECT_USERWITHAUTH, + .parameters.rsaDetail = { + .symmetric = { + .algorithm = TPM2_ALG_AES, + .keyBits.aes = 128, + .mode.aes = TPM2_ALG_CFB, }, - }; + .scheme.scheme = TPM2_ALG_NULL, + .keyBits = 2048, + }, +}; + +static const TPMT_PUBLIC test_ecc_template = { + .type = TPM2_ALG_ECC, + .nameAlg = TPM2_ALG_SHA256, + .objectAttributes = TPMA_OBJECT_RESTRICTED|TPMA_OBJECT_DECRYPT|TPMA_OBJECT_FIXEDTPM|TPMA_OBJECT_FIXEDPARENT|TPMA_OBJECT_SENSITIVEDATAORIGIN|TPMA_OBJECT_USERWITHAUTH, + .parameters.eccDetail = { + .symmetric = { + .algorithm = TPM2_ALG_AES, + .keyBits.aes = 128, + .mode.aes = TPM2_ALG_CFB, + }, + .scheme.scheme = TPM2_ALG_NULL, + .curveID = TPM2_ECC_NIST_P256, + .kdf.scheme = TPM2_ALG_NULL, + }, +}; + +static const TPMT_PUBLIC *test_templates[] = { + &test_rsa_template, + &test_ecc_template, +}; + +static void tpm2b_public_rsa_init(TPM2B_PUBLIC *public, const char *rsa_n) { + TPMT_PUBLIC tpmt = test_rsa_template; DEFINE_HEX_PTR(key, rsa_n); tpmt.unique.rsa = TPM2B_PUBLIC_KEY_RSA_MAKE(key, key_len); @@ -726,21 +750,8 @@ static void tpm2b_public_rsa_init(TPM2B_PUBLIC *public, const char *rsa_n) { } static void tpm2b_public_ecc_init(TPM2B_PUBLIC *public, TPMI_ECC_CURVE curve, const char *x, const char *y) { - TPMT_PUBLIC tpmt = { - .type = TPM2_ALG_ECC, - .nameAlg = TPM2_ALG_SHA256, - .objectAttributes = TPMA_OBJECT_RESTRICTED|TPMA_OBJECT_DECRYPT|TPMA_OBJECT_FIXEDTPM|TPMA_OBJECT_FIXEDPARENT|TPMA_OBJECT_SENSITIVEDATAORIGIN|TPMA_OBJECT_USERWITHAUTH, - .parameters.eccDetail = { - .symmetric = { - .algorithm = TPM2_ALG_AES, - .keyBits.aes = 128, - .mode.aes = TPM2_ALG_CFB, - }, - .scheme.scheme = TPM2_ALG_NULL, - .curveID = curve, - .kdf.scheme = TPM2_ALG_NULL, - }, - }; + TPMT_PUBLIC tpmt = test_ecc_template; + tpmt.parameters.eccDetail.curveID = curve; DEFINE_HEX_PTR(buf_x, x); tpmt.unique.ecc.x = TPM2B_ECC_PARAMETER_MAKE(buf_x, buf_x_len); @@ -950,15 +961,8 @@ TEST(calculate_policy_pcr) { assert_se(digest_check(&d, "7481fd1b116078eb3ac2456e4ad542c9b46b9b8eb891335771ca8e7c8f8e4415")); } -TEST(tpm_required_tests) { - int r; - - _cleanup_(tpm2_context_unrefp) Tpm2Context *c = NULL; - r = tpm2_context_new(NULL, &c); - if (r < 0) { - log_tests_skipped("Could not find TPM"); - return; - } +static void check_test_parms(Tpm2Context *c) { + assert(c); TPMU_PUBLIC_PARMS parms = { .symDetail.sym = { @@ -977,6 +981,10 @@ TEST(tpm_required_tests) { /* Test with valid parms */ assert_se(tpm2_test_parms(c, TPM2_ALG_SYMCIPHER, &parms)); +} + +static void check_supports_alg(Tpm2Context *c) { + assert(c); /* Test invalid algs */ assert_se(!tpm2_supports_alg(c, TPM2_ALG_ERROR)); @@ -986,6 +994,10 @@ TEST(tpm_required_tests) { assert_se(tpm2_supports_alg(c, TPM2_ALG_RSA)); assert_se(tpm2_supports_alg(c, TPM2_ALG_AES)); assert_se(tpm2_supports_alg(c, TPM2_ALG_CFB)); +} + +static void check_supports_command(Tpm2Context *c) { + assert(c); /* Test invalid commands. TPM specification Part 2 ("Structures") section "TPM_CC (Command Codes)" * states bits 31:30 and 28:16 are reserved and must be 0. */ @@ -1004,6 +1016,91 @@ TEST(tpm_required_tests) { assert_se(tpm2_supports_command(c, TPM2_CC_Unseal)); } +static void check_seal_unseal_for_handle(Tpm2Context *c, TPM2_HANDLE handle) { + TPM2B_DIGEST policy = TPM2B_DIGEST_MAKE(NULL, TPM2_SHA256_DIGEST_SIZE); + + assert(c); + + log_debug("Check seal/unseal for handle 0x%" PRIx32, handle); + + _cleanup_free_ void *secret = NULL, *blob = NULL, *srk = NULL, *unsealed_secret = NULL; + size_t secret_size, blob_size, srk_size, unsealed_secret_size; + assert_se(tpm2_seal( + c, + handle, + &policy, + /* pin= */ NULL, + &secret, &secret_size, + &blob, &blob_size, + /* ret_primary_alg= */ NULL, + &srk, &srk_size) >= 0); + + assert_se(tpm2_unseal( + c, + /* hash_pcr_mask= */ 0, + /* pcr_bank= */ 0, + /* pubkey= */ NULL, /* pubkey_size= */ 0, + /* pubkey_pcr_mask= */ 0, + /* signature= */ NULL, + /* pin= */ NULL, + /* primary_alg= */ 0, + blob, blob_size, + /* policy_hash= */ NULL, /* policy_hash_size= */ 0, + srk, srk_size, + &unsealed_secret, &unsealed_secret_size) >= 0); + + assert_se(memcmp_nn(secret, secret_size, unsealed_secret, unsealed_secret_size) == 0); +} + +static void check_seal_unseal(Tpm2Context *c) { + int r; + + assert(c); + + check_seal_unseal_for_handle(c, 0); + check_seal_unseal_for_handle(c, TPM2_SRK_HANDLE); + + FOREACH_ARRAY(template, test_templates, ELEMENTSOF(test_templates)) { + TPM2B_PUBLIC public = { + .publicArea = **template, + .size = sizeof(**template), + }; + _cleanup_(tpm2_handle_freep) Tpm2Handle *transient_handle = NULL; + assert_se(tpm2_create_primary( + c, + /* session= */ NULL, + &public, + /* sensitive= */ NULL, + /* ret_public= */ NULL, + &transient_handle) >= 0); + + TPMI_DH_PERSISTENT transient_handle_index; + r = tpm2_index_from_handle(c, transient_handle, &transient_handle_index); + if (r == -EOPNOTSUPP) { + /* libesys too old */ + log_tests_skipped("libesys too old for tpm2_index_from_handle"); + return; + } + assert_se(r >= 0); + + check_seal_unseal_for_handle(c, transient_handle_index); + } +} + +TEST_RET(tests_which_require_tpm) { + _cleanup_(tpm2_context_unrefp) Tpm2Context *c = NULL; + + if (tpm2_context_new(NULL, &c) < 0) + return log_tests_skipped("Could not find TPM"); + + check_test_parms(c); + check_supports_alg(c); + check_supports_command(c); + check_seal_unseal(c); + + return 0; +} + #endif /* HAVE_TPM2 */ DEFINE_TEST_MAIN(LOG_DEBUG); diff --git a/test/TEST-70-TPM2/test.sh b/test/TEST-70-TPM2/test.sh index ec1daf9afce82..3b3cce915402f 100755 --- a/test/TEST-70-TPM2/test.sh +++ b/test/TEST-70-TPM2/test.sh @@ -11,7 +11,7 @@ TEST_REQUIRE_INSTALL_TESTS=0 # shellcheck source=test/test-functions . "${TEST_BASE_DIR:?}/test-functions" -test_require_bin swtpm tpm2_pcrextend tpm2_dictionarylockout +test_require_bin openssl swtpm tpm2_createprimary tpm2_dictionarylockout tpm2_evictcontrol tpm2_flushcontext tpm2_pcrextend tpm2_pcrread test_append_files() { local workspace="${1:?}" @@ -24,10 +24,13 @@ test_append_files() { fi install_dmevent generate_module_dependencies + inst_binary openssl + inst_binary tpm2_createprimary inst_binary tpm2_dictionarylockout + inst_binary tpm2_evictcontrol + inst_binary tpm2_flushcontext inst_binary tpm2_pcrextend inst_binary tpm2_pcrread - inst_binary openssl } do_test "$@" diff --git a/test/units/testsuite-70.sh b/test/units/testsuite-70.sh index 9aba54a48f23c..d39624ef488f4 100755 --- a/test/units/testsuite-70.sh +++ b/test/units/testsuite-70.sh @@ -8,6 +8,15 @@ SD_PCREXTEND="/usr/lib/systemd/systemd-pcrextend" SD_TPM2SETUP="/usr/lib/systemd/systemd-tpm2-setup" export SYSTEMD_LOG_LEVEL=debug +trap cleanup ERR +cleanup() { + # Evict the TPM primary key that we persisted + if [[ -n $persistent ]]; then + tpm2_evictcontrol -c "$persistent" + fi +} +persistent="" + cryptsetup_has_token_plugin_support() { local plugin_path @@ -131,6 +140,57 @@ if tpm_has_pcr sha256 12; then rm -f /tmp/pcr.dat fi +# Use default (0) seal key handle +systemd-cryptenroll --wipe-slot=tpm2 "$img" +PASSWORD=passphrase systemd-cryptenroll --tpm2-device=auto --tpm2-seal-key-handle=0 "$img" +systemd-cryptsetup attach test-volume "$img" - tpm2-device=auto,headless=1 +systemd-cryptsetup detach test-volume + +systemd-cryptenroll --wipe-slot=tpm2 "$img" +PASSWORD=passphrase systemd-cryptenroll --tpm2-device=auto --tpm2-seal-key-handle=0x0 "$img" +systemd-cryptsetup attach test-volume "$img" - tpm2-device=auto,headless=1 +systemd-cryptsetup detach test-volume + +# Use SRK seal key handle +systemd-cryptenroll --wipe-slot=tpm2 "$img" +PASSWORD=passphrase systemd-cryptenroll --tpm2-device=auto --tpm2-seal-key-handle=81000001 "$img" +systemd-cryptsetup attach test-volume "$img" - tpm2-device=auto,headless=1 +systemd-cryptsetup detach test-volume + +systemd-cryptenroll --wipe-slot=tpm2 "$img" +PASSWORD=passphrase systemd-cryptenroll --tpm2-device=auto --tpm2-seal-key-handle=0x81000001 "$img" +systemd-cryptsetup attach test-volume "$img" - tpm2-device=auto,headless=1 +systemd-cryptsetup detach test-volume + +# Test invalid ranges: pcr, nv, session, permanent +systemd-cryptenroll --wipe-slot=tpm2 "$img" +(! PASSWORD=passphrase systemd-cryptenroll --tpm2-device=auto --tpm2-seal-key-handle=7 "$img") # PCR +(! PASSWORD=passphrase systemd-cryptenroll --tpm2-device=auto --tpm2-seal-key-handle=0x01000001 "$img") # NV index +(! PASSWORD=passphrase systemd-cryptenroll --tpm2-device=auto --tpm2-seal-key-handle=0x02000001 "$img") # HMAC/loaded session +(! PASSWORD=passphrase systemd-cryptenroll --tpm2-device=auto --tpm2-seal-key-handle=0x03000001 "$img") # Policy/saved session +(! PASSWORD=passphrase systemd-cryptenroll --tpm2-device=auto --tpm2-seal-key-handle=0x40000001 "$img") # Permanent + +# Use non-SRK persistent seal key handle (by creating/persisting new key) +primary=/tmp/primary.ctx +tpm2_createprimary -c "$primary" +persistent_line=$(tpm2_evictcontrol -c "$primary" | grep persistent-handle) +persistent="0x${persistent_line##*0x}" +tpm2_flushcontext -t + +systemd-cryptenroll --wipe-slot=tpm2 "$img" +PASSWORD=passphrase systemd-cryptenroll --tpm2-device=auto --tpm2-seal-key-handle="${persistent#0x}" "$img" +systemd-cryptsetup attach test-volume "$img" - tpm2-device=auto,headless=1 +systemd-cryptsetup detach test-volume + +systemd-cryptenroll --wipe-slot=tpm2 "$img" +PASSWORD=passphrase systemd-cryptenroll --tpm2-device=auto --tpm2-seal-key-handle="$persistent" "$img" +systemd-cryptsetup attach test-volume "$img" - tpm2-device=auto,headless=1 +systemd-cryptsetup detach test-volume + +tpm2_evictcontrol -c "$persistent" +persistent="" +rm -f "$primary" + rm -f "${img:?}" if [[ -x "$SD_MEASURE" ]]; then @@ -390,4 +450,6 @@ if [[ -x "$SD_TPM2SETUP" ]]; then "$SD_TPM2SETUP" --early=no fi +cleanup + touch /testok