diff --git a/man/systemd-cryptenroll.xml b/man/systemd-cryptenroll.xml
index 3d0efc9bf7..ad32bf68f2 100644
--- a/man/systemd-cryptenroll.xml
+++ b/man/systemd-cryptenroll.xml
@@ -411,6 +411,28 @@
+
+ PATH
+
+ Enroll a TPM2 security chip using its public key. Expects a path referring to the
+ TPM2 public key in TPM2B_PUBLIC format. This cannot be used with , as
+ it performs the same operation, but without connecting to the TPM2 security chip; instead the
+ enrollment is calculated using the provided TPM2 key. This is useful in situations where the TPM2
+ security chip is not available at the time of enrollment.
+
+ The key, in most cases, should be the Storage Root Key (SRK) from the TPM2 security chip. If a
+ key from a different handle (not the SRK) is used, you must specify its handle index using
+ .
+
+ You may use tpm2-tss tools to get the SRK from the TPM2 security chip with tpm2_readpublic1,
+ for example:
+
+ tpm2_readpublic -c 0x81000001 -o srk.pub
+
+
+
+
HANDLE
diff --git a/src/cryptenroll/cryptenroll-tpm2.c b/src/cryptenroll/cryptenroll-tpm2.c
index 1273822d3a..4b9f6b2d07 100644
--- a/src/cryptenroll/cryptenroll-tpm2.c
+++ b/src/cryptenroll/cryptenroll-tpm2.c
@@ -134,6 +134,7 @@ int enroll_tpm2(struct crypt_device *cd,
size_t volume_key_size,
const char *device,
uint32_t seal_key_handle,
+ const char *device_key,
Tpm2PCRValue *hash_pcr_values,
size_t n_hash_pcr_values,
const char *pubkey_path,
@@ -208,25 +209,62 @@ int enroll_tpm2(struct crypt_device *cd,
return log_debug_errno(r, "Failed to read TPM PCR signature: %m");
}
+ bool any_pcr_value_specified = tpm2_pcr_values_has_any_values(hash_pcr_values, n_hash_pcr_values);
+
_cleanup_(tpm2_pcrlock_policy_done) Tpm2PCRLockPolicy pcrlock_policy = {};
if (pcrlock_path) {
r = tpm2_pcrlock_policy_load(pcrlock_path, &pcrlock_policy);
if (r < 0)
return r;
+ any_pcr_value_specified = true;
flags |= TPM2_FLAGS_USE_PCRLOCK;
}
_cleanup_(tpm2_context_unrefp) Tpm2Context *tpm2_context = NULL;
- r = tpm2_context_new(device, &tpm2_context);
- if (r < 0)
- return log_error_errno(r, "Failed to create TPM2 context: %m");
+ TPM2B_PUBLIC device_key_public = {};
+ if (device_key) {
+ _cleanup_free_ char *device_key_buffer = NULL;
+ size_t device_key_buffer_size;
+ r = read_full_file(device_key, &device_key_buffer, &device_key_buffer_size);
+ if (r < 0)
+ return log_error_errno(r, "Failed to read device key from file: %m");
- bool pcr_value_specified = tpm2_pcr_values_has_any_values(hash_pcr_values, n_hash_pcr_values);
+ r = dlopen_tpm2();
+ if (r < 0)
+ return log_debug_errno(r, "TPM2 support not installed: %m");
- r = tpm2_pcr_read_missing_values(tpm2_context, hash_pcr_values, n_hash_pcr_values);
- if (r < 0)
- return log_error_errno(r, "Could not read pcr values: %m");
+ TSS2_RC rc;
+ size_t offset = 0;
+ rc = sym_Tss2_MU_TPM2B_PUBLIC_Unmarshal(
+ (uint8_t*) device_key_buffer,
+ device_key_buffer_size,
+ &offset,
+ &device_key_public);
+ if (rc != TSS2_RC_SUCCESS)
+ return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
+ "Could not unmarshal public key from file.");
+
+ assert(offset <= device_key_buffer_size);
+ if (offset != device_key_buffer_size)
+ return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
+ "Found %zu bytes of trailing garbage in public key file.",
+ device_key_buffer_size - offset);
+
+ if (!tpm2_pcr_values_has_all_values(hash_pcr_values, n_hash_pcr_values))
+ return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
+ "Must provide all PCR values when using TPM2 device key.");
+ } else {
+ r = tpm2_context_new(device, &tpm2_context);
+ if (r < 0)
+ return log_error_errno(r, "Failed to create TPM2 context: %m");
+
+ if (!tpm2_pcr_values_has_all_values(hash_pcr_values, n_hash_pcr_values)) {
+ r = tpm2_pcr_read_missing_values(tpm2_context, hash_pcr_values, n_hash_pcr_values);
+ if (r < 0)
+ return log_error_errno(r, "Could not read pcr values: %m");
+ }
+ }
uint16_t hash_pcr_bank = 0;
uint32_t hash_pcr_mask = 0;
@@ -263,15 +301,26 @@ int enroll_tpm2(struct crypt_device *cd,
if (r < 0)
return r;
- r = tpm2_seal(tpm2_context,
- seal_key_handle,
- &policy,
- pin_str,
- &secret, &secret_size,
- &blob, &blob_size,
- /* ret_primary_alg= */ NULL,
- &srk_buf,
- &srk_buf_size);
+ if (device_key)
+ r = tpm2_calculate_seal(
+ seal_key_handle,
+ &device_key_public,
+ /* attributes= */ NULL,
+ /* secret= */ NULL, /* secret_size= */ 0,
+ &policy,
+ pin_str,
+ &secret, &secret_size,
+ &blob, &blob_size,
+ &srk_buf, &srk_buf_size);
+ else
+ r = tpm2_seal(tpm2_context,
+ seal_key_handle,
+ &policy,
+ pin_str,
+ &secret, &secret_size,
+ &blob, &blob_size,
+ /* ret_primary_alg= */ NULL,
+ &srk_buf, &srk_buf_size);
if (r < 0)
return log_error_errno(r, "Failed to seal to TPM2: %m");
@@ -286,8 +335,8 @@ int enroll_tpm2(struct crypt_device *cd,
return r; /* return existing keyslot, so that wiping won't kill it */
}
- /* Quick verification that everything is in order, we are not in a hurry after all. */
- if ((!pubkey || signature_json) && !pcr_value_specified) {
+ /* If possible, verify the sealed data object. */
+ if ((!pubkey || signature_json) && !any_pcr_value_specified && !device_key) {
_cleanup_(erase_and_freep) void *secret2 = NULL;
size_t secret2_size;
diff --git a/src/cryptenroll/cryptenroll-tpm2.h b/src/cryptenroll/cryptenroll-tpm2.h
index 6439a08df4..2fbcdd4b2f 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, 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, const char *pcrlock_path);
+int enroll_tpm2(struct crypt_device *cd, const void *volume_key, size_t volume_key_size, const char *device, uint32_t seal_key_handle, const char *device_key, Tpm2PCRValue *hash_pcrs, size_t n_hash_pcrs, const char *pubkey_path, uint32_t pubkey_pcr_mask, const char *signature_path, bool use_pin, const char *pcrlock_path);
#else
-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, const char *pcrlock_path) {
+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, const char *device_key, Tpm2PCRValue *hash_pcrs, size_t n_hash_pcrs, const char *pubkey_path, uint32_t pubkey_pcr_mask, const char *signature_path, bool use_pin, const char *pcrlock_path) {
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 20d6bc2680..d2915ad7ee 100644
--- a/src/cryptenroll/cryptenroll.c
+++ b/src/cryptenroll/cryptenroll.c
@@ -37,6 +37,7 @@ 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 char *arg_tpm2_device_key = NULL;
static Tpm2PCRValue *arg_tpm2_hash_pcr_values = NULL;
static size_t arg_tpm2_n_hash_pcr_values = 0;
static bool arg_tpm2_pin = false;
@@ -63,6 +64,7 @@ STATIC_DESTRUCTOR_REGISTER(arg_unlock_fido2_device, freep);
STATIC_DESTRUCTOR_REGISTER(arg_pkcs11_token_uri, freep);
STATIC_DESTRUCTOR_REGISTER(arg_fido2_device, freep);
STATIC_DESTRUCTOR_REGISTER(arg_tpm2_device, freep);
+STATIC_DESTRUCTOR_REGISTER(arg_tpm2_device_key, freep);
STATIC_DESTRUCTOR_REGISTER(arg_tpm2_hash_pcr_values, freep);
STATIC_DESTRUCTOR_REGISTER(arg_tpm2_public_key, freep);
STATIC_DESTRUCTOR_REGISTER(arg_tpm2_signature, freep);
@@ -137,6 +139,8 @@ static int help(void) {
" Enroll a TPM2 device\n"
" --tpm2-seal-key-handle=HANDLE\n"
" Specify handle of key to use for sealing\n"
+ " --tpm2-device-key=PATH\n"
+ " Enroll a TPM2 device using its public key\n"
" --tpm2-pcrs=PCR1+PCR2+PCR3+…\n"
" Specify TPM2 PCRs to seal against\n"
" --tpm2-public-key=PATH\n"
@@ -172,6 +176,7 @@ static int parse_argv(int argc, char *argv[]) {
ARG_FIDO2_DEVICE,
ARG_TPM2_DEVICE,
ARG_TPM2_SEAL_KEY_HANDLE,
+ ARG_TPM2_DEVICE_KEY,
ARG_TPM2_PCRS,
ARG_TPM2_PUBLIC_KEY,
ARG_TPM2_PUBLIC_KEY_PCRS,
@@ -200,6 +205,7 @@ static int parse_argv(int argc, char *argv[]) {
{ "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-device-key", required_argument, NULL, ARG_TPM2_DEVICE_KEY },
{ "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 },
@@ -381,6 +387,19 @@ static int parse_argv(int argc, char *argv[]) {
break;
+ case ARG_TPM2_DEVICE_KEY:
+ if (arg_enroll_type >= 0 || arg_tpm2_device_key)
+ return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
+ "Multiple operations specified at once, refusing.");
+
+
+ r = parse_path_argument(optarg, /* suppress_root= */ false, &arg_tpm2_device_key);
+ if (r < 0)
+ return r;
+
+ arg_enroll_type = ENROLL_TPM2;
+ break;
+
case ARG_TPM2_PCRS:
auto_hash_pcr_values = false;
r = tpm2_parse_pcr_argument_append(optarg, &arg_tpm2_hash_pcr_values, &arg_tpm2_n_hash_pcr_values);
@@ -715,7 +734,7 @@ static int run(int argc, char *argv[]) {
break;
case ENROLL_TPM2:
- 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, arg_tpm2_pcrlock);
+ slot = enroll_tpm2(cd, vk, vks, arg_tpm2_device, arg_tpm2_seal_key_handle, arg_tpm2_device_key, 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, arg_tpm2_pcrlock);
break;
case _ENROLL_TYPE_INVALID:
diff --git a/src/shared/tpm2-util.c b/src/shared/tpm2-util.c
index 2cf66351ae..f3712c8496 100644
--- a/src/shared/tpm2-util.c
+++ b/src/shared/tpm2-util.c
@@ -48,6 +48,7 @@ static TSS2_RC (*sym_Esys_FlushContext)(ESYS_CONTEXT *esysContext, ESYS_TR flush
static void (*sym_Esys_Free)(void *ptr) = NULL;
static TSS2_RC (*sym_Esys_GetCapability)(ESYS_CONTEXT *esysContext, ESYS_TR shandle1, ESYS_TR shandle2, ESYS_TR shandle3, TPM2_CAP capability, UINT32 property, UINT32 propertyCount, TPMI_YES_NO *moreData, TPMS_CAPABILITY_DATA **capabilityData) = NULL;
static TSS2_RC (*sym_Esys_GetRandom)(ESYS_CONTEXT *esysContext, ESYS_TR shandle1, ESYS_TR shandle2, ESYS_TR shandle3, UINT16 bytesRequested, TPM2B_DIGEST **randomBytes) = NULL;
+static TSS2_RC (*sym_Esys_Import)(ESYS_CONTEXT *esysContext, ESYS_TR parentHandle, ESYS_TR shandle1, ESYS_TR shandle2, ESYS_TR shandle3, const TPM2B_DATA *encryptionKey, const TPM2B_PUBLIC *objectPublic, const TPM2B_PRIVATE *duplicate, const TPM2B_ENCRYPTED_SECRET *inSymSeed, const TPMT_SYM_DEF_OBJECT *symmetricAlg, TPM2B_PRIVATE **outPrivate) = NULL;
static TSS2_RC (*sym_Esys_Initialize)(ESYS_CONTEXT **esys_context, TSS2_TCTI_CONTEXT *tcti, TSS2_ABI_VERSION *abiVersion) = NULL;
static TSS2_RC (*sym_Esys_Load)(ESYS_CONTEXT *esysContext, ESYS_TR parentHandle, ESYS_TR shandle1, ESYS_TR shandle2, ESYS_TR shandle3, const TPM2B_PRIVATE *inPrivate, const TPM2B_PUBLIC *inPublic, ESYS_TR *objectHandle) = NULL;
static TSS2_RC (*sym_Esys_LoadExternal)(ESYS_CONTEXT *esysContext, ESYS_TR shandle1, ESYS_TR shandle2, ESYS_TR shandle3, const TPM2B_SENSITIVE *inPrivate, const TPM2B_PUBLIC *inPublic, ESYS_TR hierarchy, ESYS_TR *objectHandle) = NULL;
@@ -79,16 +80,24 @@ static TSS2_RC (*sym_Esys_Unseal)(ESYS_CONTEXT *esysContext, ESYS_TR itemHandle,
static TSS2_RC (*sym_Esys_VerifySignature)(ESYS_CONTEXT *esysContext, ESYS_TR keyHandle, ESYS_TR shandle1, ESYS_TR shandle2, ESYS_TR shandle3, const TPM2B_DIGEST *digest, const TPMT_SIGNATURE *signature, TPMT_TK_VERIFIED **validation) = NULL;
static TSS2_RC (*sym_Tss2_MU_TPM2_CC_Marshal)(TPM2_CC src, uint8_t buffer[], size_t buffer_size, size_t *offset) = NULL;
+static TSS2_RC (*sym_Tss2_MU_TPM2_HANDLE_Marshal)(TPM2_HANDLE src, uint8_t buffer[], size_t buffer_size, size_t *offset) = NULL;
+static TSS2_RC (*sym_Tss2_MU_TPM2B_DIGEST_Marshal)(TPM2B_DIGEST const *src, uint8_t buffer[], size_t buffer_size, size_t *offset) = NULL;
+static TSS2_RC (*sym_Tss2_MU_TPM2B_ENCRYPTED_SECRET_Marshal)(TPM2B_ENCRYPTED_SECRET const *src, uint8_t buffer[], size_t buffer_size, size_t *offset) = NULL;
+static TSS2_RC (*sym_Tss2_MU_TPM2B_ENCRYPTED_SECRET_Unmarshal)(uint8_t const buffer[], size_t buffer_size, size_t *offset, TPM2B_ENCRYPTED_SECRET *dest) = NULL;
+static TSS2_RC (*sym_Tss2_MU_TPM2B_NAME_Marshal)(TPM2B_NAME const *src, uint8_t buffer[], size_t buffer_size, size_t *offset) = NULL;
static TSS2_RC (*sym_Tss2_MU_TPM2B_PRIVATE_Marshal)(TPM2B_PRIVATE const *src, uint8_t buffer[], size_t buffer_size, size_t *offset) = NULL;
static TSS2_RC (*sym_Tss2_MU_TPM2B_PRIVATE_Unmarshal)(uint8_t const buffer[], size_t buffer_size, size_t *offset, TPM2B_PRIVATE *dest) = NULL;
static TSS2_RC (*sym_Tss2_MU_TPM2B_PUBLIC_Marshal)(TPM2B_PUBLIC const *src, uint8_t buffer[], size_t buffer_size, size_t *offset) = NULL;
-static TSS2_RC (*sym_Tss2_MU_TPM2B_PUBLIC_Unmarshal)(uint8_t const buffer[], size_t buffer_size, size_t *offset, TPM2B_PUBLIC *dest) = NULL;
+TSS2_RC (*sym_Tss2_MU_TPM2B_PUBLIC_Unmarshal)(uint8_t const buffer[], size_t buffer_size, size_t *offset, TPM2B_PUBLIC *dest) = NULL;
+static TSS2_RC (*sym_Tss2_MU_TPM2B_SENSITIVE_Marshal)(TPM2B_SENSITIVE const *src, uint8_t buffer[], size_t buffer_size, size_t *offset) = NULL;
static TSS2_RC (*sym_Tss2_MU_TPML_PCR_SELECTION_Marshal)(TPML_PCR_SELECTION const *src, uint8_t buffer[], size_t buffer_size, size_t *offset) = NULL;
static TSS2_RC (*sym_Tss2_MU_TPMS_NV_PUBLIC_Marshal)(TPMS_NV_PUBLIC const *src, uint8_t buffer[], size_t buffer_size, size_t *offset) = NULL;
static TSS2_RC (*sym_Tss2_MU_TPM2B_NV_PUBLIC_Marshal)(TPM2B_NV_PUBLIC const *src, uint8_t buffer[], size_t buffer_size, size_t *offset) = NULL;
static TSS2_RC (*sym_Tss2_MU_TPM2B_NV_PUBLIC_Unmarshal)(uint8_t const buffer[], size_t buffer_size, size_t *offset, TPM2B_NV_PUBLIC *dest) = NULL;
+static TSS2_RC (*sym_Tss2_MU_TPMS_ECC_POINT_Marshal)(TPMS_ECC_POINT const *src, uint8_t buffer[], size_t buffer_size, size_t *offset) = NULL;
static TSS2_RC (*sym_Tss2_MU_TPMT_HA_Marshal)(TPMT_HA const *src, uint8_t buffer[], size_t buffer_size, size_t *offset) = NULL;
static TSS2_RC (*sym_Tss2_MU_TPMT_PUBLIC_Marshal)(TPMT_PUBLIC const *src, uint8_t buffer[], size_t buffer_size, size_t *offset) = NULL;
+static TSS2_RC (*sym_Tss2_MU_UINT32_Marshal)(UINT32 src, uint8_t buffer[], size_t buffer_size, size_t *offset) = NULL;
static const char* (*sym_Tss2_RC_Decode)(TSS2_RC rc) = NULL;
@@ -106,6 +115,7 @@ int dlopen_tpm2(void) {
DLSYM_ARG(Esys_Free),
DLSYM_ARG(Esys_GetCapability),
DLSYM_ARG(Esys_GetRandom),
+ DLSYM_ARG(Esys_Import),
DLSYM_ARG(Esys_Initialize),
DLSYM_ARG(Esys_Load),
DLSYM_ARG(Esys_LoadExternal),
@@ -152,16 +162,24 @@ int dlopen_tpm2(void) {
return dlopen_many_sym_or_warn(
&libtss2_mu_dl, "libtss2-mu.so.0", LOG_DEBUG,
DLSYM_ARG(Tss2_MU_TPM2_CC_Marshal),
+ DLSYM_ARG(Tss2_MU_TPM2_HANDLE_Marshal),
+ DLSYM_ARG(Tss2_MU_TPM2B_DIGEST_Marshal),
+ DLSYM_ARG(Tss2_MU_TPM2B_ENCRYPTED_SECRET_Marshal),
+ DLSYM_ARG(Tss2_MU_TPM2B_ENCRYPTED_SECRET_Unmarshal),
+ DLSYM_ARG(Tss2_MU_TPM2B_NAME_Marshal),
DLSYM_ARG(Tss2_MU_TPM2B_PRIVATE_Marshal),
DLSYM_ARG(Tss2_MU_TPM2B_PRIVATE_Unmarshal),
DLSYM_ARG(Tss2_MU_TPM2B_PUBLIC_Marshal),
DLSYM_ARG(Tss2_MU_TPM2B_PUBLIC_Unmarshal),
+ DLSYM_ARG(Tss2_MU_TPM2B_SENSITIVE_Marshal),
DLSYM_ARG(Tss2_MU_TPML_PCR_SELECTION_Marshal),
DLSYM_ARG(Tss2_MU_TPMS_NV_PUBLIC_Marshal),
DLSYM_ARG(Tss2_MU_TPM2B_NV_PUBLIC_Marshal),
DLSYM_ARG(Tss2_MU_TPM2B_NV_PUBLIC_Unmarshal),
+ 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_Marshal),
+ DLSYM_ARG(Tss2_MU_UINT32_Marshal));
}
void Esys_Freep(void *p) {
@@ -1122,9 +1140,8 @@ static int tpm2_get_legacy_template(TPMI_ALG_PUBLIC alg, TPMT_PUBLIC *ret_templa
* SRK-compatible). Preferably, the TPM should contain a shared SRK located at the reserved shared SRK handle
* (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. */
-static int tpm2_get_srk_template(Tpm2Context *c, TPMI_ALG_PUBLIC alg, TPMT_PUBLIC *ret_template) {
+ * Returns 0 if the specified algorithm is ECC or RSA, otherwise -EOPNOTSUPP. */
+int tpm2_get_srk_template(TPMI_ALG_PUBLIC alg, TPMT_PUBLIC *ret_template) {
/* The attributes are the same between ECC and RSA templates. This has the changes specified in the
* Provisioning Guidance document, specifically:
* TPMA_OBJECT_USERWITHAUTH is added.
@@ -1173,47 +1190,55 @@ static int tpm2_get_srk_template(Tpm2Context *c, TPMI_ALG_PUBLIC alg, TPMT_PUBLI
},
};
- assert(c);
assert(ret_template);
- if (alg == TPM2_ALG_ECC) {
- if (!tpm2_supports_alg(c, TPM2_ALG_ECC))
- return log_debug_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE),
- "TPM does not support ECC.");
-
- if (!tpm2_supports_ecc_curve(c, srk_ecc.parameters.eccDetail.curveID))
- return log_debug_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE),
- "TPM does not support ECC-NIST-P256 curve.");
-
- if (!tpm2_supports_tpmt_public(c, &srk_ecc))
- return log_debug_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE),
- "TPM does not support SRK ECC template L-2.");
-
+ switch (alg) {
+ case TPM2_ALG_ECC:
*ret_template = srk_ecc;
return 0;
- }
-
- if (alg == TPM2_ALG_RSA) {
- if (!tpm2_supports_alg(c, TPM2_ALG_RSA))
- return log_debug_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE),
- "TPM does not support RSA.");
-
- if (!tpm2_supports_tpmt_public(c, &srk_rsa))
- return log_debug_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE),
- "TPM does not support SRK RSA template L-1.");
-
+ case TPM2_ALG_RSA:
*ret_template = srk_rsa;
return 0;
}
- return log_debug_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "Unsupported SRK alg: 0x%x.", alg);
+ return log_debug_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "No SRK for algorithm 0x%" PRIx16, alg);
}
/* Get the best supported SRK template. ECC is preferred, then RSA. */
-static int tpm2_get_best_srk_template(Tpm2Context *c, TPMT_PUBLIC *ret_template) {
- if (tpm2_get_srk_template(c, TPM2_ALG_ECC, ret_template) >= 0 ||
- tpm2_get_srk_template(c, TPM2_ALG_RSA, ret_template) >= 0)
+int tpm2_get_best_srk_template(Tpm2Context *c, TPMT_PUBLIC *ret_template) {
+ TPMT_PUBLIC template;
+ int r;
+
+ assert(c);
+ assert(ret_template);
+
+ r = tpm2_get_srk_template(TPM2_ALG_ECC, &template);
+ if (r < 0)
+ return r;
+
+ if (!tpm2_supports_alg(c, TPM2_ALG_ECC))
+ log_debug("TPM does not support ECC.");
+ else if (!tpm2_supports_ecc_curve(c, template.parameters.eccDetail.curveID))
+ log_debug("TPM does not support ECC-NIST-P256 curve.");
+ else if (!tpm2_supports_tpmt_public(c, &template))
+ log_debug("TPM does not support SRK ECC template L-2.");
+ else {
+ *ret_template = template;
return 0;
+ }
+
+ r = tpm2_get_srk_template(TPM2_ALG_RSA, &template);
+ if (r < 0)
+ return r;
+
+ if (!tpm2_supports_alg(c, TPM2_ALG_RSA))
+ log_debug("TPM does not support RSA.");
+ else if (!tpm2_supports_tpmt_public(c, &template))
+ log_debug("TPM does not support SRK RSA template L-1.");
+ else {
+ *ret_template = template;
+ return 0;
+ }
return log_debug_errno(SYNTHETIC_ERRNO(EOPNOTSUPP),
"TPM does not support either SRK template L-1 (RSA) or L-2 (ECC).");
@@ -1221,7 +1246,7 @@ static int tpm2_get_best_srk_template(Tpm2Context *c, TPMT_PUBLIC *ret_template)
/* 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(
+int tpm2_get_srk(
Tpm2Context *c,
const Tpm2Handle *session,
TPM2B_PUBLIC **ret_public,
@@ -2474,6 +2499,48 @@ int tpm2_unmarshal_nv_public(const void *data, size_t size, TPM2B_NV_PUBLIC *ret
return 0;
}
+static int tpm2_import(
+ Tpm2Context *c,
+ const Tpm2Handle *parent,
+ const Tpm2Handle *session,
+ const TPM2B_PUBLIC *public,
+ const TPM2B_PRIVATE *private,
+ const TPM2B_ENCRYPTED_SECRET *seed,
+ const TPM2B_DATA *encryption_key,
+ const TPMT_SYM_DEF_OBJECT *symmetric,
+ TPM2B_PRIVATE **ret_private) {
+
+ TSS2_RC rc;
+
+ assert(c);
+ assert(parent);
+ assert(!!encryption_key == !!symmetric);
+ assert(public);
+ assert(private);
+ assert(seed);
+ assert(ret_private);
+
+ log_debug("Importing key into TPM.");
+
+ rc = sym_Esys_Import(
+ c->esys_context,
+ parent->esys_handle,
+ session ? session->esys_handle : ESYS_TR_PASSWORD,
+ ESYS_TR_NONE,
+ ESYS_TR_NONE,
+ encryption_key,
+ public,
+ private,
+ seed,
+ symmetric ?: &(TPMT_SYM_DEF_OBJECT){ .algorithm = TPM2_ALG_NULL, },
+ ret_private);
+ if (rc != TSS2_RC_SUCCESS)
+ return log_debug_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE),
+ "Failed to import key into TPM: %s", sym_Tss2_RC_Decode(rc));
+
+ return 0;
+}
+
/* Read hash values from the specified PCR selection. Provides a Tpm2PCRValue array that contains all
* requested PCR values, in the order provided by the TPM. Normally, the provided pcr values will match
* exactly what is in the provided selection, but the TPM may ignore some selected PCRs (for example, if an
@@ -3312,7 +3379,7 @@ int tpm2_calculate_pubkey_name(const TPMT_PUBLIC *public, TPM2B_NAME *ret_name)
/* Get the "name" of a key from the TPM.
*
- * The "name" of a key is explained above in tpm2_calculate_name().
+ * The "name" of a key is explained above in tpm2_calculate_pubkey_name().
*
* The handle must reference a key already present in the TPM. It may be either a public key only, or a
* public/private keypair. */
@@ -4264,12 +4331,14 @@ int tpm2_tpm2b_public_from_pem(const void *pem, size_t pem_size, TPM2B_PUBLIC *r
#endif
}
-/* Marshal the public and private objects into a single nonstandard 'blob'. This is not a (publicly) standard
- * format, this is specific to how we currently store the sealed object. This 'blob' can be unmarshalled by
+/* Marshal the public, private, and seed objects into a single nonstandard 'blob'. The public and private
+ * objects are required, while the seed is optional. This is not a (publicly) standard format, this is
+ * specific to how we currently store the sealed object. This 'blob' can be unmarshalled by
* tpm2_unmarshal_blob(). */
-static int tpm2_marshal_blob(
+int tpm2_marshal_blob(
const TPM2B_PUBLIC *public,
const TPM2B_PRIVATE *private,
+ const TPM2B_ENCRYPTED_SECRET *seed,
void **ret_blob,
size_t *ret_blob_size) {
@@ -4281,6 +4350,8 @@ static int tpm2_marshal_blob(
assert(ret_blob_size);
size_t max_size = sizeof(*private) + sizeof(*public);
+ if (seed)
+ max_size += sizeof(*seed);
_cleanup_free_ void *blob = malloc(max_size);
if (!blob)
@@ -4297,26 +4368,36 @@ static int tpm2_marshal_blob(
return log_debug_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE),
"Failed to marshal public key: %s", sym_Tss2_RC_Decode(rc));
+ if (seed) {
+ rc = sym_Tss2_MU_TPM2B_ENCRYPTED_SECRET_Marshal(seed, blob, max_size, &blob_size);
+ if (rc != TSS2_RC_SUCCESS)
+ return log_debug_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE),
+ "Failed to marshal encrypted seed: %s", sym_Tss2_RC_Decode(rc));
+ }
+
*ret_blob = TAKE_PTR(blob);
*ret_blob_size = blob_size;
return 0;
}
-/* Unmarshal the 'blob' into public and private objects. This is not a (publicly) standard format, this is
- * specific to how we currently store the sealed object. This expects the 'blob' to have been created by
+/* Unmarshal the 'blob' into public, private, and seed objects. The public and private objects are required
+ * in the 'blob', while the seed is optional. This is not a (publicly) standard format, this is specific to
+ * how we currently store the sealed object. This expects the 'blob' to have been created by
* tpm2_marshal_blob(). */
-static int tpm2_unmarshal_blob(
+int tpm2_unmarshal_blob(
const void *blob,
size_t blob_size,
TPM2B_PUBLIC *ret_public,
- TPM2B_PRIVATE *ret_private) {
+ TPM2B_PRIVATE *ret_private,
+ TPM2B_ENCRYPTED_SECRET *ret_seed) {
TSS2_RC rc;
assert(blob);
assert(ret_public);
assert(ret_private);
+ assert(ret_seed);
TPM2B_PRIVATE private = {};
size_t offset = 0;
@@ -4331,8 +4412,67 @@ static int tpm2_unmarshal_blob(
return log_debug_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE),
"Failed to unmarshal public key: %s", sym_Tss2_RC_Decode(rc));
+ TPM2B_ENCRYPTED_SECRET seed = {};
+ if (blob_size > offset) {
+ rc = sym_Tss2_MU_TPM2B_ENCRYPTED_SECRET_Unmarshal(blob, blob_size, &offset, &seed);
+ if (rc != TSS2_RC_SUCCESS)
+ return log_debug_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE),
+ "Failed to unmarshal encrypted seed: %s", sym_Tss2_RC_Decode(rc));
+ }
+
*ret_public = public;
*ret_private = private;
+ *ret_seed = seed;
+
+ return 0;
+}
+
+/* Calculate a serialized handle. Once the upstream tpm2-tss library provides an api to do this, we can
+ * remove this function. The addition of this functionality in tpm2-tss may be tracked here:
+ * https://github.com/tpm2-software/tpm2-tss/issues/2575 */
+int tpm2_calculate_serialize(
+ TPM2_HANDLE handle,
+ const TPM2B_NAME *name,
+ const TPM2B_PUBLIC *public,
+ void **ret_serialized,
+ size_t *ret_serialized_size) {
+
+ TSS2_RC rc;
+
+ assert(name);
+ assert(public);
+ assert(ret_serialized);
+ assert(ret_serialized_size);
+
+ size_t max_size = sizeof(TPM2_HANDLE) + sizeof(TPM2B_NAME) + sizeof(uint32_t) + sizeof(TPM2B_PUBLIC);
+ _cleanup_free_ void *serialized = malloc(max_size);
+ if (!serialized)
+ return log_oom_debug();
+
+ size_t serialized_size = 0;
+ rc = sym_Tss2_MU_TPM2_HANDLE_Marshal(handle, serialized, max_size, &serialized_size);
+ if (rc != TSS2_RC_SUCCESS)
+ return log_debug_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE),
+ "Failed to marshal tpm handle: %s", sym_Tss2_RC_Decode(rc));
+
+ rc = sym_Tss2_MU_TPM2B_NAME_Marshal(name, serialized, max_size, &serialized_size);
+ if (rc != TSS2_RC_SUCCESS)
+ return log_debug_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE),
+ "Failed to marshal name: %s", sym_Tss2_RC_Decode(rc));
+
+ /* This is defined (non-publicly) in the tpm2-tss source as IESYSC_KEY_RSRC, to a value of "1". */
+ rc = sym_Tss2_MU_UINT32_Marshal(UINT32_C(1), serialized, max_size, &serialized_size);
+ if (rc != TSS2_RC_SUCCESS)
+ return log_debug_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE),
+ "Failed to marshal esys resource id: %s", sym_Tss2_RC_Decode(rc));
+
+ rc = sym_Tss2_MU_TPM2B_PUBLIC_Marshal(public, serialized, max_size, &serialized_size);
+ if (rc != TSS2_RC_SUCCESS)
+ return log_debug_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE),
+ "Failed to marshal public: %s", sym_Tss2_RC_Decode(rc));
+
+ *ret_serialized = TAKE_PTR(serialized);
+ *ret_serialized_size = serialized_size;
return 0;
}
@@ -4396,6 +4536,654 @@ int tpm2_deserialize(
return 0;
}
+#if HAVE_OPENSSL
+
+/* KDFa() as defined by the TPM spec. */
+static int tpm2_kdfa(
+ TPMI_ALG_HASH hash_alg,
+ const void *key,
+ size_t key_len,
+ const char *label,
+ const void *context,
+ size_t context_len,
+ size_t bits,
+ void **ret_key,
+ size_t *ret_key_len) {
+
+ int r;
+
+ assert(key);
+ assert(label);
+ assert(context || context_len == 0);
+ assert(bits > 0);
+ assert(bits <= SIZE_MAX - 7);
+ assert(ret_key);
+ assert(ret_key_len);
+
+ log_debug("Calculating KDFa().");
+
+ size_t len = DIV_ROUND_UP(bits, 8);
+
+ const char *hash_alg_name = tpm2_hash_alg_to_string(hash_alg);
+ if (!hash_alg_name)
+ return -EOPNOTSUPP;
+
+ _cleanup_free_ void *buf = NULL;
+ r = kdf_kb_hmac_derive(
+ "COUNTER",
+ hash_alg_name,
+ key,
+ key_len,
+ label,
+ strlen(label),
+ context,
+ context_len,
+ /* seed= */ NULL,
+ /* seed_len= */ 0,
+ len,
+ &buf);
+ if (r < 0)
+ return r;
+
+ /* If the number of bits results in a partial byte, the TPM spec requires we zero the unrequested
+ * bits in the MSB (i.e. at index 0). From the spec Part 1 ("Architecture") section on Key
+ * Derivation Function, specifically KDFa():
+ *
+ * "The implied return from this function is a sequence of octets with a length equal to (bits + 7) /
+ * 8. If bits is not an even multiple of 8, then the returned value occupies the least significant
+ * bits of the returned octet array, and the additional, high-order bits in the 0th octet are
+ * CLEAR. The unused bits of the most significant octet (MSO) are masked off and not shifted." */
+ size_t partial = bits % 8;
+ if (partial > 0)
+ ((uint8_t*) buf)[0] &= 0xffu >> (8 - partial);
+
+ *ret_key = TAKE_PTR(buf);
+ *ret_key_len = len;
+
+ return 0;
+}
+
+/* KDFe() as defined by the TPM spec. */
+static int tpm2_kdfe(
+ TPMI_ALG_HASH hash_alg,
+ const void *shared_secret,
+ size_t shared_secret_len,
+ const char *label,
+ const void *context_u,
+ size_t context_u_size,
+ const void *context_v,
+ size_t context_v_size,
+ size_t bits,
+ void **ret_key,
+ size_t *ret_key_len) {
+
+ int r;
+
+ assert(shared_secret);
+ assert(label);
+ assert(context_u);
+ assert(context_v);
+ assert(bits > 0);
+ assert(bits <= SIZE_MAX - 7);
+ assert(ret_key);
+ assert(ret_key_len);
+
+ log_debug("Calculating KDFe().");
+
+ size_t len = DIV_ROUND_UP(bits, 8);
+
+ const char *hash_alg_name = tpm2_hash_alg_to_string(hash_alg);
+ if (!hash_alg_name)
+ return -EOPNOTSUPP;
+
+ size_t info_len = strlen(label) + 1 + context_u_size + context_v_size;
+ _cleanup_free_ void *info = malloc(info_len);
+ if (!info)
+ return log_oom_debug();
+
+ void *end = mempcpy(mempcpy(stpcpy(info, label) + 1, context_u, context_u_size), context_v, context_v_size);
+ /* assert we copied exactly the right amount that we allocated */
+ assert(end > info && (uintptr_t) end - (uintptr_t) info == info_len);
+
+ _cleanup_free_ void *buf = NULL;
+ r = kdf_ss_derive(
+ hash_alg_name,
+ shared_secret,
+ shared_secret_len,
+ /* salt= */ NULL,
+ /* salt_size= */ 0,
+ info,
+ info_len,
+ len,
+ &buf);
+ if (r < 0)
+ return r;
+
+ *ret_key = TAKE_PTR(buf);
+ *ret_key_len = len;
+
+ return 0;
+}
+
+static int tpm2_calculate_seal_public(
+ const TPM2B_PUBLIC *parent,
+ const TPMA_OBJECT *attributes,
+ const TPM2B_DIGEST *policy,
+ const TPM2B_DIGEST *seed,
+ const void *secret,
+ size_t secret_size,
+ TPM2B_PUBLIC *ret) {
+
+ int r;
+
+ assert(parent);
+ assert(seed);
+ assert(secret);
+ assert(ret);
+
+ log_debug("Calculating public part of sealed object.");
+
+ struct iovec data[] = {
+ IOVEC_MAKE((void*) seed->buffer, seed->size),
+ IOVEC_MAKE((void*) secret, secret_size),
+ };
+ TPM2B_DIGEST unique;
+ r = tpm2_digest_many(
+ parent->publicArea.nameAlg,
+ &unique,
+ data,
+ ELEMENTSOF(data),
+ /* extend= */ false);
+ if (r < 0)
+ return r;
+
+ *ret = (TPM2B_PUBLIC) {
+ .size = sizeof(TPMT_PUBLIC),
+ .publicArea = {
+ .type = TPM2_ALG_KEYEDHASH,
+ .nameAlg = parent->publicArea.nameAlg,
+ .objectAttributes = attributes ? *attributes : 0,
+ .authPolicy = policy ? *policy : TPM2B_DIGEST_MAKE(NULL, unique.size),
+ .parameters.keyedHashDetail.scheme.scheme = TPM2_ALG_NULL,
+ .unique.keyedHash = unique,
+ },
+ };
+
+ return 0;
+}
+
+static int tpm2_calculate_seal_private(
+ const TPM2B_PUBLIC *parent,
+ const TPM2B_NAME *name,
+ const char *pin,
+ const TPM2B_DIGEST *seed,
+ const void *secret,
+ size_t secret_size,
+ TPM2B_PRIVATE *ret) {
+
+ TSS2_RC rc;
+ int r;
+
+ assert(parent);
+ assert(name);
+ assert(seed);
+ assert(secret);
+ assert(ret);
+
+ log_debug("Calculating private part of sealed object.");
+
+ _cleanup_free_ void *storage_key = NULL;
+ size_t storage_key_size;
+ r = tpm2_kdfa(parent->publicArea.nameAlg,
+ seed->buffer,
+ seed->size,
+ "STORAGE",
+ name->name,
+ name->size,
+ (size_t) parent->publicArea.parameters.asymDetail.symmetric.keyBits.sym,
+ &storage_key,
+ &storage_key_size);
+ if (r < 0)
+ return log_debug_errno(r, "Could not calculate storage key KDFa: %m");
+
+ r = tpm2_hash_alg_to_size(parent->publicArea.nameAlg);
+ if (r < 0)
+ return -EOPNOTSUPP;
+
+ size_t bits = (size_t) r * 8;
+
+ _cleanup_free_ void *integrity_key = NULL;
+ size_t integrity_key_size;
+ r = tpm2_kdfa(parent->publicArea.nameAlg,
+ seed->buffer,
+ seed->size,
+ "INTEGRITY",
+ /* context= */ NULL,
+ /* n_context= */ 0,
+ bits,
+ &integrity_key,
+ &integrity_key_size);
+ if (r < 0)
+ return log_debug_errno(r, "Could not calculate integrity key KDFa: %m");
+
+ TPM2B_AUTH auth = {};
+ if (pin) {
+ r = tpm2_get_pin_auth(parent->publicArea.nameAlg, pin, &auth);
+ if (r < 0)
+ return r;
+ }
+
+ TPM2B_SENSITIVE sensitive = {
+ .size = sizeof(TPMT_SENSITIVE),
+ .sensitiveArea = {
+ .sensitiveType = TPM2_ALG_KEYEDHASH,
+ .authValue = auth,
+ .seedValue = *seed,
+ .sensitive.bits = TPM2B_SENSITIVE_DATA_MAKE(secret, secret_size),
+ },
+ };
+
+ _cleanup_free_ void *marshalled_sensitive = malloc(sizeof(sensitive));
+ if (!marshalled_sensitive)
+ return log_oom_debug();
+
+ size_t marshalled_sensitive_size = 0;
+ rc = sym_Tss2_MU_TPM2B_SENSITIVE_Marshal(
+ &sensitive,
+ marshalled_sensitive,
+ sizeof(sensitive),
+ &marshalled_sensitive_size);
+ if (rc != TSS2_RC_SUCCESS)
+ return log_debug_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE),
+ "Failed to marshal sensitive: %s", sym_Tss2_RC_Decode(rc));
+
+ const char *sym_alg = tpm2_sym_alg_to_string(parent->publicArea.parameters.asymDetail.symmetric.algorithm);
+ if (!sym_alg)
+ return -EOPNOTSUPP;
+
+ const char *sym_mode = tpm2_sym_mode_to_string(parent->publicArea.parameters.asymDetail.symmetric.mode.sym);
+ if (!sym_mode)
+ return -EOPNOTSUPP;
+
+ _cleanup_free_ void *encrypted_sensitive = NULL;
+ size_t encrypted_sensitive_size;
+ r = openssl_cipher(
+ sym_alg,
+ parent->publicArea.parameters.asymDetail.symmetric.keyBits.sym,
+ sym_mode,
+ storage_key, storage_key_size,
+ /* iv= */ NULL, /* n_iv= */ 0,
+ marshalled_sensitive, marshalled_sensitive_size,
+ &encrypted_sensitive, &encrypted_sensitive_size);
+ if (r < 0)
+ return r;
+
+ const char *hash_alg_name = tpm2_hash_alg_to_string(parent->publicArea.nameAlg);
+ if (!hash_alg_name)
+ return -EOPNOTSUPP;
+
+ _cleanup_free_ void *hmac_buffer = NULL;
+ size_t hmac_size = 0;
+ struct iovec hmac_data[] = {
+ IOVEC_MAKE((void*) encrypted_sensitive, encrypted_sensitive_size),
+ IOVEC_MAKE((void*) name->name, name->size),
+ };
+ r = openssl_hmac_many(
+ hash_alg_name,
+ integrity_key,
+ integrity_key_size,
+ hmac_data,
+ ELEMENTSOF(hmac_data),
+ &hmac_buffer,
+ &hmac_size);
+ if (r < 0)
+ return r;
+
+ TPM2B_DIGEST outer_hmac = TPM2B_DIGEST_MAKE(hmac_buffer, hmac_size);
+
+ TPM2B_PRIVATE private = {};
+ size_t private_size = 0;
+ rc = sym_Tss2_MU_TPM2B_DIGEST_Marshal(
+ &outer_hmac,
+ private.buffer,
+ sizeof(private.buffer),
+ &private_size);
+ if (rc != TSS2_RC_SUCCESS)
+ return log_debug_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE),
+ "Failed to marshal digest: %s", sym_Tss2_RC_Decode(rc));
+ private.size = private_size;
+
+ assert(sizeof(private.buffer) - private.size >= encrypted_sensitive_size);
+ memcpy_safe(&private.buffer[private.size], encrypted_sensitive, encrypted_sensitive_size);
+ private.size += encrypted_sensitive_size;
+
+ *ret = private;
+
+ return 0;
+}
+
+static int tpm2_calculate_seal_rsa_seed(
+ const TPM2B_PUBLIC *parent,
+ void **ret_seed,
+ size_t *ret_seed_size,
+ void **ret_encrypted_seed,
+ size_t *ret_encrypted_seed_size) {
+
+ int r;
+
+ assert(parent);
+ assert(ret_seed);
+ assert(ret_seed_size);
+ assert(ret_encrypted_seed);
+ assert(ret_encrypted_seed_size);
+
+ log_debug("Calculating encrypted seed for RSA sealed object.");
+
+ _cleanup_(EVP_PKEY_freep) EVP_PKEY *parent_pkey = NULL;
+ r = tpm2_tpm2b_public_to_openssl_pkey(parent, &parent_pkey);
+ if (r < 0)
+ return log_debug_errno(r, "Could not convert TPM2B_PUBLIC to Openssl PKEY: %m");
+
+ r = tpm2_hash_alg_to_size(parent->publicArea.nameAlg);
+ if (r < 0)
+ return -EOPNOTSUPP;
+
+ size_t seed_size = (size_t) r;
+
+ _cleanup_free_ void *seed = malloc(seed_size);
+ if (!seed)
+ return log_oom_debug();
+
+ r = crypto_random_bytes(seed, seed_size);
+ if (r < 0)
+ return log_debug_errno(r, "Failed to generate random seed: %m");
+
+ const char *hash_alg_name = tpm2_hash_alg_to_string(parent->publicArea.nameAlg);
+ if (!hash_alg_name)
+ return -EOPNOTSUPP;
+
+ _cleanup_free_ void *encrypted_seed = NULL;
+ size_t encrypted_seed_size;
+ r = rsa_oaep_encrypt_bytes(
+ parent_pkey,
+ hash_alg_name,
+ "DUPLICATE",
+ seed,
+ seed_size,
+ &encrypted_seed,
+ &encrypted_seed_size);
+ if (r < 0)
+ return log_debug_errno(r, "Could not RSA-OAEP encrypt random seed: %m");
+
+ *ret_seed = TAKE_PTR(seed);
+ *ret_seed_size = seed_size;
+ *ret_encrypted_seed = TAKE_PTR(encrypted_seed);
+ *ret_encrypted_seed_size = encrypted_seed_size;
+
+ return 0;
+}
+
+static int tpm2_calculate_seal_ecc_seed(
+ const TPM2B_PUBLIC *parent,
+ void **ret_seed,
+ size_t *ret_seed_size,
+ void **ret_encrypted_seed,
+ size_t *ret_encrypted_seed_size) {
+
+ TSS2_RC rc;
+ int r;
+
+ assert(parent);
+ assert(ret_seed);
+ assert(ret_seed_size);
+ assert(ret_encrypted_seed);
+ assert(ret_encrypted_seed_size);
+
+ log_debug("Calculating encrypted seed for ECC sealed object.");
+
+ _cleanup_(EVP_PKEY_freep) EVP_PKEY *parent_pkey = NULL;
+ r = tpm2_tpm2b_public_to_openssl_pkey(parent, &parent_pkey);
+ if (r < 0)
+ return log_debug_errno(r, "Could not convert TPM2B_PUBLIC to Openssl PKEY: %m");
+
+ int curve_id;
+ r = ecc_pkey_to_curve_x_y(
+ parent_pkey,
+ &curve_id,
+ /* ret_x= */ NULL, /* ret_x_size= */ 0,
+ /* ret_y= */ NULL, /* ret_y_size= */ 0);
+ if (r < 0)
+ return r;
+
+ _cleanup_(EVP_PKEY_freep) EVP_PKEY *pkey = NULL;
+ r = ecc_pkey_new(curve_id, &pkey);
+ if (r < 0)
+ return r;
+
+ _cleanup_free_ void *shared_secret = NULL;
+ size_t shared_secret_size;
+ r = ecc_ecdh(pkey, parent_pkey, &shared_secret, &shared_secret_size);
+ if (r < 0)
+ return log_debug_errno(r, "Could not generate ECC shared secret: %m");
+
+ _cleanup_free_ void *x = NULL, *y = NULL;
+ size_t x_size, y_size;
+ r = ecc_pkey_to_curve_x_y(pkey, /* curve_id= */ NULL, &x, &x_size, &y, &y_size);
+ if (r < 0)
+ return log_debug_errno(r, "Could not get ECC get x/y: %m");
+
+ r = TPM2B_ECC_PARAMETER_CHECK_SIZE(x_size);
+ if (r < 0)
+ return log_debug_errno(r, "ECC point x size %zu is too large: %m", x_size);
+
+ r = TPM2B_ECC_PARAMETER_CHECK_SIZE(y_size);
+ if (r < 0)
+ return log_debug_errno(r, "ECC point y size %zu is too large: %m", y_size);
+
+ TPMS_ECC_POINT point = {
+ .x = TPM2B_ECC_PARAMETER_MAKE(x, x_size),
+ .y = TPM2B_ECC_PARAMETER_MAKE(y, y_size),
+ };
+
+ _cleanup_free_ void *encrypted_seed = malloc(sizeof(point));
+ if (!encrypted_seed)
+ return log_oom_debug();
+
+ size_t encrypted_seed_size = 0;
+ rc = sym_Tss2_MU_TPMS_ECC_POINT_Marshal(&point, encrypted_seed, sizeof(point), &encrypted_seed_size);
+ if (rc != TPM2_RC_SUCCESS)
+ return log_debug_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE),
+ "Failed to marshal ECC point: %s", sym_Tss2_RC_Decode(rc));
+
+ r = tpm2_hash_alg_to_size(parent->publicArea.nameAlg);
+ if (r < 0)
+ return -EOPNOTSUPP;
+
+ size_t bits = (size_t) r * 8;
+
+ _cleanup_free_ void *seed = NULL;
+ size_t seed_size;
+ r = tpm2_kdfe(parent->publicArea.nameAlg,
+ shared_secret,
+ shared_secret_size,
+ "DUPLICATE",
+ x,
+ x_size,
+ parent->publicArea.unique.ecc.x.buffer,
+ parent->publicArea.unique.ecc.x.size,
+ bits,
+ &seed,
+ &seed_size);
+ if (r < 0)
+ return log_debug_errno(r, "Could not calculate KDFe: %m");
+
+ *ret_seed = TAKE_PTR(seed);
+ *ret_seed_size = seed_size;
+ *ret_encrypted_seed = TAKE_PTR(encrypted_seed);
+ *ret_encrypted_seed_size = encrypted_seed_size;
+
+ return 0;
+}
+
+static int tpm2_calculate_seal_seed(
+ const TPM2B_PUBLIC *parent,
+ TPM2B_DIGEST *ret_seed,
+ TPM2B_ENCRYPTED_SECRET *ret_encrypted_seed) {
+
+ int r;
+
+ assert(parent);
+ assert(ret_seed);
+ assert(ret_encrypted_seed);
+
+ log_debug("Calculating encrypted seed for sealed object.");
+
+ _cleanup_free_ void *seed = NULL, *encrypted_seed = NULL;
+ size_t seed_size, encrypted_seed_size;
+ if (parent->publicArea.type == TPM2_ALG_RSA)
+ r = tpm2_calculate_seal_rsa_seed(parent, &seed, &seed_size, &encrypted_seed, &encrypted_seed_size);
+ else if (parent->publicArea.type == TPM2_ALG_ECC)
+ r = tpm2_calculate_seal_ecc_seed(parent, &seed, &seed_size, &encrypted_seed, &encrypted_seed_size);
+ else
+ return log_debug_errno(SYNTHETIC_ERRNO(EOPNOTSUPP),
+ "Unsupported parent key type 0x%" PRIx16, parent->publicArea.type);
+ if (r < 0)
+ return log_debug_errno(r, "Could not calculate encrypted seed: %m");
+
+ *ret_seed = TPM2B_DIGEST_MAKE(seed, seed_size);
+ *ret_encrypted_seed = TPM2B_ENCRYPTED_SECRET_MAKE(encrypted_seed, encrypted_seed_size);
+
+ return 0;
+}
+
+#endif /* HAVE_OPENSSL */
+
+int tpm2_calculate_seal(
+ TPM2_HANDLE parent_handle,
+ const TPM2B_PUBLIC *parent_public,
+ const TPMA_OBJECT *attributes,
+ const void *secret,
+ size_t secret_size,
+ const TPM2B_DIGEST *policy,
+ const char *pin,
+ void **ret_secret,
+ size_t *ret_secret_size,
+ void **ret_blob,
+ size_t *ret_blob_size,
+ void **ret_serialized_parent,
+ size_t *ret_serialized_parent_size) {
+
+#if HAVE_OPENSSL
+ int r;
+
+ assert(parent_public);
+ assert(secret || secret_size == 0);
+ assert(secret || ret_secret);
+ assert(!(secret && ret_secret)); /* Either provide a secret, or we create one, but not both */
+ assert(ret_blob);
+ assert(ret_blob_size);
+ assert(ret_serialized_parent);
+ assert(ret_serialized_parent_size);
+
+ log_debug("Calculating sealed object.");
+
+ /* Default to the SRK. */
+ if (parent_handle == 0)
+ parent_handle = TPM2_SRK_HANDLE;
+
+ switch (TPM2_HANDLE_TYPE(parent_handle)) {
+ case TPM2_HT_PERSISTENT:
+ case TPM2_HT_NV_INDEX:
+ break;
+ case TPM2_HT_TRANSIENT:
+ log_warning("Handle is transient, sealed secret may not be recoverable.");
+ break;
+ default:
+ return log_debug_errno(SYNTHETIC_ERRNO(EINVAL),
+ "Handle 0x%" PRIx32 " not persistent, transient, or NV.",
+ parent_handle);
+ }
+
+ _cleanup_(erase_and_freep) void *generated_secret = NULL;
+ if (!secret) {
+ /* No secret provided, generate a random secret. We use SHA256 digest length, though it can
+ * be up to TPM2_MAX_SEALED_DATA. The secret length is not limited to the nameAlg hash
+ * size. */
+ secret_size = TPM2_SHA256_DIGEST_SIZE;
+ generated_secret = malloc(secret_size);
+ if (!generated_secret)
+ return log_oom_debug();
+
+ r = crypto_random_bytes(generated_secret, secret_size);
+ if (r < 0)
+ return log_debug_errno(r, "Failed to generate secret key: %m");
+
+ secret = generated_secret;
+ }
+
+ if (secret_size > TPM2_MAX_SEALED_DATA)
+ return log_debug_errno(SYNTHETIC_ERRNO(EOVERFLOW),
+ "Secret size %zu too large, limit is %d bytes.",
+ secret_size, TPM2_MAX_SEALED_DATA);
+
+ TPM2B_DIGEST random_seed;
+ TPM2B_ENCRYPTED_SECRET seed;
+ r = tpm2_calculate_seal_seed(parent_public, &random_seed, &seed);
+ if (r < 0)
+ return r;
+
+ TPM2B_PUBLIC public;
+ r = tpm2_calculate_seal_public(parent_public, attributes, policy, &random_seed, secret, secret_size, &public);
+ if (r < 0)
+ return r;
+
+ TPM2B_NAME name;
+ r = tpm2_calculate_pubkey_name(&public.publicArea, &name);
+ if (r < 0)
+ return r;
+
+ TPM2B_PRIVATE private;
+ r = tpm2_calculate_seal_private(parent_public, &name, pin, &random_seed, secret, secret_size, &private);
+ if (r < 0)
+ return r;
+
+ _cleanup_free_ void *blob = NULL;
+ size_t blob_size;
+ r = tpm2_marshal_blob(&public, &private, &seed, &blob, &blob_size);
+ if (r < 0)
+ return log_debug_errno(r, "Could not create sealed blob: %m");
+
+ TPM2B_NAME parent_name;
+ r = tpm2_calculate_pubkey_name(&parent_public->publicArea, &parent_name);
+ if (r < 0)
+ return r;
+
+ _cleanup_free_ void *serialized_parent = NULL;
+ size_t serialized_parent_size;
+ r = tpm2_calculate_serialize(
+ parent_handle,
+ &parent_name,
+ parent_public,
+ &serialized_parent,
+ &serialized_parent_size);
+ if (r < 0)
+ return r;
+
+ if (ret_secret)
+ *ret_secret = TAKE_PTR(generated_secret);
+ if (ret_secret_size)
+ *ret_secret_size = secret_size;
+ *ret_blob = TAKE_PTR(blob);
+ *ret_blob_size = blob_size;
+ *ret_serialized_parent = TAKE_PTR(serialized_parent);
+ *ret_serialized_parent_size = serialized_parent_size;
+
+ return 0;
+#else /* HAVE_OPENSSL */
+ return log_debug_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "OpenSSL support is disabled.");
+#endif
+}
+
int tpm2_seal(Tpm2Context *c,
uint32_t seal_key_handle,
const TPM2B_DIGEST *policy,
@@ -4560,7 +5348,7 @@ int tpm2_seal(Tpm2Context *c,
_cleanup_free_ void *blob = NULL;
size_t blob_size = 0;
- r = tpm2_marshal_blob(public, private, &blob, &blob_size);
+ r = tpm2_marshal_blob(public, private, /* seed= */ NULL, &blob, &blob_size);
if (r < 0)
return log_debug_errno(r, "Could not create sealed blob: %m");
@@ -4645,7 +5433,8 @@ int tpm2_unseal(Tpm2Context *c,
TPM2B_PUBLIC public;
TPM2B_PRIVATE private;
- r = tpm2_unmarshal_blob(blob, blob_size, &public, &private);
+ TPM2B_ENCRYPTED_SECRET seed = {};
+ r = tpm2_unmarshal_blob(blob, blob_size, &public, &private, &seed);
if (r < 0)
return log_debug_errno(r, "Could not extract parts from blob: %m");
@@ -4683,6 +5472,24 @@ int tpm2_unseal(Tpm2Context *c,
return log_debug_errno(SYNTHETIC_ERRNO(EINVAL),
"No SRK or primary alg provided.");
+ if (seed.size > 0) {
+ /* This is a calculated (or duplicated) sealed object, and must be imported. */
+ _cleanup_free_ TPM2B_PRIVATE *imported_private = NULL;
+ r = tpm2_import(c,
+ primary_handle,
+ /* session= */ NULL,
+ &public,
+ &private,
+ &seed,
+ /* encryption_key= */ NULL,
+ /* symmetric= */ NULL,
+ &imported_private);
+ if (r < 0)
+ return r;
+
+ private = *imported_private;
+ }
+
log_debug("Loading HMAC key into TPM.");
/*
@@ -6416,6 +7223,62 @@ int tpm2_asym_alg_from_string(const char *alg) {
return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "Unknown asymmetric algorithm name '%s'", alg);
}
+const char *tpm2_sym_alg_to_string(uint16_t alg) {
+ switch (alg) {
+#if HAVE_TPM2
+ case TPM2_ALG_AES:
+ return "aes";
+#endif
+ default:
+ log_debug("Unknown symmetric algorithm id 0x%" PRIx16, alg);
+ return NULL;
+ }
+}
+
+int tpm2_sym_alg_from_string(const char *alg) {
+#if HAVE_TPM2
+ if (strcaseeq_ptr(alg, "aes"))
+ return TPM2_ALG_AES;
+#endif
+ return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "Unknown symmetric algorithm name '%s'", alg);
+}
+
+const char *tpm2_sym_mode_to_string(uint16_t mode) {
+ switch (mode) {
+#if HAVE_TPM2
+ case TPM2_ALG_CTR:
+ return "ctr";
+ case TPM2_ALG_OFB:
+ return "ofb";
+ case TPM2_ALG_CBC:
+ return "cbc";
+ case TPM2_ALG_CFB:
+ return "cfb";
+ case TPM2_ALG_ECB:
+ return "ecb";
+#endif
+ default:
+ log_debug("Unknown symmetric mode id 0x%" PRIx16, mode);
+ return NULL;
+ }
+}
+
+int tpm2_sym_mode_from_string(const char *mode) {
+#if HAVE_TPM2
+ if (strcaseeq_ptr(mode, "ctr"))
+ return TPM2_ALG_CTR;
+ if (strcaseeq_ptr(mode, "ofb"))
+ return TPM2_ALG_OFB;
+ if (strcaseeq_ptr(mode, "cbc"))
+ return TPM2_ALG_CBC;
+ if (strcaseeq_ptr(mode, "cfb"))
+ return TPM2_ALG_CFB;
+ if (strcaseeq_ptr(mode, "ecb"))
+ return TPM2_ALG_ECB;
+#endif
+ return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "Unknown symmetric mode name '%s'", mode);
+}
+
Tpm2Support tpm2_support(void) {
Tpm2Support support = TPM2_SUPPORT_NONE;
int r;
diff --git a/src/shared/tpm2-util.h b/src/shared/tpm2-util.h
index 35d851a0d0..e867976369 100644
--- a/src/shared/tpm2-util.h
+++ b/src/shared/tpm2-util.h
@@ -30,6 +30,11 @@ typedef enum TPM2Flags {
* the Provisioning Guidance document for more details. */
#define TPM2_SRK_HANDLE UINT32_C(0x81000001)
+/* 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." */
+#define TPM2_MAX_SEALED_DATA UINT16_C(128)
+
static inline bool TPM2_PCR_INDEX_VALID(unsigned pcr) {
return pcr < TPM2_PCRS_MAX;
}
@@ -123,6 +128,8 @@ int tpm2_create_loaded(Tpm2Context *c, const Tpm2Handle *parent, const Tpm2Handl
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_nv_public(const TPM2B_NV_PUBLIC *nv_public, void **ret, size_t *ret_size);
int tpm2_unmarshal_nv_public(const void *data, size_t size, TPM2B_NV_PUBLIC *ret_nv_public);
+int tpm2_marshal_blob(const TPM2B_PUBLIC *public, const TPM2B_PRIVATE *private, const TPM2B_ENCRYPTED_SECRET *seed, void **ret_blob, size_t *ret_blob_size);
+int tpm2_unmarshal_blob(const void *blob, size_t blob_size, TPM2B_PUBLIC *ret_public, TPM2B_PRIVATE *ret_private, TPM2B_ENCRYPTED_SECRET *ret_seed);
bool tpm2_supports_alg(Tpm2Context *c, TPM2_ALG_ID alg);
bool tpm2_supports_command(Tpm2Context *c, TPM2_CC command);
@@ -267,8 +274,14 @@ int tpm2_calculate_policy_authorize_nv(const TPM2B_NV_PUBLIC *public, TPM2B_DIGE
int tpm2_calculate_policy_pcr(const Tpm2PCRValue *pcr_values, size_t n_pcr_values, TPM2B_DIGEST *digest);
int tpm2_calculate_policy_or(const TPM2B_DIGEST *branches, size_t n_branches, TPM2B_DIGEST *digest);
int tpm2_calculate_policy_super_pcr(Tpm2PCRPrediction *prediction, uint16_t algorithm, TPM2B_DIGEST *pcr_policy);
+int tpm2_calculate_serialize(TPM2_HANDLE handle, const TPM2B_NAME *name, const TPM2B_PUBLIC *public, void **ret_serialized, size_t *ret_serialized_size);
int tpm2_calculate_sealing_policy(const Tpm2PCRValue *pcr_values, size_t n_pcr_values, const TPM2B_PUBLIC *public, bool use_pin, const Tpm2PCRLockPolicy *policy, TPM2B_DIGEST *digest);
+int tpm2_calculate_seal(TPM2_HANDLE parent_handle, const TPM2B_PUBLIC *parent_public, const TPMA_OBJECT *attributes, const void *secret, size_t secret_size, const TPM2B_DIGEST *policy, const char *pin, void **ret_secret, size_t *ret_secret_size, void **ret_blob, size_t *ret_blob_size, void **ret_serialized_parent, size_t *ret_serialized_parent_size);
+int tpm2_get_srk_template(TPMI_ALG_PUBLIC alg, TPMT_PUBLIC *ret_template);
+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);
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);
@@ -348,6 +361,8 @@ int tpm2_deserialize(Tpm2Context *c, const void *serialized, size_t serialized_s
0; \
})
+extern TSS2_RC (*sym_Tss2_MU_TPM2B_PUBLIC_Unmarshal)(uint8_t const buffer[], size_t buffer_size, size_t *offset, TPM2B_PUBLIC *dest);
+
#else /* HAVE_TPM2 */
typedef struct {} Tpm2Context;
typedef struct {} Tpm2Handle;
@@ -408,6 +423,12 @@ int tpm2_hash_alg_from_string(const char *alg) _pure_;
const char *tpm2_asym_alg_to_string(uint16_t alg) _const_;
int tpm2_asym_alg_from_string(const char *alg) _pure_;
+const char *tpm2_sym_alg_to_string(uint16_t alg) _const_;
+int tpm2_sym_alg_from_string(const char *alg) _pure_;
+
+const char *tpm2_sym_mode_to_string(uint16_t mode) _const_;
+int tpm2_sym_mode_from_string(const char *mode) _pure_;
+
char *tpm2_pcr_mask_to_string(uint32_t mask);
extern const uint16_t tpm2_hash_algorithms[];
diff --git a/src/test/test-tpm2.c b/src/test/test-tpm2.c
index 7cefb0c93b..0469625ab1 100644
--- a/src/test/test-tpm2.c
+++ b/src/test/test-tpm2.c
@@ -961,6 +961,51 @@ TEST(calculate_policy_pcr) {
assert_se(digest_check(&d, "7481fd1b116078eb3ac2456e4ad542c9b46b9b8eb891335771ca8e7c8f8e4415"));
}
+static void check_srk_rsa_template(TPMT_PUBLIC *template) {
+ assert_se(template->type == TPM2_ALG_RSA);
+ assert_se(template->nameAlg == TPM2_ALG_SHA256);
+ assert_se(template->objectAttributes == 0x30472);
+ assert_se(template->parameters.rsaDetail.symmetric.algorithm == TPM2_ALG_AES);
+ assert_se(template->parameters.rsaDetail.symmetric.keyBits.sym == 128);
+ assert_se(template->parameters.rsaDetail.symmetric.mode.sym == TPM2_ALG_CFB);
+ assert_se(template->parameters.rsaDetail.scheme.scheme == TPM2_ALG_NULL);
+ assert_se(template->parameters.rsaDetail.keyBits == 2048);
+}
+
+static void check_srk_ecc_template(TPMT_PUBLIC *template) {
+ assert_se(template->type == TPM2_ALG_ECC);
+ assert_se(template->nameAlg == TPM2_ALG_SHA256);
+ assert_se(template->objectAttributes == 0x30472);
+ assert_se(template->parameters.eccDetail.symmetric.algorithm == TPM2_ALG_AES);
+ assert_se(template->parameters.eccDetail.symmetric.keyBits.sym == 128);
+ assert_se(template->parameters.eccDetail.symmetric.mode.sym == TPM2_ALG_CFB);
+ assert_se(template->parameters.eccDetail.scheme.scheme == TPM2_ALG_NULL);
+ assert_se(template->parameters.eccDetail.kdf.scheme == TPM2_ALG_NULL);
+ assert_se(template->parameters.eccDetail.curveID == TPM2_ECC_NIST_P256);
+}
+
+TEST(tpm2_get_srk_template) {
+ TPMT_PUBLIC template;
+
+ assert_se(tpm2_get_srk_template(TPM2_ALG_RSA, &template) >= 0);
+ check_srk_rsa_template(&template);
+
+ assert_se(tpm2_get_srk_template(TPM2_ALG_ECC, &template) >= 0);
+ check_srk_ecc_template(&template);
+}
+
+static void check_best_srk_template(Tpm2Context *c) {
+ TPMT_PUBLIC template;
+ assert_se(tpm2_get_best_srk_template(c, &template) >= 0);
+
+ assert_se(IN_SET(template.type, TPM2_ALG_ECC, TPM2_ALG_RSA));
+
+ if (template.type == TPM2_ALG_RSA)
+ check_srk_rsa_template(&template);
+ else
+ check_srk_ecc_template(&template);
+}
+
static void check_test_parms(Tpm2Context *c) {
assert(c);
@@ -1016,6 +1061,107 @@ static void check_supports_command(Tpm2Context *c) {
assert_se(tpm2_supports_command(c, TPM2_CC_Unseal));
}
+static void check_get_or_create_srk(Tpm2Context *c) {
+ _cleanup_free_ TPM2B_PUBLIC *public = NULL;
+ _cleanup_free_ TPM2B_NAME *name = NULL, *qname = NULL;
+ _cleanup_(tpm2_handle_freep) Tpm2Handle *handle = NULL;
+ assert_se(tpm2_get_or_create_srk(c, NULL, &public, &name, &qname, &handle) >= 0);
+ assert_se(public && name && qname && handle);
+
+ _cleanup_free_ TPM2B_PUBLIC *public2 = NULL;
+ _cleanup_free_ TPM2B_NAME *name2 = NULL, *qname2 = NULL;
+ _cleanup_(tpm2_handle_freep) Tpm2Handle *handle2 = NULL;
+ assert_se(tpm2_get_srk(c, NULL, &public2, &name2, &qname2, &handle2) >= 0);
+ assert_se(public2 && name2 && qname2 && handle2);
+
+ assert_se(memcmp_nn(public, sizeof(*public), public2, sizeof(*public2)) == 0);
+ assert_se(memcmp_nn(name->name, name->size, name2->name, name2->size) == 0);
+ assert_se(memcmp_nn(qname->name, qname->size, qname2->name, qname2->size) == 0);
+}
+
+#if HAVE_OPENSSL && OPENSSL_VERSION_MAJOR >= 3
+static void calculate_seal_and_unseal(
+ Tpm2Context *c,
+ TPM2_HANDLE parent_index,
+ const TPM2B_PUBLIC *parent_public) {
+
+ _cleanup_free_ char *secret_string = NULL;
+ assert_se(asprintf(&secret_string, "The classified documents are in room %x", parent_index) > 0);
+ size_t secret_size = strlen(secret_string) + 1;
+
+ _cleanup_free_ void *blob = NULL;
+ size_t blob_size = 0;
+ _cleanup_free_ void *serialized_parent = NULL;
+ size_t serialized_parent_size;
+ assert_se(tpm2_calculate_seal(
+ parent_index,
+ parent_public,
+ /* attributes= */ NULL,
+ secret_string, secret_size,
+ /* policy= */ NULL,
+ /* pin= */ NULL,
+ /* ret_secret= */ NULL, /* ret_secret_size= */ 0,
+ &blob, &blob_size,
+ &serialized_parent, &serialized_parent_size) >= 0);
+
+ _cleanup_free_ void *unsealed_secret = NULL;
+ size_t unsealed_secret_size;
+ 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,
+ /* pcrlock_policy= */ NULL,
+ /* primary_alg= */ 0,
+ blob, blob_size,
+ /* known_policy_hash= */ NULL, /* known_policy_hash_size= */ 0,
+ serialized_parent, serialized_parent_size,
+ &unsealed_secret, &unsealed_secret_size) >= 0);
+
+ assert_se(memcmp_nn(secret_string, secret_size, unsealed_secret, unsealed_secret_size) == 0);
+
+ char unsealed_string[unsealed_secret_size];
+ assert_se(snprintf(unsealed_string, unsealed_secret_size, "%s", (char*) unsealed_secret) == (int) unsealed_secret_size - 1);
+ log_debug("Unsealed secret is: %s", unsealed_string);
+}
+
+static int check_calculate_seal(Tpm2Context *c) {
+ assert(c);
+ int r;
+
+ _cleanup_free_ TPM2B_PUBLIC *srk_public = NULL;
+ assert_se(tpm2_get_srk(c, NULL, &srk_public, NULL, NULL, NULL) >= 0);
+ calculate_seal_and_unseal(c, TPM2_SRK_HANDLE, srk_public);
+
+ TPMI_ALG_ASYM test_algs[] = { TPM2_ALG_RSA, TPM2_ALG_ECC, };
+ for (unsigned i = 0; i < ELEMENTSOF(test_algs); i++) {
+ TPMI_ALG_ASYM alg = test_algs[i];
+
+ TPM2B_PUBLIC template = { .size = sizeof(TPMT_PUBLIC), };
+ assert_se(tpm2_get_srk_template(alg, &template.publicArea) >= 0);
+
+ _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);
+
+ /* Once our minimum libtss2-esys version is 2.4.0 or later, this can assume
+ * tpm2_index_from_handle() should always work. */
+ TPM2_HANDLE index;
+ r = tpm2_index_from_handle(c, handle, &index);
+ if (r == -EOPNOTSUPP)
+ return log_tests_skipped("libtss2-esys version too old to support tpm2_index_from_handle()");
+ assert_se(r >= 0);
+
+ calculate_seal_and_unseal(c, index, public);
+ }
+
+ return 0;
+}
+#endif /* HAVE_OPENSSL && OPENSSL_VERSION_MAJOR >= 3 */
+
static void check_seal_unseal_for_handle(Tpm2Context *c, TPM2_HANDLE handle) {
TPM2B_DIGEST policy = TPM2B_DIGEST_MAKE(NULL, TPM2_SHA256_DIGEST_SIZE);
@@ -1090,6 +1236,7 @@ static void check_seal_unseal(Tpm2Context *c) {
TEST_RET(tests_which_require_tpm) {
_cleanup_(tpm2_context_unrefp) Tpm2Context *c = NULL;
+ int r = 0;
if (tpm2_context_new(NULL, &c) < 0)
return log_tests_skipped("Could not find TPM");
@@ -1097,9 +1244,15 @@ TEST_RET(tests_which_require_tpm) {
check_test_parms(c);
check_supports_alg(c);
check_supports_command(c);
+ check_best_srk_template(c);
+ check_get_or_create_srk(c);
check_seal_unseal(c);
- return 0;
+#if HAVE_OPENSSL && OPENSSL_VERSION_MAJOR >= 3 /* calculating sealed object requires openssl >= 3 */
+ r = check_calculate_seal(c);
+#endif
+
+ return r;
}
#endif /* HAVE_TPM2 */
diff --git a/test/TEST-70-TPM2/test.sh b/test/TEST-70-TPM2/test.sh
index 3b3cce9154..c33e71cc06 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 openssl swtpm tpm2_createprimary tpm2_dictionarylockout tpm2_evictcontrol tpm2_flushcontext tpm2_pcrextend tpm2_pcrread
+test_require_bin openssl swtpm tpm2_createprimary tpm2_dictionarylockout tpm2_evictcontrol tpm2_flushcontext tpm2_pcrextend tpm2_pcrread tpm2_readpublic
test_append_files() {
local workspace="${1:?}"
@@ -31,6 +31,7 @@ test_append_files() {
inst_binary tpm2_flushcontext
inst_binary tpm2_pcrextend
inst_binary tpm2_pcrread
+ inst_binary tpm2_readpublic
}
do_test "$@"
diff --git a/test/units/testsuite-70.cryptsetup.sh b/test/units/testsuite-70.cryptsetup.sh
index 98a188fd1e..a1c6035e45 100755
--- a/test/units/testsuite-70.cryptsetup.sh
+++ b/test/units/testsuite-70.cryptsetup.sh
@@ -131,7 +131,15 @@ if tpm_has_pcr sha256 12; then
systemd-cryptsetup attach test-volume "$IMAGE" - tpm2-device=auto,headless=1
systemd-cryptsetup detach test-volume
- rm -f /tmp/pcr.dat
+ # enroll TPM using device key instead of direct access, then verify unlock using TPM
+ tpm2_pcrread -Q -o /tmp/pcr.dat sha256:12
+ CURRENT_PCR_VALUE=$(cat /sys/class/tpm/tpm0/pcr-sha256/12)
+ tpm2_readpublic -c 0x81000001 -o /tmp/srk.pub
+ PASSWORD=passphrase systemd-cryptenroll --tpm2-device-key=/tmp/srk.pub --tpm2-pcrs="12:sha256=$CURRENT_PCR_VALUE" "$IMAGE"
+ systemd-cryptsetup attach test-volume "$IMAGE" - tpm2-device=auto,headless=1
+ systemd-cryptsetup detach test-volume
+
+ rm -f /tmp/pcr.dat /tmp/srk.pub
fi
# Use default (0) seal key handle
@@ -168,16 +176,16 @@ systemd-cryptenroll --wipe-slot=tpm2 "$IMAGE"
PRIMARY=/tmp/primary.ctx
tpm2_createprimary -c "$PRIMARY"
PERSISTENT_LINE=$(tpm2_evictcontrol -c "$PRIMARY" | grep persistent-handle)
-PERSISTENT="0x${PERSISTENT_LINE##*0x}"
+PERSISTENT_HANDLE="0x${PERSISTENT_LINE##*0x}"
tpm2_flushcontext -t
systemd-cryptenroll --wipe-slot=tpm2 "$IMAGE"
-PASSWORD=passphrase systemd-cryptenroll --tpm2-device=auto --tpm2-seal-key-handle="${PERSISTENT#0x}" "$IMAGE"
+PASSWORD=passphrase systemd-cryptenroll --tpm2-device=auto --tpm2-seal-key-handle="${PERSISTENT_HANDLE#0x}" "$IMAGE"
systemd-cryptsetup attach test-volume "$IMAGE" - tpm2-device=auto,headless=1
systemd-cryptsetup detach test-volume
systemd-cryptenroll --wipe-slot=tpm2 "$IMAGE"
-PASSWORD=passphrase systemd-cryptenroll --tpm2-device=auto --tpm2-seal-key-handle="$PERSISTENT" "$IMAGE"
+PASSWORD=passphrase systemd-cryptenroll --tpm2-device=auto --tpm2-seal-key-handle="$PERSISTENT_HANDLE" "$IMAGE"
systemd-cryptsetup attach test-volume "$IMAGE" - tpm2-device=auto,headless=1
systemd-cryptsetup detach test-volume