Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Cryptenroll specify handle index #29427

Merged
merged 4 commits into from Oct 11, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
22 changes: 22 additions & 0 deletions man/systemd-cryptenroll.xml
Expand Up @@ -411,6 +411,28 @@
<xi:include href="version-info.xml" xpointer="v248"/></listitem>
</varlistentry>

<varlistentry>
<term><option>--tpm2-seal-key-handle=</option><replaceable>HANDLE</replaceable></term>

<listitem><para>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
<literal>0x</literal>. Allowable values are any handle index in the persistent
(<literal>0x81000000</literal>-<literal>0x81ffffff</literal>) or transient
(<literal>0x80000000</literal>-<literal>0x80ffffff</literal>) 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.</para>

<para>The default is the Storage Root Key (SRK) handle index <literal>0x81000001</literal>. 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.</para>

<para>This should not be changed unless you know what you are doing.</para>

<xi:include href="version-info.xml" xpointer="v255"/></listitem>
</varlistentry>

<varlistentry>
<term><option>--tpm2-pcrs=</option><arg rep="repeat">PCR</arg></term>

Expand Down
2 changes: 2 additions & 0 deletions src/cryptenroll/cryptenroll-tpm2.c
Expand Up @@ -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,
Expand Down Expand Up @@ -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,
Expand Down
4 changes: 2 additions & 2 deletions src/cryptenroll/cryptenroll-tpm2.h
Expand Up @@ -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.");
}
Expand Down
14 changes: 13 additions & 1 deletion src/cryptenroll/cryptenroll.c
Expand Up @@ -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;
Expand Down Expand Up @@ -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"
Expand Down Expand Up @@ -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,
Expand All @@ -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 },
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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:
Expand Down
1 change: 1 addition & 0 deletions src/partition/repart.c
Expand Up @@ -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,
Expand Down
1 change: 1 addition & 0 deletions src/shared/creds-util.c
Expand Up @@ -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,
Expand Down
116 changes: 76 additions & 40 deletions src/shared/tpm2-util.c
Expand Up @@ -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));
}

Expand Down Expand Up @@ -758,41 +768,50 @@ 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:
return log_debug_errno(SYNTHETIC_ERRNO(EINVAL),
"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;
Expand Down Expand Up @@ -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. */
Expand Down Expand Up @@ -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(
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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;

Expand Down