Resguardo inicial

This commit is contained in:
lastdragon
2026-09-06 12:53:21 -06:00
parent 662efe3c2a
commit ad2088d178
94853 changed files with 43200404 additions and 0 deletions
@@ -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
@@ -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
@@ -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})
}
@@ -0,0 +1,191 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (C) 2021 Pengutronix, Ahmad Fatoum <kernel@pengutronix.de>
* Copyright 2025 NXP
*/
#include <keys/trusted_caam.h>
#include <keys/trusted-type.h>
#include <linux/build_bug.h>
#include <linux/key-type.h>
#include <linux/parser.h>
#include <soc/fsl/caam-blob.h>
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,
};
@@ -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 <keys/user-type.h>
#include <keys/trusted-type.h>
#include <keys/trusted_tee.h>
#include <keys/trusted_caam.h>
#include <keys/trusted_dcp.h>
#include <keys/trusted_tpm.h>
#include <keys/trusted_pkwm.h>
#include <linux/capability.h>
#include <linux/err.h>
#include <linux/hex.h>
#include <linux/init.h>
#include <linux/key-type.h>
#include <linux/module.h>
#include <linux/parser.h>
#include <linux/random.h>
#include <linux/rcupdate.h>
#include <linux/slab.h>
#include <linux/static_call.h>
#include <linux/string.h>
#include <linux/uaccess.h>
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");
@@ -0,0 +1,363 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (C) 2021 sigma star gmbh
*/
#include <crypto/aead.h>
#include <crypto/aes.h>
#include <crypto/algapi.h>
#include <crypto/gcm.h>
#include <crypto/skcipher.h>
#include <keys/trusted-type.h>
#include <linux/key-type.h>
#include <linux/module.h>
#include <linux/printk.h>
#include <linux/random.h>
#include <linux/scatterlist.h>
#include <soc/fsl/dcp.h>
#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,
};
@@ -0,0 +1,190 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (C) 2025 IBM Corporation, Srish Srinivasan <ssrish@linux.ibm.com>
*/
#include <keys/trusted_pkwm.h>
#include <keys/trusted-type.h>
#include <linux/build_bug.h>
#include <linux/key-type.h>
#include <linux/parser.h>
#include <asm/plpks.h>
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,
};
@@ -0,0 +1,287 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Copyright (C) 2019-2021 Linaro Ltd.
*
* Author:
* Sumit Garg <sumit.garg@linaro.org>
*/
#include <linux/err.h>
#include <linux/key-type.h>
#include <linux/module.h>
#include <linux/slab.h>
#include <linux/string.h>
#include <linux/tee_drv.h>
#include <linux/uuid.h>
#include <keys/trusted_tee.h>
#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(&param, 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(&param, 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(&param, 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,
};
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,605 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (C) 2004 IBM Corporation
* Copyright (C) 2014 Intel Corporation
*/
#include <linux/asn1_encoder.h>
#include <linux/oid_registry.h>
#include <linux/string.h>
#include <linux/err.h>
#include <linux/tpm.h>
#include <linux/tpm_command.h>
#include <keys/trusted-type.h>
#include <keys/trusted_tpm.h>
#include <linux/unaligned.h>
#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, sized;
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;
rc = tpm_buf_init(&buf, TPM2_ST_SESSIONS, TPM2_CC_CREATE);
if (rc) {
tpm2_end_auth_session(chip);
goto out_put;
}
rc = tpm_buf_init_sized(&sized);
if (rc) {
tpm_buf_destroy(&buf);
tpm2_end_auth_session(chip);
goto out_put;
}
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_OVERFLOW) {
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_BOUNDARY_ERROR) {
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:
tpm_buf_destroy(&sized);
tpm_buf_destroy(&buf);
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;
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;
rc = tpm_buf_init(&buf, TPM2_ST_SESSIONS, TPM2_CC_LOAD);
if (rc) {
tpm2_end_auth_session(chip);
return rc;
}
rc = tpm_buf_append_name(chip, &buf, options->keyhandle, NULL);
if (rc)
goto out;
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_OVERFLOW) {
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, "loading blob");
rc = tpm_buf_check_hmac_response(chip, &buf, rc);
if (!rc)
*blob_handle = be32_to_cpup(
(__be32 *) &buf.data[TPM_HEADER_SIZE]);
out:
tpm_buf_destroy(&buf);
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;
u16 data_len;
int offset;
u8 *data;
int rc;
rc = tpm2_start_auth_session(chip);
if (rc)
return rc;
rc = tpm_buf_init(&buf, TPM2_ST_SESSIONS, TPM2_CC_UNSEAL);
if (rc) {
tpm2_end_auth_session(chip);
return rc;
}
rc = tpm_buf_append_name(chip, &buf, blob_handle, NULL);
if (rc)
goto out;
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)
goto out;
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) {
rc = -EFAULT;
goto out;
}
if (tpm_buf_length(&buf) < TPM_HEADER_SIZE + 6 + data_len) {
rc = -EFAULT;
goto out;
}
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;
}
}
out:
tpm_buf_destroy(&buf);
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);
}