From 3a2c4d55e32ad65efebdb6de44eef3bfa08bb49d Mon Sep 17 00:00:00 2001 From: Kees Cook Date: Wed, 2 Sep 2026 15:31:14 -0700 Subject: treewide: refresh kmalloc_obj() conversions This is another run of the Coccinelle script for converting kmalloc() family of allocations to kmalloc_obj() via the existing rules in scripts/coccinelle/api/kmalloc_objs.cocci This catches both the set of kmalloc() uses added since the first kmalloc_obj() conversions in v7.0 and adds a large group missed in the first pass due to Coccinelle not interacting well with the cleanup.h scoped_...() family of macros[1]. I worked around this with spatch's "--macro-file" argument to a file with all the scoped_...() macros mapped to Coccinelle's YACFE_ITERATOR[2] as that was the closest viable control flow indicator I could find. Build tested allmodconfig on x86, arm64, arm, loongarch, mips, powerpc, riscv, and s390 with no new warnings. Link: https://lore.kernel.org/lkml/202609021314.8A9C0B8@keescook/ [1] Link: https://github.com/coccinelle/coccinelle/blob/master/standard.h [2] Signed-off-by: Kees Cook --- security/keys/trusted-keys/Kconfig | 82 +++ security/keys/trusted-keys/Makefile | 20 + security/keys/trusted-keys/tpm2key.asn1 | 11 + security/keys/trusted-keys/trusted_caam.c | 191 ++++++ security/keys/trusted-keys/trusted_core.c | 410 ++++++++++++ security/keys/trusted-keys/trusted_dcp.c | 363 +++++++++++ security/keys/trusted-keys/trusted_pkwm.c | 190 ++++++ security/keys/trusted-keys/trusted_tee.c | 287 +++++++++ security/keys/trusted-keys/trusted_tpm1.c | 998 ++++++++++++++++++++++++++++++ security/keys/trusted-keys/trusted_tpm2.c | 605 ++++++++++++++++++ 10 files changed, 3157 insertions(+) create mode 100644 security/keys/trusted-keys/Kconfig create mode 100644 security/keys/trusted-keys/Makefile create mode 100644 security/keys/trusted-keys/tpm2key.asn1 create mode 100644 security/keys/trusted-keys/trusted_caam.c create mode 100644 security/keys/trusted-keys/trusted_core.c create mode 100644 security/keys/trusted-keys/trusted_dcp.c create mode 100644 security/keys/trusted-keys/trusted_pkwm.c create mode 100644 security/keys/trusted-keys/trusted_tee.c create mode 100644 security/keys/trusted-keys/trusted_tpm1.c create mode 100644 security/keys/trusted-keys/trusted_tpm2.c (limited to 'security/keys/trusted-keys') diff --git a/security/keys/trusted-keys/Kconfig b/security/keys/trusted-keys/Kconfig new file mode 100644 index 000000000..e5a4a53ae --- /dev/null +++ b/security/keys/trusted-keys/Kconfig @@ -0,0 +1,82 @@ +config HAVE_TRUSTED_KEYS + bool + +config HAVE_TRUSTED_KEYS_DEBUG + bool + +config TRUSTED_KEYS_DEBUG + bool "Debug trusted keys" + depends on HAVE_TRUSTED_KEYS_DEBUG + default n + help + Trusted key backends and core code that support debug traces can + opt-in that feature here. Traces must only use debug level output, as + sensitive data may pass by. In the kernel-command line traces can be + enabled via trusted.dyndbg='+p'. + + SAFETY: Debug dumps are inactive at runtime until trusted.debug is set + to a true value on the kernel command-line. Use at your utmost + consideration when enabling this feature on a production build. The + general advice is not to do this. + +config TRUSTED_KEYS_TPM + bool "TPM-based trusted keys" + depends on TCG_TPM >= TRUSTED_KEYS + default y + select HAVE_TRUSTED_KEYS_DEBUG + select CRYPTO_HASH_INFO + select CRYPTO_LIB_SHA1 + select CRYPTO_LIB_UTILS + select ASN1_ENCODER + select OID_REGISTRY + select ASN1 + select HAVE_TRUSTED_KEYS + help + Enable use of the Trusted Platform Module (TPM) as trusted key + backend. Trusted keys are random number symmetric keys, + which will be generated and RSA-sealed by the TPM. + The TPM only unseals the keys, if the boot PCRs and other + criteria match. + +config TRUSTED_KEYS_TEE + bool "TEE-based trusted keys" + depends on TEE >= TRUSTED_KEYS + default y + select HAVE_TRUSTED_KEYS_DEBUG + select HAVE_TRUSTED_KEYS + help + Enable use of the Trusted Execution Environment (TEE) as trusted + key backend. + +config TRUSTED_KEYS_CAAM + bool "CAAM-based trusted keys" + depends on CRYPTO_DEV_FSL_CAAM_JR >= TRUSTED_KEYS + select CRYPTO_DEV_FSL_CAAM_BLOB_GEN + default y + select HAVE_TRUSTED_KEYS_DEBUG + select HAVE_TRUSTED_KEYS + help + Enable use of NXP's Cryptographic Accelerator and Assurance Module + (CAAM) as trusted key backend. + +config TRUSTED_KEYS_DCP + bool "DCP-based trusted keys" + depends on CRYPTO_DEV_MXS_DCP >= TRUSTED_KEYS + default y + select HAVE_TRUSTED_KEYS_DEBUG + select HAVE_TRUSTED_KEYS + help + Enable use of NXP's DCP (Data Co-Processor) as trusted key backend. + +config TRUSTED_KEYS_PKWM + bool "PKWM-based trusted keys" + depends on PSERIES_PLPKS >= TRUSTED_KEYS + default y + select HAVE_TRUSTED_KEYS_DEBUG + select HAVE_TRUSTED_KEYS + help + Enable use of IBM PowerVM Key Wrapping Module (PKWM) as a trusted key backend. + +if !HAVE_TRUSTED_KEYS + comment "No trust source selected!" +endif diff --git a/security/keys/trusted-keys/Makefile b/security/keys/trusted-keys/Makefile new file mode 100644 index 000000000..5fc053a21 --- /dev/null +++ b/security/keys/trusted-keys/Makefile @@ -0,0 +1,20 @@ +# SPDX-License-Identifier: GPL-2.0 +# +# Makefile for trusted keys +# + +obj-$(CONFIG_TRUSTED_KEYS) += trusted.o +trusted-y += trusted_core.o +trusted-$(CONFIG_TRUSTED_KEYS_TPM) += trusted_tpm1.o + +$(obj)/trusted_tpm2.o: $(obj)/tpm2key.asn1.h +trusted-$(CONFIG_TRUSTED_KEYS_TPM) += trusted_tpm2.o +trusted-$(CONFIG_TRUSTED_KEYS_TPM) += tpm2key.asn1.o + +trusted-$(CONFIG_TRUSTED_KEYS_TEE) += trusted_tee.o + +trusted-$(CONFIG_TRUSTED_KEYS_CAAM) += trusted_caam.o + +trusted-$(CONFIG_TRUSTED_KEYS_DCP) += trusted_dcp.o + +trusted-$(CONFIG_TRUSTED_KEYS_PKWM) += trusted_pkwm.o diff --git a/security/keys/trusted-keys/tpm2key.asn1 b/security/keys/trusted-keys/tpm2key.asn1 new file mode 100644 index 000000000..f57f869ad --- /dev/null +++ b/security/keys/trusted-keys/tpm2key.asn1 @@ -0,0 +1,11 @@ +--- +--- ASN.1 for TPM 2.0 keys +--- + +TPMKey ::= SEQUENCE { + type OBJECT IDENTIFIER ({tpm2_key_type}), + emptyAuth [0] EXPLICIT BOOLEAN OPTIONAL, + parent INTEGER ({tpm2_key_parent}), + pubkey OCTET STRING ({tpm2_key_pub}), + privkey OCTET STRING ({tpm2_key_priv}) + } diff --git a/security/keys/trusted-keys/trusted_caam.c b/security/keys/trusted-keys/trusted_caam.c new file mode 100644 index 000000000..6a33dbf2a --- /dev/null +++ b/security/keys/trusted-keys/trusted_caam.c @@ -0,0 +1,191 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (C) 2021 Pengutronix, Ahmad Fatoum + * Copyright 2025 NXP + */ + +#include +#include +#include +#include +#include +#include + +static struct caam_blob_priv *blobifier; + +#define KEYMOD "SECURE_KEY" + +static_assert(MAX_KEY_SIZE + CAAM_BLOB_OVERHEAD <= CAAM_BLOB_MAX_LEN); +static_assert(MAX_BLOB_SIZE <= CAAM_BLOB_MAX_LEN); + +enum { + opt_err, + opt_key_enc_algo, +}; + +static const match_table_t key_tokens = { + {opt_key_enc_algo, "key_enc_algo=%s"}, + {opt_err, NULL} +}; + +#ifdef CONFIG_TRUSTED_KEYS_DEBUG +static inline void dump_options(const struct caam_pkey_info *pkey_info) +{ + if (!trusted_debug) + return; + + pr_debug("key encryption algo %d\n", pkey_info->key_enc_algo); +} +#else +static inline void dump_options(const struct caam_pkey_info *pkey_info) +{ +} +#endif + +static int get_pkey_options(char *c, + struct caam_pkey_info *pkey_info) +{ + substring_t args[MAX_OPT_ARGS]; + unsigned long token_mask = 0; + u16 key_enc_algo; + char *p = c; + int token; + int res; + + if (!c) + return 0; + + while ((p = strsep(&c, " \t"))) { + if (*p == '\0' || *p == ' ' || *p == '\t') + continue; + token = match_token(p, key_tokens, args); + if (test_and_set_bit(token, &token_mask)) + return -EINVAL; + + switch (token) { + case opt_key_enc_algo: + res = kstrtou16(args[0].from, 16, &key_enc_algo); + if (res < 0) + return -EINVAL; + pkey_info->key_enc_algo = key_enc_algo; + break; + default: + return -EINVAL; + } + } + return 0; +} + +static bool is_key_pkey(char **datablob) +{ + char *c = NULL; + + do { + /* Second argument onwards, + * determine if tied to HW + */ + c = strsep(datablob, " \t"); + if (c && (strcmp(c, "pk") == 0)) + return true; + } while (c); + + return false; +} + +static int trusted_caam_seal(struct trusted_key_payload *p, char *datablob) +{ + int ret; + struct caam_blob_info info = { + .input = p->key, .input_len = p->key_len, + .output = p->blob, .output_len = MAX_BLOB_SIZE, + .key_mod = KEYMOD, .key_mod_len = sizeof(KEYMOD) - 1, + }; + + /* + * If it is to be treated as protected key, + * read next arguments too. + */ + if (is_key_pkey(&datablob)) { + info.pkey_info.plain_key_sz = p->key_len; + info.pkey_info.is_pkey = 1; + ret = get_pkey_options(datablob, &info.pkey_info); + if (ret < 0) + return 0; + dump_options(&info.pkey_info); + } + + ret = caam_encap_blob(blobifier, &info); + if (ret) + return ret; + + p->blob_len = info.output_len; + if (info.pkey_info.is_pkey) { + p->key_len = p->blob_len + sizeof(struct caam_pkey_info); + memcpy(p->key, &info.pkey_info, sizeof(struct caam_pkey_info)); + memcpy(p->key + sizeof(struct caam_pkey_info), p->blob, p->blob_len); + } + + return 0; +} + +static int trusted_caam_unseal(struct trusted_key_payload *p, char *datablob) +{ + int ret; + struct caam_blob_info info = { + .input = p->blob, .input_len = p->blob_len, + .output = p->key, .output_len = MAX_KEY_SIZE, + .key_mod = KEYMOD, .key_mod_len = sizeof(KEYMOD) - 1, + }; + + if (is_key_pkey(&datablob)) { + info.pkey_info.plain_key_sz = p->blob_len - CAAM_BLOB_OVERHEAD; + info.pkey_info.is_pkey = 1; + ret = get_pkey_options(datablob, &info.pkey_info); + if (ret < 0) + return 0; + dump_options(&info.pkey_info); + + p->key_len = p->blob_len + sizeof(struct caam_pkey_info); + memcpy(p->key, &info.pkey_info, sizeof(struct caam_pkey_info)); + memcpy(p->key + sizeof(struct caam_pkey_info), p->blob, p->blob_len); + + return 0; + } + + ret = caam_decap_blob(blobifier, &info); + if (ret) + return ret; + + p->key_len = info.output_len; + + return 0; +} + +static int trusted_caam_init(void) +{ + int ret; + + blobifier = caam_blob_gen_init(); + if (IS_ERR(blobifier)) + return PTR_ERR(blobifier); + + ret = register_key_type(&key_type_trusted); + if (ret) + caam_blob_gen_exit(blobifier); + + return ret; +} + +static void trusted_caam_exit(void) +{ + unregister_key_type(&key_type_trusted); + caam_blob_gen_exit(blobifier); +} + +struct trusted_key_ops trusted_key_caam_ops = { + .migratable = 0, /* non-migratable */ + .init = trusted_caam_init, + .seal = trusted_caam_seal, + .unseal = trusted_caam_unseal, + .exit = trusted_caam_exit, +}; diff --git a/security/keys/trusted-keys/trusted_core.c b/security/keys/trusted-keys/trusted_core.c new file mode 100644 index 000000000..0509d9955 --- /dev/null +++ b/security/keys/trusted-keys/trusted_core.c @@ -0,0 +1,410 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (C) 2010 IBM Corporation + * Copyright (c) 2019-2021, Linaro Limited + * + * See Documentation/security/keys/trusted-encrypted.rst + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static char *trusted_rng = "default"; +module_param_named(rng, trusted_rng, charp, 0); +MODULE_PARM_DESC(rng, "Select trusted key RNG"); + +#ifdef CONFIG_TRUSTED_KEYS_DEBUG +bool trusted_debug; +module_param_named(debug, trusted_debug, bool, 0); +MODULE_PARM_DESC(debug, "Enable trusted keys debug traces (default: 0)"); +#endif + +static char *trusted_key_source; +module_param_named(source, trusted_key_source, charp, 0); +MODULE_PARM_DESC(source, "Select trusted keys source (tpm, tee, caam, dcp or pkwm)"); + +static const struct trusted_key_source trusted_key_sources[] = { +#if defined(CONFIG_TRUSTED_KEYS_TPM) + { "tpm", &trusted_key_tpm_ops }, +#endif +#if defined(CONFIG_TRUSTED_KEYS_TEE) + { "tee", &trusted_key_tee_ops }, +#endif +#if defined(CONFIG_TRUSTED_KEYS_CAAM) + { "caam", &trusted_key_caam_ops }, +#endif +#if defined(CONFIG_TRUSTED_KEYS_DCP) + { "dcp", &dcp_trusted_key_ops }, +#endif +#if defined(CONFIG_TRUSTED_KEYS_PKWM) + { "pkwm", &pkwm_trusted_key_ops }, +#endif +}; + +DEFINE_STATIC_CALL_NULL(trusted_key_seal, *trusted_key_sources[0].ops->seal); +DEFINE_STATIC_CALL_NULL(trusted_key_unseal, + *trusted_key_sources[0].ops->unseal); +DEFINE_STATIC_CALL_NULL(trusted_key_get_random, + *trusted_key_sources[0].ops->get_random); +static void (*trusted_key_exit)(void); +static unsigned char migratable __ro_after_init; + +enum { + Opt_err, + Opt_new, Opt_load, Opt_update, +}; + +static const match_table_t key_tokens = { + {Opt_new, "new"}, + {Opt_load, "load"}, + {Opt_update, "update"}, + {Opt_err, NULL} +}; + +/* + * datablob_parse - parse the keyctl data and fill in the + * payload structure + * + * On success returns 0, otherwise -EINVAL. + */ +static int datablob_parse(char **datablob, struct trusted_key_payload *p) +{ + substring_t args[MAX_OPT_ARGS]; + long keylen; + int ret = -EINVAL; + int key_cmd; + char *c; + + /* main command */ + c = strsep(datablob, " \t"); + if (!c) + return -EINVAL; + key_cmd = match_token(c, key_tokens, args); + switch (key_cmd) { + case Opt_new: + /* first argument is key size */ + c = strsep(datablob, " \t"); + if (!c) + return -EINVAL; + ret = kstrtol(c, 10, &keylen); + if (ret < 0 || keylen < MIN_KEY_SIZE || keylen > MAX_KEY_SIZE) + return -EINVAL; + p->key_len = keylen; + ret = Opt_new; + break; + case Opt_load: + /* first argument is sealed blob */ + c = strsep(datablob, " \t"); + if (!c) + return -EINVAL; + p->blob_len = strlen(c) / 2; + if (p->blob_len > MAX_BLOB_SIZE) + return -EINVAL; + ret = hex2bin(p->blob, c, p->blob_len); + if (ret < 0) + return -EINVAL; + ret = Opt_load; + break; + case Opt_update: + ret = Opt_update; + break; + case Opt_err: + return -EINVAL; + } + return ret; +} + +static struct trusted_key_payload *trusted_payload_alloc(struct key *key) +{ + struct trusted_key_payload *p = NULL; + int ret; + + ret = key_payload_reserve(key, sizeof(*p)); + if (ret < 0) + goto err; + p = kzalloc_obj(*p); + if (!p) + goto err; + + p->migratable = migratable; +err: + return p; +} + +/* + * trusted_instantiate - create a new trusted key + * + * Unseal an existing trusted blob or, for a new key, get a + * random key, then seal and create a trusted key-type key, + * adding it to the specified keyring. + * + * On success, return 0. Otherwise return errno. + */ +static int trusted_instantiate(struct key *key, + struct key_preparsed_payload *prep) +{ + struct trusted_key_payload *payload = NULL; + size_t datalen = prep->datalen; + char *datablob, *orig_datablob; + int ret = 0; + int key_cmd; + size_t key_len; + + if (datalen == 0 || datalen > 32767 || !prep->data) + return -EINVAL; + + orig_datablob = datablob = kmalloc(datalen + 1, GFP_KERNEL); + if (!datablob) + return -ENOMEM; + memcpy(datablob, prep->data, datalen); + datablob[datalen] = '\0'; + + payload = trusted_payload_alloc(key); + if (!payload) { + ret = -ENOMEM; + goto out; + } + + key_cmd = datablob_parse(&datablob, payload); + if (key_cmd < 0) { + ret = key_cmd; + goto out; + } + + dump_payload(payload); + + switch (key_cmd) { + case Opt_load: + ret = static_call(trusted_key_unseal)(payload, datablob); + dump_payload(payload); + if (ret < 0) + pr_info("key_unseal failed (%d)\n", ret); + break; + case Opt_new: + key_len = payload->key_len; + ret = static_call(trusted_key_get_random)(payload->key, + key_len); + if (ret < 0) + goto out; + + if (ret != key_len) { + pr_info("key_create failed (%d)\n", ret); + ret = -EIO; + goto out; + } + + ret = static_call(trusted_key_seal)(payload, datablob); + if (ret < 0) + pr_info("key_seal failed (%d)\n", ret); + break; + default: + ret = -EINVAL; + } +out: + kfree_sensitive(orig_datablob); + if (!ret) + rcu_assign_keypointer(key, payload); + else + kfree_sensitive(payload); + return ret; +} + +static void trusted_rcu_free(struct rcu_head *rcu) +{ + struct trusted_key_payload *p; + + p = container_of(rcu, struct trusted_key_payload, rcu); + kfree_sensitive(p); +} + +/* + * trusted_update - reseal an existing key with new PCR values + */ +static int trusted_update(struct key *key, struct key_preparsed_payload *prep) +{ + struct trusted_key_payload *p; + struct trusted_key_payload *new_p; + size_t datalen = prep->datalen; + char *datablob, *orig_datablob; + int ret = 0; + + if (key_is_negative(key)) + return -ENOKEY; + p = key->payload.data[0]; + if (!p->migratable) + return -EPERM; + if (datalen == 0 || datalen > 32767 || !prep->data) + return -EINVAL; + + orig_datablob = datablob = kmalloc(datalen + 1, GFP_KERNEL); + if (!datablob) + return -ENOMEM; + + new_p = trusted_payload_alloc(key); + if (!new_p) { + ret = -ENOMEM; + goto out; + } + + memcpy(datablob, prep->data, datalen); + datablob[datalen] = '\0'; + ret = datablob_parse(&datablob, new_p); + if (ret != Opt_update) { + ret = -EINVAL; + kfree_sensitive(new_p); + goto out; + } + + /* copy old key values, and reseal with new pcrs */ + new_p->migratable = p->migratable; + new_p->key_len = p->key_len; + memcpy(new_p->key, p->key, p->key_len); + dump_payload(p); + dump_payload(new_p); + + ret = static_call(trusted_key_seal)(new_p, datablob); + if (ret < 0) { + pr_info("key_seal failed (%d)\n", ret); + kfree_sensitive(new_p); + goto out; + } + + rcu_assign_keypointer(key, new_p); + call_rcu(&p->rcu, trusted_rcu_free); +out: + kfree_sensitive(orig_datablob); + return ret; +} + +/* + * trusted_read - copy the sealed blob data to userspace in hex. + * On success, return to userspace the trusted key datablob size. + */ +static long trusted_read(const struct key *key, char *buffer, + size_t buflen) +{ + const struct trusted_key_payload *p; + char *bufp; + int i; + + p = dereference_key_locked(key); + if (!p) + return -EINVAL; + + if (buffer && buflen >= 2 * p->blob_len) { + bufp = buffer; + for (i = 0; i < p->blob_len; i++) + bufp = hex_byte_pack(bufp, p->blob[i]); + } + return 2 * p->blob_len; +} + +/* + * trusted_destroy - clear and free the key's payload + */ +static void trusted_destroy(struct key *key) +{ + kfree_sensitive(key->payload.data[0]); +} + +struct key_type key_type_trusted = { + .name = "trusted", + .instantiate = trusted_instantiate, + .update = trusted_update, + .destroy = trusted_destroy, + .describe = user_describe, + .read = trusted_read, +}; +EXPORT_SYMBOL_GPL(key_type_trusted); + +static int kernel_get_random(unsigned char *key, size_t key_len) +{ + return get_random_bytes_wait(key, key_len) ?: key_len; +} + +static int __init init_trusted(void) +{ + int (*get_random)(unsigned char *key, size_t key_len); + int i, ret = 0; + + for (i = 0; i < ARRAY_SIZE(trusted_key_sources); i++) { + if (trusted_key_source && + strncmp(trusted_key_source, trusted_key_sources[i].name, + strlen(trusted_key_sources[i].name))) + continue; + + /* + * We always support trusted.rng="kernel" and "default" as + * well as trusted.rng=$trusted.source if the trust source + * defines its own get_random callback. + */ + get_random = trusted_key_sources[i].ops->get_random; + if (trusted_rng && strcmp(trusted_rng, "default")) { + if (!strcmp(trusted_rng, "kernel")) { + get_random = kernel_get_random; + } else if (strcmp(trusted_rng, trusted_key_sources[i].name) || + !get_random) { + pr_warn("Unsupported RNG. Supported: kernel"); + if (get_random) + pr_cont(", %s", trusted_key_sources[i].name); + pr_cont(", default\n"); + return -EINVAL; + } + } + + if (!get_random) + get_random = kernel_get_random; + + ret = trusted_key_sources[i].ops->init(); + if (!ret) { + static_call_update(trusted_key_seal, trusted_key_sources[i].ops->seal); + static_call_update(trusted_key_unseal, trusted_key_sources[i].ops->unseal); + static_call_update(trusted_key_get_random, get_random); + + trusted_key_exit = trusted_key_sources[i].ops->exit; + migratable = trusted_key_sources[i].ops->migratable; + } + + if (!ret || ret != -ENODEV) + break; + } + + /* + * encrypted_keys.ko depends on successful load of this module even if + * trusted key implementation is not found. + */ + if (ret == -ENODEV) + return 0; + + return ret; +} + +static void __exit cleanup_trusted(void) +{ + if (trusted_key_exit) + (*trusted_key_exit)(); +} + +late_initcall(init_trusted); +module_exit(cleanup_trusted); + +MODULE_DESCRIPTION("Trusted Key type"); +MODULE_LICENSE("GPL"); diff --git a/security/keys/trusted-keys/trusted_dcp.c b/security/keys/trusted-keys/trusted_dcp.c new file mode 100644 index 000000000..c078adebe --- /dev/null +++ b/security/keys/trusted-keys/trusted_dcp.c @@ -0,0 +1,363 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (C) 2021 sigma star gmbh + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define DCP_BLOB_VERSION 1 +#define DCP_BLOB_AUTHLEN 16 + +/** + * DOC: dcp blob format + * + * The Data Co-Processor (DCP) provides hardware-bound AES keys using its + * AES encryption engine only. It does not provide direct key sealing/unsealing. + * To make DCP hardware encryption keys usable as trust source, we define + * our own custom format that uses a hardware-bound key to secure the sealing + * key stored in the key blob. + * + * Whenever a new trusted key using DCP is generated, we generate a random 128-bit + * blob encryption key (BEK) and 128-bit nonce. The BEK and nonce are used to + * encrypt the trusted key payload using AES-128-GCM. + * + * The BEK itself is encrypted using the hardware-bound key using the DCP's AES + * encryption engine with AES-128-ECB. The encrypted BEK, generated nonce, + * BEK-encrypted payload and authentication tag make up the blob format together + * with a version number, payload length and authentication tag. + */ + +/** + * struct dcp_blob_fmt - DCP BLOB format. + * + * @fmt_version: Format version, currently being %1. + * @blob_key: Random AES 128 key which is used to encrypt @payload, + * @blob_key itself is encrypted with OTP or UNIQUE device key in + * AES-128-ECB mode by DCP. + * @nonce: Random nonce used for @payload encryption. + * @payload_len: Length of the plain text @payload. + * @payload: The payload itself, encrypted using AES-128-GCM and @blob_key, + * GCM auth tag of size DCP_BLOB_AUTHLEN is attached at the end of it. + * + * The total size of a DCP BLOB is sizeof(struct dcp_blob_fmt) + @payload_len + + * DCP_BLOB_AUTHLEN. + */ +struct dcp_blob_fmt { + __u8 fmt_version; + __u8 blob_key[AES_KEYSIZE_128]; + __u8 nonce[AES_KEYSIZE_128]; + __le32 payload_len; + __u8 payload[]; +} __packed; + +static bool use_otp_key; +module_param_named(dcp_use_otp_key, use_otp_key, bool, 0); +MODULE_PARM_DESC(dcp_use_otp_key, "Use OTP instead of UNIQUE key for sealing"); + +static bool skip_zk_test; +module_param_named(dcp_skip_zk_test, skip_zk_test, bool, 0); +MODULE_PARM_DESC(dcp_skip_zk_test, "Don't test whether device keys are zero'ed"); + +static size_t calc_blob_len(unsigned int payload_len) +{ + return sizeof(struct dcp_blob_fmt) + payload_len + DCP_BLOB_AUTHLEN; +} + +static int do_dcp_crypto(u8 *in, u8 *out, bool do_encrypt) +{ + struct skcipher_request *req = NULL; + struct scatterlist src_sg, dst_sg; + struct crypto_skcipher *tfm; + u8 paes_key[DCP_PAES_KEYSIZE]; + DECLARE_CRYPTO_WAIT(wait); + int res = 0; + + if (use_otp_key) + paes_key[0] = DCP_PAES_KEY_OTP; + else + paes_key[0] = DCP_PAES_KEY_UNIQUE; + + tfm = crypto_alloc_skcipher("ecb-paes-dcp", CRYPTO_ALG_INTERNAL, + CRYPTO_ALG_INTERNAL); + if (IS_ERR(tfm)) { + res = PTR_ERR(tfm); + tfm = NULL; + goto out; + } + + req = skcipher_request_alloc(tfm, GFP_NOFS); + if (!req) { + res = -ENOMEM; + goto out; + } + + skcipher_request_set_callback(req, CRYPTO_TFM_REQ_MAY_BACKLOG | + CRYPTO_TFM_REQ_MAY_SLEEP, + crypto_req_done, &wait); + res = crypto_skcipher_setkey(tfm, paes_key, sizeof(paes_key)); + if (res < 0) + goto out; + + sg_init_one(&src_sg, in, AES_KEYSIZE_128); + sg_init_one(&dst_sg, out, AES_KEYSIZE_128); + skcipher_request_set_crypt(req, &src_sg, &dst_sg, AES_KEYSIZE_128, + NULL); + + if (do_encrypt) + res = crypto_wait_req(crypto_skcipher_encrypt(req), &wait); + else + res = crypto_wait_req(crypto_skcipher_decrypt(req), &wait); + +out: + skcipher_request_free(req); + crypto_free_skcipher(tfm); + + return res; +} + +static int do_aead_crypto(u8 *in, u8 *out, size_t len, u8 *key, u8 *nonce, + bool do_encrypt) +{ + struct aead_request *aead_req = NULL; + struct scatterlist src_sg, dst_sg; + struct crypto_aead *aead; + int ret; + DECLARE_CRYPTO_WAIT(wait); + + aead = crypto_alloc_aead("gcm(aes)", 0, CRYPTO_ALG_ASYNC); + if (IS_ERR(aead)) { + ret = PTR_ERR(aead); + goto out; + } + + ret = crypto_aead_setauthsize(aead, DCP_BLOB_AUTHLEN); + if (ret < 0) { + pr_err("Can't set crypto auth tag len: %d\n", ret); + goto free_aead; + } + + aead_req = aead_request_alloc(aead, GFP_KERNEL); + if (!aead_req) { + ret = -ENOMEM; + goto free_aead; + } + + sg_init_one(&src_sg, in, len); + if (do_encrypt) { + /* + * If we encrypt our buffer has extra space for the auth tag. + */ + sg_init_one(&dst_sg, out, len + DCP_BLOB_AUTHLEN); + } else { + sg_init_one(&dst_sg, out, len); + } + + aead_request_set_crypt(aead_req, &src_sg, &dst_sg, len, nonce); + aead_request_set_callback(aead_req, CRYPTO_TFM_REQ_MAY_SLEEP, + crypto_req_done, &wait); + aead_request_set_ad(aead_req, 0); + + if (crypto_aead_setkey(aead, key, AES_KEYSIZE_128)) { + pr_err("Can't set crypto AEAD key\n"); + ret = -EINVAL; + goto free_req; + } + + if (do_encrypt) + ret = crypto_wait_req(crypto_aead_encrypt(aead_req), &wait); + else + ret = crypto_wait_req(crypto_aead_decrypt(aead_req), &wait); + +free_req: + aead_request_free(aead_req); +free_aead: + crypto_free_aead(aead); +out: + return ret; +} + +static int decrypt_blob_key(u8 *encrypted_key, u8 *plain_key) +{ + return do_dcp_crypto(encrypted_key, plain_key, false); +} + +static int encrypt_blob_key(u8 *plain_key, u8 *encrypted_key) +{ + return do_dcp_crypto(plain_key, encrypted_key, true); +} + +static int trusted_dcp_seal(struct trusted_key_payload *p, char *datablob) +{ + struct dcp_blob_fmt *b = (struct dcp_blob_fmt *)p->blob; + size_t blen; + int ret; + u8 *plain_blob_key; + + blen = calc_blob_len(p->key_len); + if (blen > MAX_BLOB_SIZE) + return -E2BIG; + + plain_blob_key = kmalloc(AES_KEYSIZE_128, GFP_KERNEL); + if (!plain_blob_key) + return -ENOMEM; + + b->fmt_version = DCP_BLOB_VERSION; + get_random_bytes(b->nonce, AES_KEYSIZE_128); + get_random_bytes(plain_blob_key, AES_KEYSIZE_128); + + ret = do_aead_crypto(p->key, b->payload, p->key_len, plain_blob_key, + b->nonce, true); + if (ret) { + pr_err("Unable to encrypt blob payload: %i\n", ret); + goto out; + } + + ret = encrypt_blob_key(plain_blob_key, b->blob_key); + if (ret) { + pr_err("Unable to encrypt blob key: %i\n", ret); + goto out; + } + + put_unaligned_le32(p->key_len, &b->payload_len); + p->blob_len = blen; + ret = 0; + +out: + memzero_explicit(plain_blob_key, AES_KEYSIZE_128); + kfree(plain_blob_key); + + return ret; +} + +static int trusted_dcp_unseal(struct trusted_key_payload *p, char *datablob) +{ + struct dcp_blob_fmt *b = (struct dcp_blob_fmt *)p->blob; + size_t blen; + int ret; + u8 *plain_blob_key = NULL; + + if (b->fmt_version != DCP_BLOB_VERSION) { + pr_err("DCP blob has bad version: %i, expected %i\n", + b->fmt_version, DCP_BLOB_VERSION); + ret = -EINVAL; + goto out; + } + + p->key_len = le32_to_cpu(b->payload_len); + if (p->key_len < MIN_KEY_SIZE || p->key_len > MAX_KEY_SIZE) { + ret = -EINVAL; + goto out; + } + + blen = calc_blob_len(p->key_len); + if (blen != p->blob_len) { + pr_err("DCP blob has bad length: %zu != %u\n", blen, + p->blob_len); + ret = -EINVAL; + goto out; + } + + plain_blob_key = kmalloc(AES_KEYSIZE_128, GFP_KERNEL); + if (!plain_blob_key) { + ret = -ENOMEM; + goto out; + } + + ret = decrypt_blob_key(b->blob_key, plain_blob_key); + if (ret) { + pr_err("Unable to decrypt blob key: %i\n", ret); + goto out; + } + + ret = do_aead_crypto(b->payload, p->key, p->key_len + DCP_BLOB_AUTHLEN, + plain_blob_key, b->nonce, false); + if (ret) { + pr_err("Unwrap of DCP payload failed: %i\n", ret); + goto out; + } + + ret = 0; +out: + if (plain_blob_key) { + memzero_explicit(plain_blob_key, AES_KEYSIZE_128); + kfree(plain_blob_key); + } + + return ret; +} + +static int test_for_zero_key(void) +{ + /* + * Encrypting a plaintext of all 0x55 bytes will yield + * this ciphertext in case the DCP test key is used. + */ + static const u8 bad[] = {0x9a, 0xda, 0xe0, 0x54, 0xf6, 0x3d, 0xfa, 0xff, + 0x5e, 0xa1, 0x8e, 0x45, 0xed, 0xf6, 0xea, 0x6f}; + void *buf = NULL; + int ret = 0; + + if (skip_zk_test) + goto out; + + buf = kmalloc(AES_BLOCK_SIZE, GFP_KERNEL); + if (!buf) { + ret = -ENOMEM; + goto out; + } + + memset(buf, 0x55, AES_BLOCK_SIZE); + + ret = do_dcp_crypto(buf, buf, true); + if (ret) + goto out; + + if (memcmp(buf, bad, AES_BLOCK_SIZE) == 0) { + pr_warn("Device neither in secure nor trusted mode!\n"); + ret = -EINVAL; + } +out: + kfree(buf); + return ret; +} + +static int trusted_dcp_init(void) +{ + int ret; + + if (use_otp_key) + pr_info("Using DCP OTP key\n"); + + ret = test_for_zero_key(); + if (ret) { + pr_warn("Test for zero'ed keys failed: %i\n", ret); + + return -EINVAL; + } + + return register_key_type(&key_type_trusted); +} + +static void trusted_dcp_exit(void) +{ + unregister_key_type(&key_type_trusted); +} + +struct trusted_key_ops dcp_trusted_key_ops = { + .exit = trusted_dcp_exit, + .init = trusted_dcp_init, + .seal = trusted_dcp_seal, + .unseal = trusted_dcp_unseal, + .migratable = 0, +}; diff --git a/security/keys/trusted-keys/trusted_pkwm.c b/security/keys/trusted-keys/trusted_pkwm.c new file mode 100644 index 000000000..bf42c6679 --- /dev/null +++ b/security/keys/trusted-keys/trusted_pkwm.c @@ -0,0 +1,190 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (C) 2025 IBM Corporation, Srish Srinivasan + */ + +#include +#include +#include +#include +#include +#include + +enum { + Opt_err, + Opt_wrap_flags, +}; + +static const match_table_t key_tokens = { + {Opt_wrap_flags, "wrap_flags=%s"}, + {Opt_err, NULL} +}; + +static int getoptions(char *datablob, struct trusted_key_options *opt) +{ + substring_t args[MAX_OPT_ARGS]; + char *p = datablob; + int token; + int res; + u16 wrap_flags; + unsigned long token_mask = 0; + struct trusted_pkwm_options *pkwm; + + if (!datablob) + return 0; + + pkwm = opt->private; + + while ((p = strsep(&datablob, " \t"))) { + if (*p == '\0' || *p == ' ' || *p == '\t') + continue; + + token = match_token(p, key_tokens, args); + if (test_and_set_bit(token, &token_mask)) + return -EINVAL; + + switch (token) { + case Opt_wrap_flags: + res = kstrtou16(args[0].from, 16, &wrap_flags); + if (res < 0 || wrap_flags > 2) + return -EINVAL; + pkwm->wrap_flags = wrap_flags; + break; + default: + return -EINVAL; + } + } + return 0; +} + +static struct trusted_key_options *trusted_options_alloc(void) +{ + struct trusted_key_options *options; + struct trusted_pkwm_options *pkwm; + + options = kzalloc_obj(*options); + + if (options) { + pkwm = kzalloc_obj(*pkwm); + + if (!pkwm) { + kfree_sensitive(options); + options = NULL; + } else { + options->private = pkwm; + } + } + + return options; +} + +static int trusted_pkwm_seal(struct trusted_key_payload *p, char *datablob) +{ + struct trusted_key_options *options = NULL; + struct trusted_pkwm_options *pkwm = NULL; + u8 *input_buf, *output_buf; + u32 output_len, input_len; + int rc; + + options = trusted_options_alloc(); + + if (!options) + return -ENOMEM; + + rc = getoptions(datablob, options); + if (rc < 0) + goto out; + dump_options(options); + + input_len = p->key_len; + input_buf = kmalloc(ALIGN(input_len, 4096), GFP_KERNEL); + if (!input_buf) { + pr_err("Input buffer allocation failed. Returning -ENOMEM."); + rc = -ENOMEM; + goto out; + } + + memcpy(input_buf, p->key, p->key_len); + + pkwm = options->private; + + rc = plpks_wrap_object(&input_buf, input_len, pkwm->wrap_flags, + &output_buf, &output_len); + if (!rc) { + memcpy(p->blob, output_buf, output_len); + p->blob_len = output_len; + dump_payload(p); + } else { + pr_err("Wrapping of payload key failed: %d\n", rc); + } + + kfree(input_buf); + kfree(output_buf); + +out: + kfree_sensitive(options->private); + kfree_sensitive(options); + return rc; +} + +static int trusted_pkwm_unseal(struct trusted_key_payload *p, char *datablob) +{ + u8 *input_buf, *output_buf; + u32 input_len, output_len; + int rc; + + input_len = p->blob_len; + input_buf = kmalloc(ALIGN(input_len, 4096), GFP_KERNEL); + if (!input_buf) { + pr_err("Input buffer allocation failed. Returning -ENOMEM."); + return -ENOMEM; + } + + memcpy(input_buf, p->blob, p->blob_len); + + rc = plpks_unwrap_object(&input_buf, input_len, &output_buf, + &output_len); + if (!rc) { + memcpy(p->key, output_buf, output_len); + p->key_len = output_len; + dump_payload(p); + } else { + pr_err("Unwrapping of payload failed: %d\n", rc); + } + + kfree(input_buf); + kfree(output_buf); + + return rc; +} + +static int trusted_pkwm_init(void) +{ + int ret; + + if (!plpks_wrapping_is_supported()) { + pr_err("H_PKS_WRAP_OBJECT interface not supported\n"); + return -ENODEV; + } + + ret = plpks_gen_wrapping_key(); + if (ret) { + pr_err("Failed to generate default wrapping key\n"); + return -EINVAL; + } + + return register_key_type(&key_type_trusted); +} + +static void trusted_pkwm_exit(void) +{ + unregister_key_type(&key_type_trusted); +} + +struct trusted_key_ops pkwm_trusted_key_ops = { + .migratable = 0, /* non-migratable */ + .init = trusted_pkwm_init, + .seal = trusted_pkwm_seal, + .unseal = trusted_pkwm_unseal, + .exit = trusted_pkwm_exit, +}; diff --git a/security/keys/trusted-keys/trusted_tee.c b/security/keys/trusted-keys/trusted_tee.c new file mode 100644 index 000000000..6e465c8be --- /dev/null +++ b/security/keys/trusted-keys/trusted_tee.c @@ -0,0 +1,287 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2019-2021 Linaro Ltd. + * + * Author: + * Sumit Garg + */ + +#include +#include +#include +#include +#include +#include +#include + +#include + +#define DRIVER_NAME "trusted-key-tee" + +/* + * Get random data for symmetric key + * + * [out] memref[0] Random data + */ +#define TA_CMD_GET_RANDOM 0x0 + +/* + * Seal trusted key using hardware unique key + * + * [in] memref[0] Plain key + * [out] memref[1] Sealed key datablob + */ +#define TA_CMD_SEAL 0x1 + +/* + * Unseal trusted key using hardware unique key + * + * [in] memref[0] Sealed key datablob + * [out] memref[1] Plain key + */ +#define TA_CMD_UNSEAL 0x2 + +/** + * struct trusted_key_tee_private - TEE Trusted key private data + * @dev: TEE based Trusted key device. + * @ctx: TEE context handler. + * @session_id: Trusted key TA session identifier. + * @shm_pool: Memory pool shared with TEE device. + */ +struct trusted_key_tee_private { + struct device *dev; + struct tee_context *ctx; + u32 session_id; + struct tee_shm *shm_pool; +}; + +static struct trusted_key_tee_private pvt_data; + +/* + * Have the TEE seal(encrypt) the symmetric key + */ +static int trusted_tee_seal(struct trusted_key_payload *p, char *datablob) +{ + int ret; + struct tee_ioctl_invoke_arg inv_arg; + struct tee_param param[4]; + struct tee_shm *reg_shm = NULL; + + memset(&inv_arg, 0, sizeof(inv_arg)); + memset(¶m, 0, sizeof(param)); + + reg_shm = tee_shm_register_kernel_buf(pvt_data.ctx, p->key, + sizeof(p->key) + sizeof(p->blob)); + if (IS_ERR(reg_shm)) { + dev_err(pvt_data.dev, "shm register failed\n"); + return PTR_ERR(reg_shm); + } + + inv_arg.func = TA_CMD_SEAL; + inv_arg.session = pvt_data.session_id; + inv_arg.num_params = 4; + + param[0].attr = TEE_IOCTL_PARAM_ATTR_TYPE_MEMREF_INPUT; + param[0].u.memref.shm = reg_shm; + param[0].u.memref.size = p->key_len; + param[0].u.memref.shm_offs = 0; + param[1].attr = TEE_IOCTL_PARAM_ATTR_TYPE_MEMREF_OUTPUT; + param[1].u.memref.shm = reg_shm; + param[1].u.memref.size = sizeof(p->blob); + param[1].u.memref.shm_offs = sizeof(p->key); + + ret = tee_client_invoke_func(pvt_data.ctx, &inv_arg, param); + if ((ret < 0) || (inv_arg.ret != 0)) { + dev_err(pvt_data.dev, "TA_CMD_SEAL invoke err: %x\n", + inv_arg.ret); + ret = -EFAULT; + } else { + p->blob_len = param[1].u.memref.size; + } + + tee_shm_free(reg_shm); + + return ret; +} + +/* + * Have the TEE unseal(decrypt) the symmetric key + */ +static int trusted_tee_unseal(struct trusted_key_payload *p, char *datablob) +{ + int ret; + struct tee_ioctl_invoke_arg inv_arg; + struct tee_param param[4]; + struct tee_shm *reg_shm = NULL; + + memset(&inv_arg, 0, sizeof(inv_arg)); + memset(¶m, 0, sizeof(param)); + + reg_shm = tee_shm_register_kernel_buf(pvt_data.ctx, p->key, + sizeof(p->key) + sizeof(p->blob)); + if (IS_ERR(reg_shm)) { + dev_err(pvt_data.dev, "shm register failed\n"); + return PTR_ERR(reg_shm); + } + + inv_arg.func = TA_CMD_UNSEAL; + inv_arg.session = pvt_data.session_id; + inv_arg.num_params = 4; + + param[0].attr = TEE_IOCTL_PARAM_ATTR_TYPE_MEMREF_INPUT; + param[0].u.memref.shm = reg_shm; + param[0].u.memref.size = p->blob_len; + param[0].u.memref.shm_offs = sizeof(p->key); + param[1].attr = TEE_IOCTL_PARAM_ATTR_TYPE_MEMREF_OUTPUT; + param[1].u.memref.shm = reg_shm; + param[1].u.memref.size = sizeof(p->key); + param[1].u.memref.shm_offs = 0; + + ret = tee_client_invoke_func(pvt_data.ctx, &inv_arg, param); + if ((ret < 0) || (inv_arg.ret != 0)) { + dev_err(pvt_data.dev, "TA_CMD_UNSEAL invoke err: %x\n", + inv_arg.ret); + ret = -EFAULT; + } else { + p->key_len = param[1].u.memref.size; + } + + tee_shm_free(reg_shm); + + return ret; +} + +/* + * Have the TEE generate random symmetric key + */ +static int trusted_tee_get_random(unsigned char *key, size_t key_len) +{ + int ret; + struct tee_ioctl_invoke_arg inv_arg; + struct tee_param param[4]; + struct tee_shm *reg_shm = NULL; + + memset(&inv_arg, 0, sizeof(inv_arg)); + memset(¶m, 0, sizeof(param)); + + reg_shm = tee_shm_register_kernel_buf(pvt_data.ctx, key, key_len); + if (IS_ERR(reg_shm)) { + dev_err(pvt_data.dev, "key shm register failed\n"); + return PTR_ERR(reg_shm); + } + + inv_arg.func = TA_CMD_GET_RANDOM; + inv_arg.session = pvt_data.session_id; + inv_arg.num_params = 4; + + param[0].attr = TEE_IOCTL_PARAM_ATTR_TYPE_MEMREF_OUTPUT; + param[0].u.memref.shm = reg_shm; + param[0].u.memref.size = key_len; + param[0].u.memref.shm_offs = 0; + + ret = tee_client_invoke_func(pvt_data.ctx, &inv_arg, param); + if ((ret < 0) || (inv_arg.ret != 0)) { + dev_err(pvt_data.dev, "TA_CMD_GET_RANDOM invoke err: %x\n", + inv_arg.ret); + ret = -EFAULT; + } else { + ret = param[0].u.memref.size; + } + + tee_shm_free(reg_shm); + + return ret; +} + +static int optee_ctx_match(struct tee_ioctl_version_data *ver, const void *data) +{ + if (ver->impl_id == TEE_IMPL_ID_OPTEE && + ver->gen_caps & TEE_GEN_CAP_REG_MEM) + return 1; + else + return 0; +} + +static int trusted_key_probe(struct tee_client_device *rng_device) +{ + struct device *dev = &rng_device->dev; + int ret; + struct tee_ioctl_open_session_arg sess_arg; + + memset(&sess_arg, 0, sizeof(sess_arg)); + + pvt_data.ctx = tee_client_open_context(NULL, optee_ctx_match, NULL, + NULL); + if (IS_ERR(pvt_data.ctx)) + return -ENODEV; + + memcpy(sess_arg.uuid, rng_device->id.uuid.b, TEE_IOCTL_UUID_LEN); + sess_arg.clnt_login = TEE_IOCTL_LOGIN_REE_KERNEL; + sess_arg.num_params = 0; + + ret = tee_client_open_session(pvt_data.ctx, &sess_arg, NULL); + if ((ret < 0) || (sess_arg.ret != 0)) { + dev_err(dev, "tee_client_open_session failed, err: %x\n", + sess_arg.ret); + ret = -EINVAL; + goto out_ctx; + } + pvt_data.session_id = sess_arg.session; + + ret = register_key_type(&key_type_trusted); + if (ret < 0) + goto out_sess; + + pvt_data.dev = dev; + + return 0; + +out_sess: + tee_client_close_session(pvt_data.ctx, pvt_data.session_id); +out_ctx: + tee_client_close_context(pvt_data.ctx); + + return ret; +} + +static void trusted_key_remove(struct tee_client_device *dev) +{ + unregister_key_type(&key_type_trusted); + tee_client_close_session(pvt_data.ctx, pvt_data.session_id); + tee_client_close_context(pvt_data.ctx); +} + +static const struct tee_client_device_id trusted_key_id_table[] = { + {UUID_INIT(0xf04a0fe7, 0x1f5d, 0x4b9b, + 0xab, 0xf7, 0x61, 0x9b, 0x85, 0xb4, 0xce, 0x8c)}, + {} +}; +MODULE_DEVICE_TABLE(tee, trusted_key_id_table); + +static struct tee_client_driver trusted_key_driver = { + .probe = trusted_key_probe, + .remove = trusted_key_remove, + .id_table = trusted_key_id_table, + .driver = { + .name = DRIVER_NAME, + }, +}; + +static int trusted_tee_init(void) +{ + return tee_client_driver_register(&trusted_key_driver); +} + +static void trusted_tee_exit(void) +{ + tee_client_driver_unregister(&trusted_key_driver); +} + +struct trusted_key_ops trusted_key_tee_ops = { + .migratable = 0, /* non-migratable */ + .init = trusted_tee_init, + .seal = trusted_tee_seal, + .unseal = trusted_tee_unseal, + .get_random = trusted_tee_get_random, + .exit = trusted_tee_exit, +}; diff --git a/security/keys/trusted-keys/trusted_tpm1.c b/security/keys/trusted-keys/trusted_tpm1.c new file mode 100644 index 000000000..bf0bf7f36 --- /dev/null +++ b/security/keys/trusted-keys/trusted_tpm1.c @@ -0,0 +1,998 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (C) 2010 IBM Corporation + * Copyright (c) 2019-2021, Linaro Limited + * + * See Documentation/security/keys/trusted-encrypted.rst + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +static struct tpm_chip *chip; +static struct tpm_digest *digests; + +/* implementation specific TPM constants */ +#define TPM_SIZE_OFFSET 2 +#define TPM_RETURN_OFFSET 6 +#define TPM_DATA_OFFSET 10 + +#define LOAD32(buffer, offset) (ntohl(*(uint32_t *)&buffer[offset])) +#define LOAD32N(buffer, offset) (*(uint32_t *)&buffer[offset]) +#define LOAD16(buffer, offset) (ntohs(*(uint16_t *)&buffer[offset])) + +struct osapsess { + uint32_t handle; + unsigned char secret[SHA1_DIGEST_SIZE]; + unsigned char enonce[TPM_NONCE_SIZE]; +}; + +/* discrete values, but have to store in uint16_t for TPM use */ +enum { + SEAL_keytype = 1, + SRK_keytype = 4 +}; + +#ifdef CONFIG_TRUSTED_KEYS_DEBUG +static inline void dump_options(struct trusted_key_options *o) +{ + if (!trusted_debug) + return; + + pr_debug("sealing key type %d\n", o->keytype); + pr_debug("sealing key handle %0X\n", o->keyhandle); + pr_debug("pcrlock %d\n", o->pcrlock); + pr_debug("pcrinfo %d\n", o->pcrinfo_len); + print_hex_dump_debug("pcrinfo ", DUMP_PREFIX_NONE, + 16, 1, o->pcrinfo, o->pcrinfo_len, 0); +} + +static inline void dump_sess(struct osapsess *s) +{ + if (!trusted_debug) + return; + + print_hex_dump_debug("trusted-key: handle ", DUMP_PREFIX_NONE, + 16, 1, &s->handle, 4, 0); + pr_debug("secret:\n"); + print_hex_dump_debug("", DUMP_PREFIX_NONE, + 16, 1, &s->secret, SHA1_DIGEST_SIZE, 0); + pr_debug("trusted-key: enonce:\n"); + print_hex_dump_debug("", DUMP_PREFIX_NONE, + 16, 1, &s->enonce, SHA1_DIGEST_SIZE, 0); +} + +static inline void dump_tpm_buf(unsigned char *buf) +{ + int len; + + if (!trusted_debug) + return; + pr_debug("\ntpm buffer\n"); + len = LOAD32(buf, TPM_SIZE_OFFSET); + print_hex_dump_debug("", DUMP_PREFIX_NONE, 16, 1, buf, len, 0); +} +#else +static inline void dump_options(struct trusted_key_options *o) +{ +} + +static inline void dump_sess(struct osapsess *s) +{ +} + +static inline void dump_tpm_buf(unsigned char *buf) +{ +} +#endif + +static int TSS_rawhmac(unsigned char *digest, const unsigned char *key, + unsigned int keylen, ...) +{ + struct hmac_sha1_ctx hmac_ctx; + va_list argp; + unsigned int dlen; + unsigned char *data; + int ret = 0; + + hmac_sha1_init_usingrawkey(&hmac_ctx, key, keylen); + + va_start(argp, keylen); + for (;;) { + dlen = va_arg(argp, unsigned int); + if (dlen == 0) + break; + data = va_arg(argp, unsigned char *); + if (data == NULL) { + ret = -EINVAL; + break; + } + hmac_sha1_update(&hmac_ctx, data, dlen); + } + va_end(argp); + if (!ret) + hmac_sha1_final(&hmac_ctx, digest); + return ret; +} + +/* + * calculate authorization info fields to send to TPM + */ +static int TSS_authhmac(unsigned char *digest, const unsigned char *key, + unsigned int keylen, unsigned char *h1, + unsigned char *h2, unsigned int h3, ...) +{ + unsigned char paramdigest[SHA1_DIGEST_SIZE]; + struct sha1_ctx sha_ctx; + unsigned int dlen; + unsigned char *data; + unsigned char c; + int ret = 0; + va_list argp; + + if (!chip) + return -ENODEV; + + c = !!h3; + sha1_init(&sha_ctx); + va_start(argp, h3); + for (;;) { + dlen = va_arg(argp, unsigned int); + if (dlen == 0) + break; + data = va_arg(argp, unsigned char *); + if (!data) { + ret = -EINVAL; + break; + } + sha1_update(&sha_ctx, data, dlen); + } + va_end(argp); + if (!ret) + sha1_final(&sha_ctx, paramdigest); + if (!ret) + ret = TSS_rawhmac(digest, key, keylen, SHA1_DIGEST_SIZE, + paramdigest, TPM_NONCE_SIZE, h1, + TPM_NONCE_SIZE, h2, 1, &c, 0, 0); + return ret; +} + +/* + * verify the AUTH1_COMMAND (Seal) result from TPM + */ +static int TSS_checkhmac1(unsigned char *buffer, + const uint32_t command, + const unsigned char *ononce, + const unsigned char *key, + unsigned int keylen, ...) +{ + uint32_t bufsize; + uint16_t tag; + uint32_t ordinal; + uint32_t result; + unsigned char *enonce; + unsigned char *continueflag; + unsigned char *authdata; + unsigned char testhmac[SHA1_DIGEST_SIZE]; + unsigned char paramdigest[SHA1_DIGEST_SIZE]; + struct sha1_ctx sha_ctx; + unsigned int dlen; + unsigned int dpos; + va_list argp; + int ret; + + if (!chip) + return -ENODEV; + + bufsize = LOAD32(buffer, TPM_SIZE_OFFSET); + tag = LOAD16(buffer, 0); + ordinal = command; + result = LOAD32N(buffer, TPM_RETURN_OFFSET); + if (tag == TPM_TAG_RSP_COMMAND) + return 0; + if (tag != TPM_TAG_RSP_AUTH1_COMMAND) + return -EINVAL; + authdata = buffer + bufsize - SHA1_DIGEST_SIZE; + continueflag = authdata - 1; + enonce = continueflag - TPM_NONCE_SIZE; + + sha1_init(&sha_ctx); + sha1_update(&sha_ctx, (const u8 *)&result, sizeof(result)); + sha1_update(&sha_ctx, (const u8 *)&ordinal, sizeof(ordinal)); + va_start(argp, keylen); + for (;;) { + dlen = va_arg(argp, unsigned int); + if (dlen == 0) + break; + dpos = va_arg(argp, unsigned int); + sha1_update(&sha_ctx, buffer + dpos, dlen); + } + va_end(argp); + sha1_final(&sha_ctx, paramdigest); + + ret = TSS_rawhmac(testhmac, key, keylen, SHA1_DIGEST_SIZE, paramdigest, + TPM_NONCE_SIZE, enonce, TPM_NONCE_SIZE, ononce, + 1, continueflag, 0, 0); + if (ret < 0) + return ret; + + if (crypto_memneq(testhmac, authdata, SHA1_DIGEST_SIZE)) + return -EINVAL; + return 0; +} + +/* + * verify the AUTH2_COMMAND (unseal) result from TPM + */ +static int TSS_checkhmac2(unsigned char *buffer, + const uint32_t command, + const unsigned char *ononce, + const unsigned char *key1, + unsigned int keylen1, + const unsigned char *key2, + unsigned int keylen2, ...) +{ + uint32_t bufsize; + uint16_t tag; + uint32_t ordinal; + uint32_t result; + unsigned char *enonce1; + unsigned char *continueflag1; + unsigned char *authdata1; + unsigned char *enonce2; + unsigned char *continueflag2; + unsigned char *authdata2; + unsigned char testhmac1[SHA1_DIGEST_SIZE]; + unsigned char testhmac2[SHA1_DIGEST_SIZE]; + unsigned char paramdigest[SHA1_DIGEST_SIZE]; + struct sha1_ctx sha_ctx; + unsigned int dlen; + unsigned int dpos; + va_list argp; + int ret; + + bufsize = LOAD32(buffer, TPM_SIZE_OFFSET); + tag = LOAD16(buffer, 0); + ordinal = command; + result = LOAD32N(buffer, TPM_RETURN_OFFSET); + + if (tag == TPM_TAG_RSP_COMMAND) + return 0; + if (tag != TPM_TAG_RSP_AUTH2_COMMAND) + return -EINVAL; + authdata1 = buffer + bufsize - (SHA1_DIGEST_SIZE + 1 + + SHA1_DIGEST_SIZE + SHA1_DIGEST_SIZE); + authdata2 = buffer + bufsize - (SHA1_DIGEST_SIZE); + continueflag1 = authdata1 - 1; + continueflag2 = authdata2 - 1; + enonce1 = continueflag1 - TPM_NONCE_SIZE; + enonce2 = continueflag2 - TPM_NONCE_SIZE; + + sha1_init(&sha_ctx); + sha1_update(&sha_ctx, (const u8 *)&result, sizeof(result)); + sha1_update(&sha_ctx, (const u8 *)&ordinal, sizeof(ordinal)); + + va_start(argp, keylen2); + for (;;) { + dlen = va_arg(argp, unsigned int); + if (dlen == 0) + break; + dpos = va_arg(argp, unsigned int); + sha1_update(&sha_ctx, buffer + dpos, dlen); + } + va_end(argp); + sha1_final(&sha_ctx, paramdigest); + + ret = TSS_rawhmac(testhmac1, key1, keylen1, SHA1_DIGEST_SIZE, + paramdigest, TPM_NONCE_SIZE, enonce1, + TPM_NONCE_SIZE, ononce, 1, continueflag1, 0, 0); + if (ret < 0) + return ret; + if (crypto_memneq(testhmac1, authdata1, SHA1_DIGEST_SIZE)) + return -EINVAL; + ret = TSS_rawhmac(testhmac2, key2, keylen2, SHA1_DIGEST_SIZE, + paramdigest, TPM_NONCE_SIZE, enonce2, + TPM_NONCE_SIZE, ononce, 1, continueflag2, 0, 0); + if (ret < 0) + return ret; + if (crypto_memneq(testhmac2, authdata2, SHA1_DIGEST_SIZE)) + return -EINVAL; + return 0; +} + +/* + * For key specific tpm requests, we will generate and send our + * own TPM command packets using the drivers send function. + */ +static int trusted_tpm_send(struct tpm_buf *buf) +{ + int rc; + + if (!chip) + return -ENODEV; + + rc = tpm_try_get_ops(chip); + if (rc) + return rc; + + dump_tpm_buf(buf->data); + rc = tpm_transmit_cmd(chip, buf, 4, "sending data"); + dump_tpm_buf(buf->data); + + if (rc > 0) + /* TPM error */ + rc = -EPERM; + + tpm_put_ops(chip); + return rc; +} + +/* + * Lock a trusted key, by extending a selected PCR. + * + * Prevents a trusted key that is sealed to PCRs from being accessed. + * This uses the tpm driver's extend function. + */ +static int pcrlock(const int pcrnum) +{ + if (!capable(CAP_SYS_ADMIN)) + return -EPERM; + + return tpm_pcr_extend(chip, pcrnum, digests) ? -EINVAL : 0; +} + +/* + * Create an object specific authorisation protocol (OSAP) session + */ +static int osap(struct tpm_buf *tb, struct osapsess *s, + const unsigned char *key, uint16_t type, uint32_t handle) +{ + unsigned char enonce[TPM_NONCE_SIZE]; + unsigned char ononce[TPM_NONCE_SIZE]; + int ret; + + ret = tpm_get_random(chip, ononce, TPM_NONCE_SIZE); + if (ret < 0) + return ret; + + if (ret != TPM_NONCE_SIZE) + return -EIO; + + tpm_buf_reset(tb, TPM_TAG_RQU_COMMAND, TPM_ORD_OSAP); + tpm_buf_append_u16(tb, type); + tpm_buf_append_u32(tb, handle); + tpm_buf_append(tb, ononce, TPM_NONCE_SIZE); + + ret = trusted_tpm_send(tb); + if (ret < 0) + return ret; + + s->handle = LOAD32(tb->data, TPM_DATA_OFFSET); + memcpy(s->enonce, &(tb->data[TPM_DATA_OFFSET + sizeof(uint32_t)]), + TPM_NONCE_SIZE); + memcpy(enonce, &(tb->data[TPM_DATA_OFFSET + sizeof(uint32_t) + + TPM_NONCE_SIZE]), TPM_NONCE_SIZE); + return TSS_rawhmac(s->secret, key, SHA1_DIGEST_SIZE, TPM_NONCE_SIZE, + enonce, TPM_NONCE_SIZE, ononce, 0, 0); +} + +/* + * Create an object independent authorisation protocol (oiap) session + */ +static int oiap(struct tpm_buf *tb, uint32_t *handle, unsigned char *nonce) +{ + int ret; + + if (!chip) + return -ENODEV; + + tpm_buf_reset(tb, TPM_TAG_RQU_COMMAND, TPM_ORD_OIAP); + ret = trusted_tpm_send(tb); + if (ret < 0) + return ret; + + *handle = LOAD32(tb->data, TPM_DATA_OFFSET); + memcpy(nonce, &tb->data[TPM_DATA_OFFSET + sizeof(uint32_t)], + TPM_NONCE_SIZE); + return 0; +} + +struct tpm_digests { + unsigned char encauth[SHA1_DIGEST_SIZE]; + unsigned char pubauth[SHA1_DIGEST_SIZE]; + unsigned char xorwork[SHA1_DIGEST_SIZE * 2]; + unsigned char xorhash[SHA1_DIGEST_SIZE]; + unsigned char nonceodd[TPM_NONCE_SIZE]; +}; + +/* + * Have the TPM seal(encrypt) the trusted key, possibly based on + * Platform Configuration Registers (PCRs). AUTH1 for sealing key. + */ +static int tpm_seal(struct tpm_buf *tb, uint16_t keytype, + uint32_t keyhandle, const unsigned char *keyauth, + const unsigned char *data, uint32_t datalen, + unsigned char *blob, uint32_t *bloblen, + const unsigned char *blobauth, + const unsigned char *pcrinfo, uint32_t pcrinfosize) +{ + struct osapsess sess; + struct tpm_digests *td; + unsigned char cont; + uint32_t ordinal; + uint32_t pcrsize; + uint32_t datsize; + int sealinfosize; + int encdatasize; + int storedsize; + int ret; + int i; + + /* alloc some work space for all the hashes */ + td = kmalloc_obj(*td); + if (!td) + return -ENOMEM; + + /* get session for sealing key */ + ret = osap(tb, &sess, keyauth, keytype, keyhandle); + if (ret < 0) + goto out; + dump_sess(&sess); + + /* calculate encrypted authorization value */ + memcpy(td->xorwork, sess.secret, SHA1_DIGEST_SIZE); + memcpy(td->xorwork + SHA1_DIGEST_SIZE, sess.enonce, SHA1_DIGEST_SIZE); + sha1(td->xorwork, SHA1_DIGEST_SIZE * 2, td->xorhash); + + ret = tpm_get_random(chip, td->nonceodd, TPM_NONCE_SIZE); + if (ret < 0) + goto out; + + if (ret != TPM_NONCE_SIZE) { + ret = -EIO; + goto out; + } + + ordinal = htonl(TPM_ORD_SEAL); + datsize = htonl(datalen); + pcrsize = htonl(pcrinfosize); + cont = 0; + + /* encrypt data authorization key */ + for (i = 0; i < SHA1_DIGEST_SIZE; ++i) + td->encauth[i] = td->xorhash[i] ^ blobauth[i]; + + /* calculate authorization HMAC value */ + if (pcrinfosize == 0) { + /* no pcr info specified */ + ret = TSS_authhmac(td->pubauth, sess.secret, SHA1_DIGEST_SIZE, + sess.enonce, td->nonceodd, cont, + sizeof(uint32_t), &ordinal, SHA1_DIGEST_SIZE, + td->encauth, sizeof(uint32_t), &pcrsize, + sizeof(uint32_t), &datsize, datalen, data, 0, + 0); + } else { + /* pcr info specified */ + ret = TSS_authhmac(td->pubauth, sess.secret, SHA1_DIGEST_SIZE, + sess.enonce, td->nonceodd, cont, + sizeof(uint32_t), &ordinal, SHA1_DIGEST_SIZE, + td->encauth, sizeof(uint32_t), &pcrsize, + pcrinfosize, pcrinfo, sizeof(uint32_t), + &datsize, datalen, data, 0, 0); + } + if (ret < 0) + goto out; + + /* build and send the TPM request packet */ + tpm_buf_reset(tb, TPM_TAG_RQU_AUTH1_COMMAND, TPM_ORD_SEAL); + tpm_buf_append_u32(tb, keyhandle); + tpm_buf_append(tb, td->encauth, SHA1_DIGEST_SIZE); + tpm_buf_append_u32(tb, pcrinfosize); + tpm_buf_append(tb, pcrinfo, pcrinfosize); + tpm_buf_append_u32(tb, datalen); + tpm_buf_append(tb, data, datalen); + tpm_buf_append_u32(tb, sess.handle); + tpm_buf_append(tb, td->nonceodd, TPM_NONCE_SIZE); + tpm_buf_append_u8(tb, cont); + tpm_buf_append(tb, td->pubauth, SHA1_DIGEST_SIZE); + + ret = trusted_tpm_send(tb); + if (ret < 0) + goto out; + + /* calculate the size of the returned Blob */ + sealinfosize = LOAD32(tb->data, TPM_DATA_OFFSET + sizeof(uint32_t)); + encdatasize = LOAD32(tb->data, TPM_DATA_OFFSET + sizeof(uint32_t) + + sizeof(uint32_t) + sealinfosize); + storedsize = sizeof(uint32_t) + sizeof(uint32_t) + sealinfosize + + sizeof(uint32_t) + encdatasize; + + /* check the HMAC in the response */ + ret = TSS_checkhmac1(tb->data, ordinal, td->nonceodd, sess.secret, + SHA1_DIGEST_SIZE, storedsize, TPM_DATA_OFFSET, 0, + 0); + + /* copy the returned blob to caller */ + if (!ret) { + memcpy(blob, tb->data + TPM_DATA_OFFSET, storedsize); + *bloblen = storedsize; + } +out: + kfree_sensitive(td); + return ret; +} + +/* + * use the AUTH2_COMMAND form of unseal, to authorize both key and blob + */ +static int tpm_unseal(struct tpm_buf *tb, + uint32_t keyhandle, const unsigned char *keyauth, + const unsigned char *blob, int bloblen, + const unsigned char *blobauth, + unsigned char *data, unsigned int *datalen) +{ + unsigned char nonceodd[TPM_NONCE_SIZE]; + unsigned char enonce1[TPM_NONCE_SIZE]; + unsigned char enonce2[TPM_NONCE_SIZE]; + unsigned char authdata1[SHA1_DIGEST_SIZE]; + unsigned char authdata2[SHA1_DIGEST_SIZE]; + uint32_t authhandle1 = 0; + uint32_t authhandle2 = 0; + unsigned char cont = 0; + uint32_t ordinal; + int ret; + + /* sessions for unsealing key and data */ + ret = oiap(tb, &authhandle1, enonce1); + if (ret < 0) { + pr_info("oiap failed (%d)\n", ret); + return ret; + } + ret = oiap(tb, &authhandle2, enonce2); + if (ret < 0) { + pr_info("oiap failed (%d)\n", ret); + return ret; + } + + ordinal = htonl(TPM_ORD_UNSEAL); + ret = tpm_get_random(chip, nonceodd, TPM_NONCE_SIZE); + if (ret < 0) + return ret; + + if (ret != TPM_NONCE_SIZE) { + pr_info("tpm_get_random failed (%d)\n", ret); + return -EIO; + } + ret = TSS_authhmac(authdata1, keyauth, TPM_NONCE_SIZE, + enonce1, nonceodd, cont, sizeof(uint32_t), + &ordinal, bloblen, blob, 0, 0); + if (ret < 0) + return ret; + ret = TSS_authhmac(authdata2, blobauth, TPM_NONCE_SIZE, + enonce2, nonceodd, cont, sizeof(uint32_t), + &ordinal, bloblen, blob, 0, 0); + if (ret < 0) + return ret; + + /* build and send TPM request packet */ + tpm_buf_reset(tb, TPM_TAG_RQU_AUTH2_COMMAND, TPM_ORD_UNSEAL); + tpm_buf_append_u32(tb, keyhandle); + tpm_buf_append(tb, blob, bloblen); + tpm_buf_append_u32(tb, authhandle1); + tpm_buf_append(tb, nonceodd, TPM_NONCE_SIZE); + tpm_buf_append_u8(tb, cont); + tpm_buf_append(tb, authdata1, SHA1_DIGEST_SIZE); + tpm_buf_append_u32(tb, authhandle2); + tpm_buf_append(tb, nonceodd, TPM_NONCE_SIZE); + tpm_buf_append_u8(tb, cont); + tpm_buf_append(tb, authdata2, SHA1_DIGEST_SIZE); + + ret = trusted_tpm_send(tb); + if (ret < 0) { + pr_info("authhmac failed (%d)\n", ret); + return ret; + } + + *datalen = LOAD32(tb->data, TPM_DATA_OFFSET); + ret = TSS_checkhmac2(tb->data, ordinal, nonceodd, + keyauth, SHA1_DIGEST_SIZE, + blobauth, SHA1_DIGEST_SIZE, + sizeof(uint32_t), TPM_DATA_OFFSET, + *datalen, TPM_DATA_OFFSET + sizeof(uint32_t), 0, + 0); + if (ret < 0) { + pr_info("TSS_checkhmac2 failed (%d)\n", ret); + return ret; + } + memcpy(data, tb->data + TPM_DATA_OFFSET + sizeof(uint32_t), *datalen); + return 0; +} + +/* + * Have the TPM seal(encrypt) the symmetric key + */ +static int key_seal(struct trusted_key_payload *p, + struct trusted_key_options *o) +{ + int ret; + + struct tpm_buf *tb __free(kfree) = kzalloc(TPM_BUFSIZE, GFP_KERNEL); + if (!tb) + return -ENOMEM; + + tpm_buf_init(tb, TPM_BUFSIZE); + + /* include migratable flag at end of sealed key */ + p->key[p->key_len] = p->migratable; + + ret = tpm_seal(tb, o->keytype, o->keyhandle, o->keyauth, + p->key, p->key_len + 1, p->blob, &p->blob_len, + o->blobauth, o->pcrinfo, o->pcrinfo_len); + if (ret < 0) + pr_info("srkseal failed (%d)\n", ret); + + return ret; +} + +/* + * Have the TPM unseal(decrypt) the symmetric key + */ +static int key_unseal(struct trusted_key_payload *p, + struct trusted_key_options *o) +{ + int ret; + + struct tpm_buf *tb __free(kfree) = kzalloc(TPM_BUFSIZE, GFP_KERNEL); + if (!tb) + return -ENOMEM; + + tpm_buf_init(tb, TPM_BUFSIZE); + + ret = tpm_unseal(tb, o->keyhandle, o->keyauth, p->blob, p->blob_len, + o->blobauth, p->key, &p->key_len); + if (ret < 0) + pr_info("srkunseal failed (%d)\n", ret); + else + /* pull migratable flag out of sealed key */ + p->migratable = p->key[--p->key_len]; + + return ret; +} + +enum { + Opt_err, + Opt_keyhandle, Opt_keyauth, Opt_blobauth, + Opt_pcrinfo, Opt_pcrlock, Opt_migratable, + Opt_hash, + Opt_policydigest, + Opt_policyhandle, +}; + +static const match_table_t key_tokens = { + {Opt_keyhandle, "keyhandle=%s"}, + {Opt_keyauth, "keyauth=%s"}, + {Opt_blobauth, "blobauth=%s"}, + {Opt_pcrinfo, "pcrinfo=%s"}, + {Opt_pcrlock, "pcrlock=%s"}, + {Opt_migratable, "migratable=%s"}, + {Opt_hash, "hash=%s"}, + {Opt_policydigest, "policydigest=%s"}, + {Opt_policyhandle, "policyhandle=%s"}, + {Opt_err, NULL} +}; + +/* can have zero or more token= options */ +static int getoptions(char *c, struct trusted_key_payload *pay, + struct trusted_key_options *opt) +{ + substring_t args[MAX_OPT_ARGS]; + char *p = c; + int token; + int res; + unsigned long handle; + unsigned long lock; + unsigned long token_mask = 0; + unsigned int digest_len; + int i; + int tpm2; + + tpm2 = tpm_is_tpm2(chip); + if (tpm2 < 0) + return tpm2; + + opt->hash = tpm2 ? HASH_ALGO_SHA256 : HASH_ALGO_SHA1; + + if (!c) + return 0; + + while ((p = strsep(&c, " \t"))) { + if (*p == '\0' || *p == ' ' || *p == '\t') + continue; + token = match_token(p, key_tokens, args); + if (test_and_set_bit(token, &token_mask)) + return -EINVAL; + + switch (token) { + case Opt_pcrinfo: + opt->pcrinfo_len = strlen(args[0].from) / 2; + if (opt->pcrinfo_len > MAX_PCRINFO_SIZE) + return -EINVAL; + res = hex2bin(opt->pcrinfo, args[0].from, + opt->pcrinfo_len); + if (res < 0) + return -EINVAL; + break; + case Opt_keyhandle: + res = kstrtoul(args[0].from, 16, &handle); + if (res < 0) + return -EINVAL; + opt->keytype = SEAL_keytype; + opt->keyhandle = handle; + break; + case Opt_keyauth: + if (strlen(args[0].from) != 2 * SHA1_DIGEST_SIZE) + return -EINVAL; + res = hex2bin(opt->keyauth, args[0].from, + SHA1_DIGEST_SIZE); + if (res < 0) + return -EINVAL; + break; + case Opt_blobauth: + /* + * TPM 1.2 authorizations are sha1 hashes passed in as + * hex strings. TPM 2.0 authorizations are simple + * passwords (although it can take a hash as well) + */ + opt->blobauth_len = strlen(args[0].from); + + if (opt->blobauth_len == 2 * TPM_DIGEST_SIZE) { + res = hex2bin(opt->blobauth, args[0].from, + TPM_DIGEST_SIZE); + if (res < 0) + return -EINVAL; + + opt->blobauth_len = TPM_DIGEST_SIZE; + break; + } + + if (tpm2 && opt->blobauth_len <= sizeof(opt->blobauth)) { + memcpy(opt->blobauth, args[0].from, + opt->blobauth_len); + break; + } + + return -EINVAL; + + break; + + case Opt_migratable: + if (*args[0].from == '0') + pay->migratable = 0; + else if (*args[0].from != '1') + return -EINVAL; + break; + case Opt_pcrlock: + res = kstrtoul(args[0].from, 10, &lock); + if (res < 0) + return -EINVAL; + opt->pcrlock = lock; + break; + case Opt_hash: + if (test_bit(Opt_policydigest, &token_mask)) + return -EINVAL; + for (i = 0; i < HASH_ALGO__LAST; i++) { + if (!strcmp(args[0].from, hash_algo_name[i])) { + opt->hash = i; + break; + } + } + if (i == HASH_ALGO__LAST) + return -EINVAL; + if (!tpm2 && i != HASH_ALGO_SHA1) { + pr_info("TPM 1.x only supports SHA-1.\n"); + return -EINVAL; + } + break; + case Opt_policydigest: + digest_len = hash_digest_size[opt->hash]; + if (!tpm2 || strlen(args[0].from) != (2 * digest_len)) + return -EINVAL; + res = hex2bin(opt->policydigest, args[0].from, + digest_len); + if (res < 0) + return -EINVAL; + opt->policydigest_len = digest_len; + break; + case Opt_policyhandle: + if (!tpm2) + return -EINVAL; + res = kstrtoul(args[0].from, 16, &handle); + if (res < 0) + return -EINVAL; + opt->policyhandle = handle; + break; + default: + return -EINVAL; + } + } + return 0; +} + +static struct trusted_key_options *trusted_options_alloc(void) +{ + struct trusted_key_options *options; + int tpm2; + + tpm2 = tpm_is_tpm2(chip); + if (tpm2 < 0) + return NULL; + + options = kzalloc_obj(*options); + if (options) { + /* set any non-zero defaults */ + options->keytype = SRK_keytype; + + if (!tpm2) + options->keyhandle = SRKHANDLE; + } + return options; +} + +static int trusted_tpm_seal(struct trusted_key_payload *p, char *datablob) +{ + struct trusted_key_options *options = NULL; + int ret = 0; + int tpm2; + + tpm2 = tpm_is_tpm2(chip); + if (tpm2 < 0) + return tpm2; + + options = trusted_options_alloc(); + if (!options) + return -ENOMEM; + + ret = getoptions(datablob, p, options); + if (ret < 0) + goto out; + dump_options(options); + + if (!options->keyhandle && !tpm2) { + ret = -EINVAL; + goto out; + } + + if (tpm2) + ret = tpm2_seal_trusted(chip, p, options); + else + ret = key_seal(p, options); + if (ret < 0) { + pr_info("key_seal failed (%d)\n", ret); + goto out; + } + + if (options->pcrlock) { + ret = pcrlock(options->pcrlock); + if (ret < 0) { + pr_info("pcrlock failed (%d)\n", ret); + goto out; + } + } +out: + kfree_sensitive(options); + return ret; +} + +static int trusted_tpm_unseal(struct trusted_key_payload *p, char *datablob) +{ + struct trusted_key_options *options = NULL; + int ret = 0; + int tpm2; + + tpm2 = tpm_is_tpm2(chip); + if (tpm2 < 0) + return tpm2; + + options = trusted_options_alloc(); + if (!options) + return -ENOMEM; + + ret = getoptions(datablob, p, options); + if (ret < 0) + goto out; + dump_options(options); + + if (!options->keyhandle && !tpm2) { + ret = -EINVAL; + goto out; + } + + if (tpm2) + ret = tpm2_unseal_trusted(chip, p, options); + else + ret = key_unseal(p, options); + if (ret < 0) + pr_info("key_unseal failed (%d)\n", ret); + + if (options->pcrlock) { + ret = pcrlock(options->pcrlock); + if (ret < 0) { + pr_info("pcrlock failed (%d)\n", ret); + goto out; + } + } +out: + kfree_sensitive(options); + return ret; +} + +static int trusted_tpm_get_random(unsigned char *key, size_t key_len) +{ + return tpm_get_random(chip, key, key_len); +} + +static int __init init_digests(void) +{ + int i; + + digests = kzalloc_objs(*digests, chip->nr_allocated_banks); + if (!digests) + return -ENOMEM; + + for (i = 0; i < chip->nr_allocated_banks; i++) + digests[i].alg_id = chip->allocated_banks[i].alg_id; + + return 0; +} + +static int __init trusted_tpm_init(void) +{ + int ret; + + chip = tpm_default_chip(); + if (!chip) + return -ENODEV; + + ret = init_digests(); + if (ret < 0) + goto err_put; + ret = register_key_type(&key_type_trusted); + if (ret < 0) + goto err_free; + return 0; +err_free: + kfree(digests); +err_put: + put_device(&chip->dev); + return ret; +} + +static void trusted_tpm_exit(void) +{ + if (chip) { + unregister_key_type(&key_type_trusted); + put_device(&chip->dev); + kfree(digests); + } +} + +struct trusted_key_ops trusted_key_tpm_ops = { + .migratable = 1, /* migratable by default */ + .init = trusted_tpm_init, + .seal = trusted_tpm_seal, + .unseal = trusted_tpm_unseal, + .get_random = trusted_tpm_get_random, + .exit = trusted_tpm_exit, +}; diff --git a/security/keys/trusted-keys/trusted_tpm2.c b/security/keys/trusted-keys/trusted_tpm2.c new file mode 100644 index 000000000..67225dd56 --- /dev/null +++ b/security/keys/trusted-keys/trusted_tpm2.c @@ -0,0 +1,605 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (C) 2004 IBM Corporation + * Copyright (C) 2014 Intel Corporation + */ + +#include +#include +#include +#include +#include + +#include +#include + +#include + +#include "tpm2key.asn1.h" + +static u32 tpm2key_oid[] = { 2, 23, 133, 10, 1, 5 }; + +static int tpm2_key_encode(struct trusted_key_payload *payload, + struct trusted_key_options *options, + u8 *src, u32 len) +{ + const int SCRATCH_SIZE = PAGE_SIZE; + u8 *scratch = kmalloc(SCRATCH_SIZE, GFP_KERNEL); + u8 *work = scratch, *work1; + u8 *end_work = scratch + SCRATCH_SIZE; + u8 *priv, *pub; + u16 priv_len, pub_len; + int ret; + + priv_len = get_unaligned_be16(src) + 2; + priv = src; + + src += priv_len; + + pub_len = get_unaligned_be16(src) + 2; + pub = src; + + if (!scratch) + return -ENOMEM; + + work = asn1_encode_oid(work, end_work, tpm2key_oid, + asn1_oid_len(tpm2key_oid)); + + if (options->blobauth_len == 0) { + unsigned char bool[3], *w = bool; + /* tag 0 is emptyAuth */ + w = asn1_encode_boolean(w, w + sizeof(bool), true); + if (WARN(IS_ERR(w), "BUG: Boolean failed to encode")) { + ret = PTR_ERR(w); + goto err; + } + work = asn1_encode_tag(work, end_work, 0, bool, w - bool); + } + + /* + * Assume both octet strings will encode to a 2 byte definite length + * + * Note: For a well behaved TPM, this warning should never + * trigger, so if it does there's something nefarious going on + */ + if (WARN(work - scratch + pub_len + priv_len + 14 > SCRATCH_SIZE, + "BUG: scratch buffer is too small")) { + ret = -EINVAL; + goto err; + } + + work = asn1_encode_integer(work, end_work, options->keyhandle); + work = asn1_encode_octet_string(work, end_work, pub, pub_len); + work = asn1_encode_octet_string(work, end_work, priv, priv_len); + + work1 = payload->blob; + work1 = asn1_encode_sequence(work1, work1 + sizeof(payload->blob), + scratch, work - scratch); + if (IS_ERR(work1)) { + ret = PTR_ERR(work1); + pr_err("BUG: ASN.1 encoder failed with %d\n", ret); + goto err; + } + + kfree(scratch); + return work1 - payload->blob; + +err: + kfree(scratch); + return ret; +} + +struct tpm2_key_context { + u32 parent; + const u8 *pub; + u32 pub_len; + const u8 *priv; + u32 priv_len; +}; + +static int tpm2_key_decode(struct trusted_key_payload *payload, + struct trusted_key_options *options, + u8 **buf) +{ + int ret; + struct tpm2_key_context ctx; + u8 *blob; + + memset(&ctx, 0, sizeof(ctx)); + + ret = asn1_ber_decoder(&tpm2key_decoder, &ctx, payload->blob, + payload->blob_len); + if (ret < 0) + return ret; + + if (ctx.priv_len + ctx.pub_len > MAX_BLOB_SIZE) + return -EINVAL; + + blob = kmalloc(ctx.priv_len + ctx.pub_len + 4, GFP_KERNEL); + if (!blob) + return -ENOMEM; + + *buf = blob; + options->keyhandle = ctx.parent; + + memcpy(blob, ctx.priv, ctx.priv_len); + blob += ctx.priv_len; + + memcpy(blob, ctx.pub, ctx.pub_len); + + return 0; +} + +int tpm2_key_parent(void *context, size_t hdrlen, + unsigned char tag, + const void *value, size_t vlen) +{ + struct tpm2_key_context *ctx = context; + const u8 *v = value; + int i; + + ctx->parent = 0; + for (i = 0; i < vlen; i++) { + ctx->parent <<= 8; + ctx->parent |= v[i]; + } + + return 0; +} + +int tpm2_key_type(void *context, size_t hdrlen, + unsigned char tag, + const void *value, size_t vlen) +{ + enum OID oid = look_up_OID(value, vlen); + + if (oid != OID_TPMSealedData) { + char buffer[50]; + + sprint_oid(value, vlen, buffer, sizeof(buffer)); + pr_debug("OID is \"%s\" which is not TPMSealedData\n", + buffer); + return -EINVAL; + } + + return 0; +} + +int tpm2_key_pub(void *context, size_t hdrlen, + unsigned char tag, + const void *value, size_t vlen) +{ + struct tpm2_key_context *ctx = context; + + ctx->pub = value; + ctx->pub_len = vlen; + + return 0; +} + +int tpm2_key_priv(void *context, size_t hdrlen, + unsigned char tag, + const void *value, size_t vlen) +{ + struct tpm2_key_context *ctx = context; + + ctx->priv = value; + ctx->priv_len = vlen; + + return 0; +} + +/** + * tpm2_buf_append_auth() - append TPMS_AUTH_COMMAND to the buffer. + * + * @buf: an allocated tpm_buf instance + * @session_handle: session handle + * @nonce: the session nonce, may be NULL if not used + * @nonce_len: the session nonce length, may be 0 if not used + * @attributes: the session attributes + * @hmac: the session HMAC or password, may be NULL if not used + * @hmac_len: the session HMAC or password length, maybe 0 if not used + */ +static void tpm2_buf_append_auth(struct tpm_buf *buf, u32 session_handle, + const u8 *nonce, u16 nonce_len, + u8 attributes, + const u8 *hmac, u16 hmac_len) +{ + tpm_buf_append_u32(buf, 9 + nonce_len + hmac_len); + tpm_buf_append_u32(buf, session_handle); + tpm_buf_append_u16(buf, nonce_len); + + if (nonce && nonce_len) + tpm_buf_append(buf, nonce, nonce_len); + + tpm_buf_append_u8(buf, attributes); + tpm_buf_append_u16(buf, hmac_len); + + if (hmac && hmac_len) + tpm_buf_append(buf, hmac, hmac_len); +} + +/** + * tpm2_seal_trusted() - seal the payload of a trusted key + * + * @chip: TPM chip to use + * @payload: the key data in clear and encrypted form + * @options: authentication values and other options + * + * Return: < 0 on error and 0 on success. + */ +int tpm2_seal_trusted(struct tpm_chip *chip, + struct trusted_key_payload *payload, + struct trusted_key_options *options) +{ + off_t offset = TPM_HEADER_SIZE; + struct tpm_buf *buf __free(kfree) = NULL; + struct tpm_buf *sized __free(kfree) = NULL; + int blob_len = 0; + int hash; + u32 flags; + int rc; + + hash = tpm2_find_hash_alg(options->hash); + if (hash < 0) + return hash; + + if (!options->keyhandle) + return -EINVAL; + + rc = tpm_try_get_ops(chip); + if (rc) + return rc; + + rc = tpm2_start_auth_session(chip); + if (rc) + goto out_put; + + buf = kzalloc(TPM_BUFSIZE, GFP_KERNEL); + if (!buf) { + rc = -ENOMEM; + tpm2_end_auth_session(chip); + goto out_put; + } + + tpm_buf_init(buf, TPM_BUFSIZE); + tpm_buf_reset(buf, TPM2_ST_SESSIONS, TPM2_CC_CREATE); + + sized = kzalloc(TPM_BUFSIZE, GFP_KERNEL); + if (!sized) { + rc = -ENOMEM; + tpm2_end_auth_session(chip); + goto out_put; + } + + tpm_buf_init_sized(sized, TPM_BUFSIZE); + + rc = tpm_buf_append_name(chip, buf, options->keyhandle, NULL); + if (rc) + goto out; + + tpm_buf_append_hmac_session(chip, buf, TPM2_SA_DECRYPT, + options->keyauth, TPM_DIGEST_SIZE); + + /* sensitive */ + tpm_buf_append_u16(sized, options->blobauth_len); + + if (options->blobauth_len) + tpm_buf_append(sized, options->blobauth, options->blobauth_len); + + tpm_buf_append_u16(sized, payload->key_len); + tpm_buf_append(sized, payload->key, payload->key_len); + tpm_buf_append(buf, sized->data, sized->length); + + /* public */ + tpm_buf_reset_sized(sized); + tpm_buf_append_u16(sized, TPM_ALG_KEYEDHASH); + tpm_buf_append_u16(sized, hash); + + /* key properties */ + flags = 0; + flags |= options->policydigest_len ? 0 : TPM2_OA_USER_WITH_AUTH; + flags |= payload->migratable ? 0 : (TPM2_OA_FIXED_TPM | TPM2_OA_FIXED_PARENT); + tpm_buf_append_u32(sized, flags); + + /* policy */ + tpm_buf_append_u16(sized, options->policydigest_len); + if (options->policydigest_len) + tpm_buf_append(sized, options->policydigest, options->policydigest_len); + + /* public parameters */ + tpm_buf_append_u16(sized, TPM_ALG_NULL); + tpm_buf_append_u16(sized, 0); + + tpm_buf_append(buf, sized->data, sized->length); + + /* outside info */ + tpm_buf_append_u16(buf, 0); + + /* creation PCR */ + tpm_buf_append_u32(buf, 0); + + if (buf->flags & TPM_BUF_INVALID) { + rc = -E2BIG; + tpm2_end_auth_session(chip); + goto out; + } + + rc = tpm_buf_fill_hmac_session(chip, buf); + if (rc) + goto out; + + rc = tpm_transmit_cmd(chip, buf, 4, "sealing data"); + rc = tpm_buf_check_hmac_response(chip, buf, rc); + if (rc) + goto out; + + blob_len = tpm_buf_read_u32(buf, &offset); + if (blob_len > MAX_BLOB_SIZE || buf->flags & TPM_BUF_INVALID) { + rc = -E2BIG; + goto out; + } + if (buf->length - offset < blob_len) { + rc = -EFAULT; + goto out; + } + + blob_len = tpm2_key_encode(payload, options, &buf->data[offset], blob_len); + if (blob_len < 0) + rc = blob_len; + +out: + if (!rc) + payload->blob_len = blob_len; + +out_put: + tpm_put_ops(chip); + return tpm_ret_to_err(rc); +} + +/** + * tpm2_load_cmd() - execute a TPM2_Load command + * + * @chip: TPM chip to use + * @payload: the key data in clear and encrypted form + * @options: authentication values and other options + * @blob_handle: returned blob handle + * + * Return: 0 on success. + * -E2BIG on wrong payload size. + * -EPERM on tpm error status. + * < 0 error from tpm_send. + */ +static int tpm2_load_cmd(struct tpm_chip *chip, + struct trusted_key_payload *payload, + struct trusted_key_options *options, + u32 *blob_handle) +{ + u8 *blob_ref __free(kfree) = NULL; + struct tpm_buf *buf __free(kfree) = NULL; + unsigned int private_len; + unsigned int public_len; + unsigned int blob_len; + u8 *blob, *pub; + int rc; + u32 attrs; + + rc = tpm2_key_decode(payload, options, &blob); + if (rc) { + /* old form */ + blob = payload->blob; + payload->old_format = 1; + } else { + /* Bind for cleanup: */ + blob_ref = blob; + } + + /* new format carries keyhandle but old format doesn't */ + if (!options->keyhandle) + return -EINVAL; + + /* must be big enough for at least the two be16 size counts */ + if (payload->blob_len < 4) + return -EINVAL; + + private_len = get_unaligned_be16(blob); + + /* must be big enough for following public_len */ + if (private_len + 2 + 2 > (payload->blob_len)) + return -E2BIG; + + public_len = get_unaligned_be16(blob + 2 + private_len); + if (private_len + 2 + public_len + 2 > payload->blob_len) + return -E2BIG; + + pub = blob + 2 + private_len + 2; + /* key attributes are always at offset 4 */ + attrs = get_unaligned_be32(pub + 4); + + if ((attrs & (TPM2_OA_FIXED_TPM | TPM2_OA_FIXED_PARENT)) == + (TPM2_OA_FIXED_TPM | TPM2_OA_FIXED_PARENT)) + payload->migratable = 0; + else + payload->migratable = 1; + + blob_len = private_len + public_len + 4; + if (blob_len > payload->blob_len) + return -E2BIG; + + rc = tpm2_start_auth_session(chip); + if (rc) + return rc; + + buf = kzalloc(TPM_BUFSIZE, GFP_KERNEL); + if (!buf) { + tpm2_end_auth_session(chip); + return -ENOMEM; + } + + tpm_buf_init(buf, TPM_BUFSIZE); + tpm_buf_reset(buf, TPM2_ST_SESSIONS, TPM2_CC_LOAD); + + rc = tpm_buf_append_name(chip, buf, options->keyhandle, NULL); + if (rc) + return rc; + + tpm_buf_append_hmac_session(chip, buf, 0, options->keyauth, + TPM_DIGEST_SIZE); + + tpm_buf_append(buf, blob, blob_len); + + if (buf->flags & TPM_BUF_INVALID) { + tpm2_end_auth_session(chip); + return -E2BIG; + } + + rc = tpm_buf_fill_hmac_session(chip, buf); + if (rc) + return rc; + + rc = tpm_transmit_cmd(chip, buf, 4, "loading blob"); + rc = tpm_buf_check_hmac_response(chip, buf, rc); + if (!rc) + *blob_handle = be32_to_cpup( + (__be32 *)&buf->data[TPM_HEADER_SIZE]); + + return tpm_ret_to_err(rc); +} + +/** + * tpm2_unseal_cmd() - execute a TPM2_Unseal command + * + * @chip: TPM chip to use + * @payload: the key data in clear and encrypted form + * @options: authentication values and other options + * @blob_handle: blob handle + * + * Return: 0 on success + * -EPERM on tpm error status + * < 0 error from tpm_send + */ +static int tpm2_unseal_cmd(struct tpm_chip *chip, + struct trusted_key_payload *payload, + struct trusted_key_options *options, + u32 blob_handle) +{ + struct tpm_header *head; + struct tpm_buf *buf __free(kfree) = NULL; + u16 data_len; + int offset; + u8 *data; + int rc; + + rc = tpm2_start_auth_session(chip); + if (rc) + return rc; + + buf = kzalloc(TPM_BUFSIZE, GFP_KERNEL); + if (!buf) { + tpm2_end_auth_session(chip); + return -ENOMEM; + } + + tpm_buf_init(buf, TPM_BUFSIZE); + tpm_buf_reset(buf, TPM2_ST_SESSIONS, TPM2_CC_UNSEAL); + + rc = tpm_buf_append_name(chip, buf, blob_handle, NULL); + if (rc) + return rc; + + if (!options->policyhandle) { + tpm_buf_append_hmac_session(chip, buf, TPM2_SA_ENCRYPT, + options->blobauth, + options->blobauth_len); + } else { + /* + * FIXME: The policy session was generated outside the + * kernel so we don't known the nonce and thus can't + * calculate a HMAC on it. Therefore, the user can + * only really use TPM2_PolicyPassword and we must + * send down the plain text password, which could be + * intercepted. We can still encrypt the returned + * key, but that's small comfort since the interposer + * could repeat our actions with the exfiltrated + * password. + */ + tpm2_buf_append_auth(buf, options->policyhandle, + NULL /* nonce */, 0, 0, + options->blobauth, options->blobauth_len); + if (tpm2_chip_auth(chip)) { + tpm_buf_append_hmac_session(chip, buf, TPM2_SA_ENCRYPT, + NULL, 0); + } else { + offset = buf->handles * 4 + TPM_HEADER_SIZE; + head = (struct tpm_header *)buf->data; + if (tpm_buf_length(buf) == offset) + head->tag = cpu_to_be16(TPM2_ST_NO_SESSIONS); + } + } + + rc = tpm_buf_fill_hmac_session(chip, buf); + if (rc) + return rc; + + rc = tpm_transmit_cmd(chip, buf, 6, "unsealing"); + rc = tpm_buf_check_hmac_response(chip, buf, rc); + + if (!rc) { + data_len = be16_to_cpup( + (__be16 *)&buf->data[TPM_HEADER_SIZE + 4]); + if (data_len < MIN_KEY_SIZE || data_len > MAX_KEY_SIZE) + return -EFAULT; + + if (tpm_buf_length(buf) < TPM_HEADER_SIZE + 6 + data_len) + return -EFAULT; + data = &buf->data[TPM_HEADER_SIZE + 6]; + + if (payload->old_format) { + /* migratable flag is at the end of the key */ + memcpy(payload->key, data, data_len - 1); + payload->key_len = data_len - 1; + payload->migratable = data[data_len - 1]; + } else { + /* + * migratable flag already collected from key + * attributes + */ + memcpy(payload->key, data, data_len); + payload->key_len = data_len; + } + } + + return tpm_ret_to_err(rc); +} + +/** + * tpm2_unseal_trusted() - unseal the payload of a trusted key + * + * @chip: TPM chip to use + * @payload: the key data in clear and encrypted form + * @options: authentication values and other options + * + * Return: Same as with tpm_send. + */ +int tpm2_unseal_trusted(struct tpm_chip *chip, + struct trusted_key_payload *payload, + struct trusted_key_options *options) +{ + u32 blob_handle; + int rc; + + rc = tpm_try_get_ops(chip); + if (rc) + return rc; + + rc = tpm2_load_cmd(chip, payload, options, &blob_handle); + if (rc) + goto out; + + rc = tpm2_unseal_cmd(chip, payload, options, blob_handle); + tpm2_flush_context(chip, blob_handle); + +out: + tpm_put_ops(chip); + return tpm_ret_to_err(rc); +} -- cgit v1.3.1