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