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