diff options
Diffstat (limited to 'security/keys/trusted_defined.c')
| -rw-r--r-- | security/keys/trusted_defined.c | 1151 | 
1 files changed, 1151 insertions, 0 deletions
| diff --git a/security/keys/trusted_defined.c b/security/keys/trusted_defined.c new file mode 100644 index 000000000000..1bec72e7596d --- /dev/null +++ b/security/keys/trusted_defined.c @@ -0,0 +1,1151 @@ +/* + * Copyright (C) 2010 IBM Corporation + * + * Author: + * David Safford <safford@us.ibm.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 2 of the License. + * + * See Documentation/keys-trusted-encrypted.txt + */ + +#include <linux/uaccess.h> +#include <linux/module.h> +#include <linux/init.h> +#include <linux/slab.h> +#include <linux/parser.h> +#include <linux/string.h> +#include <keys/user-type.h> +#include <keys/trusted-type.h> +#include <linux/key-type.h> +#include <linux/rcupdate.h> +#include <linux/crypto.h> +#include <crypto/hash.h> +#include <crypto/sha.h> +#include <linux/capability.h> +#include <linux/tpm.h> +#include <linux/tpm_command.h> + +#include "trusted_defined.h" + +static const char hmac_alg[] = "hmac(sha1)"; +static const char hash_alg[] = "sha1"; + +struct sdesc { +	struct shash_desc shash; +	char ctx[]; +}; + +static struct crypto_shash *hashalg; +static struct crypto_shash *hmacalg; + +static struct sdesc *init_sdesc(struct crypto_shash *alg) +{ +	struct sdesc *sdesc; +	int size; + +	size = sizeof(struct shash_desc) + crypto_shash_descsize(alg); +	sdesc = kmalloc(size, GFP_KERNEL); +	if (!sdesc) +		return ERR_PTR(-ENOMEM); +	sdesc->shash.tfm = alg; +	sdesc->shash.flags = 0x0; +	return sdesc; +} + +static int TSS_sha1(const unsigned char *data, const unsigned int datalen, +		    unsigned char *digest) +{ +	struct sdesc *sdesc; +	int ret; + +	sdesc = init_sdesc(hashalg); +	if (IS_ERR(sdesc)) { +		pr_info("trusted_key: can't alloc %s\n", hash_alg); +		return PTR_ERR(sdesc); +	} + +	ret = crypto_shash_digest(&sdesc->shash, data, datalen, digest); +	kfree(sdesc); +	return ret; +} + +static int TSS_rawhmac(unsigned char *digest, const unsigned char *key, +		       const unsigned int keylen, ...) +{ +	struct sdesc *sdesc; +	va_list argp; +	unsigned int dlen; +	unsigned char *data; +	int ret; + +	sdesc = init_sdesc(hmacalg); +	if (IS_ERR(sdesc)) { +		pr_info("trusted_key: can't alloc %s\n", hmac_alg); +		return PTR_ERR(sdesc); +	} + +	ret = crypto_shash_setkey(hmacalg, key, keylen); +	if (ret < 0) +		goto out; +	ret = crypto_shash_init(&sdesc->shash); +	if (ret < 0) +		goto out; + +	va_start(argp, keylen); +	for (;;) { +		dlen = va_arg(argp, unsigned int); +		if (dlen == 0) +			break; +		data = va_arg(argp, unsigned char *); +		if (data == NULL) +			return -EINVAL; +		ret = crypto_shash_update(&sdesc->shash, data, dlen); +		if (ret < 0) +			goto out; +	} +	va_end(argp); +	ret = crypto_shash_final(&sdesc->shash, digest); +out: +	kfree(sdesc); +	return ret; +} + +/* + * calculate authorization info fields to send to TPM + */ +static uint32_t TSS_authhmac(unsigned char *digest, const unsigned char *key, +			     const unsigned int keylen, unsigned char *h1, +			     unsigned char *h2, unsigned char h3, ...) +{ +	unsigned char paramdigest[SHA1_DIGEST_SIZE]; +	struct sdesc *sdesc; +	unsigned int dlen; +	unsigned char *data; +	unsigned char c; +	int ret; +	va_list argp; + +	sdesc = init_sdesc(hashalg); +	if (IS_ERR(sdesc)) { +		pr_info("trusted_key: can't alloc %s\n", hash_alg); +		return PTR_ERR(sdesc); +	} + +	c = h3; +	ret = crypto_shash_init(&sdesc->shash); +	if (ret < 0) +		goto out; +	va_start(argp, h3); +	for (;;) { +		dlen = va_arg(argp, unsigned int); +		if (dlen == 0) +			break; +		data = va_arg(argp, unsigned char *); +		ret = crypto_shash_update(&sdesc->shash, data, dlen); +		if (ret < 0) +			goto out; +	} +	va_end(argp); +	ret = crypto_shash_final(&sdesc->shash, paramdigest); +	if (!ret) +		TSS_rawhmac(digest, key, keylen, SHA1_DIGEST_SIZE, +			    paramdigest, TPM_NONCE_SIZE, h1, +			    TPM_NONCE_SIZE, h2, 1, &c, 0, 0); +out: +	kfree(sdesc); +	return ret; +} + +/* + * verify the AUTH1_COMMAND (Seal) result from TPM + */ +static uint32_t TSS_checkhmac1(unsigned char *buffer, +			       const uint32_t command, +			       const unsigned char *ononce, +			       const unsigned char *key, +			       const 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 sdesc *sdesc; +	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_AUTH1_COMMAND) +		return -EINVAL; +	authdata = buffer + bufsize - SHA1_DIGEST_SIZE; +	continueflag = authdata - 1; +	enonce = continueflag - TPM_NONCE_SIZE; + +	sdesc = init_sdesc(hashalg); +	if (IS_ERR(sdesc)) { +		pr_info("trusted_key: can't alloc %s\n", hash_alg); +		return PTR_ERR(sdesc); +	} +	ret = crypto_shash_init(&sdesc->shash); +	if (ret < 0) +		goto out; +	ret = crypto_shash_update(&sdesc->shash, (const u8 *)&result, +				  sizeof result); +	if (ret < 0) +		goto out; +	ret = crypto_shash_update(&sdesc->shash, (const u8 *)&ordinal, +				  sizeof ordinal); +	if (ret < 0) +		goto out; +	va_start(argp, keylen); +	for (;;) { +		dlen = va_arg(argp, unsigned int); +		if (dlen == 0) +			break; +		dpos = va_arg(argp, unsigned int); +		ret = crypto_shash_update(&sdesc->shash, buffer + dpos, dlen); +		if (ret < 0) +			goto out; +	} +	va_end(argp); +	ret = crypto_shash_final(&sdesc->shash, paramdigest); +	if (ret < 0) +		goto out; +	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) +		goto out; +	if (memcmp(testhmac, authdata, SHA1_DIGEST_SIZE)) +		ret = -EINVAL; +out: +	kfree(sdesc); +	return ret; +} + +/* + * verify the AUTH2_COMMAND (unseal) result from TPM + */ +static uint32_t TSS_checkhmac2(unsigned char *buffer, +			       const uint32_t command, +			       const unsigned char *ononce, +			       const unsigned char *key1, +			       const unsigned int keylen1, +			       const unsigned char *key2, +			       const 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 sdesc *sdesc; +	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; + +	sdesc = init_sdesc(hashalg); +	if (IS_ERR(sdesc)) { +		pr_info("trusted_key: can't alloc %s\n", hash_alg); +		return PTR_ERR(sdesc); +	} +	ret = crypto_shash_init(&sdesc->shash); +	if (ret < 0) +		goto out; +	ret = crypto_shash_update(&sdesc->shash, (const u8 *)&result, +				  sizeof result); +	if (ret < 0) +		goto out; +	ret = crypto_shash_update(&sdesc->shash, (const u8 *)&ordinal, +				  sizeof ordinal); +	if (ret < 0) +		goto out; + +	va_start(argp, keylen2); +	for (;;) { +		dlen = va_arg(argp, unsigned int); +		if (dlen == 0) +			break; +		dpos = va_arg(argp, unsigned int); +		ret = crypto_shash_update(&sdesc->shash, buffer + dpos, dlen); +		if (ret < 0) +			goto out; +	} +	ret = crypto_shash_final(&sdesc->shash, paramdigest); +	if (ret < 0) +		goto out; + +	ret = TSS_rawhmac(testhmac1, key1, keylen1, SHA1_DIGEST_SIZE, +			  paramdigest, TPM_NONCE_SIZE, enonce1, +			  TPM_NONCE_SIZE, ononce, 1, continueflag1, 0, 0); +	if (memcmp(testhmac1, authdata1, SHA1_DIGEST_SIZE)) { +		ret = -EINVAL; +		goto out; +	} +	ret = TSS_rawhmac(testhmac2, key2, keylen2, SHA1_DIGEST_SIZE, +			  paramdigest, TPM_NONCE_SIZE, enonce2, +			  TPM_NONCE_SIZE, ononce, 1, continueflag2, 0, 0); +	if (memcmp(testhmac2, authdata2, SHA1_DIGEST_SIZE)) +		ret = -EINVAL; +out: +	kfree(sdesc); +	return ret; +} + +/* + * 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(const u32 chip_num, unsigned char *cmd, +			    size_t buflen) +{ +	int rc; + +	dump_tpm_buf(cmd); +	rc = tpm_send(chip_num, cmd, buflen); +	dump_tpm_buf(cmd); +	if (rc > 0) +		/* Can't return positive return codes values to keyctl */ +		rc = -EPERM; +	return rc; +} + +/* + * get a random value from TPM + */ +static int tpm_get_random(struct tpm_buf *tb, unsigned char *buf, uint32_t len) +{ +	int ret; + +	INIT_BUF(tb); +	store16(tb, TPM_TAG_RQU_COMMAND); +	store32(tb, TPM_GETRANDOM_SIZE); +	store32(tb, TPM_ORD_GETRANDOM); +	store32(tb, len); +	ret = trusted_tpm_send(TPM_ANY_NUM, tb->data, sizeof tb->data); +	memcpy(buf, tb->data + TPM_GETRANDOM_SIZE, len); + +	return ret; +} + +static int my_get_random(unsigned char *buf, int len) +{ +	struct tpm_buf *tb; +	int ret; + +	tb = kzalloc(sizeof *tb, GFP_KERNEL); +	if (!tb) +		return -ENOMEM; +	ret = tpm_get_random(tb, buf, len); + +	kfree(tb); +	return ret; +} + +/* + * 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) +{ +	unsigned char hash[SHA1_DIGEST_SIZE]; + +	if (!capable(CAP_SYS_ADMIN)) +		return -EPERM; +	my_get_random(hash, SHA1_DIGEST_SIZE); +	return tpm_pcr_extend(TPM_ANY_NUM, pcrnum, hash) ? -EINVAL : 0; +} + +/* + * Create an object specific authorisation protocol (OSAP) session + */ +static int osap(struct tpm_buf *tb, struct osapsess *s, +		const unsigned char *key, const uint16_t type, +		const uint32_t handle) +{ +	unsigned char enonce[TPM_NONCE_SIZE]; +	unsigned char ononce[TPM_NONCE_SIZE]; +	int ret; + +	ret = tpm_get_random(tb, ononce, TPM_NONCE_SIZE); +	if (ret < 0) +		return ret; + +	INIT_BUF(tb); +	store16(tb, TPM_TAG_RQU_COMMAND); +	store32(tb, TPM_OSAP_SIZE); +	store32(tb, TPM_ORD_OSAP); +	store16(tb, type); +	store32(tb, handle); +	storebytes(tb, ononce, TPM_NONCE_SIZE); + +	ret = trusted_tpm_send(TPM_ANY_NUM, tb->data, MAX_BUF_SIZE); +	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); +	ret = TSS_rawhmac(s->secret, key, SHA1_DIGEST_SIZE, TPM_NONCE_SIZE, +			  enonce, TPM_NONCE_SIZE, ononce, 0, 0); +	return ret; +} + +/* + * Create an object independent authorisation protocol (oiap) session + */ +static int oiap(struct tpm_buf *tb, uint32_t *handle, unsigned char *nonce) +{ +	int ret; + +	INIT_BUF(tb); +	store16(tb, TPM_TAG_RQU_COMMAND); +	store32(tb, TPM_OIAP_SIZE); +	store32(tb, TPM_ORD_OIAP); +	ret = trusted_tpm_send(TPM_ANY_NUM, tb->data, MAX_BUF_SIZE); +	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 ret; +} + +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, const uint16_t keytype, +		    const uint32_t keyhandle, const unsigned char *keyauth, +		    const unsigned char *data, const uint32_t datalen, +		    unsigned char *blob, uint32_t *bloblen, +		    const unsigned char *blobauth, +		    const unsigned char *pcrinfo, const 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(sizeof *td, GFP_KERNEL); +	if (!td) +		return -ENOMEM; + +	/* get session for sealing key */ +	ret = osap(tb, &sess, keyauth, keytype, keyhandle); +	if (ret < 0) +		return ret; +	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); +	ret = TSS_sha1(td->xorwork, SHA1_DIGEST_SIZE * 2, td->xorhash); +	if (ret < 0) +		return ret; + +	ret = tpm_get_random(tb, td->nonceodd, TPM_NONCE_SIZE); +	if (ret < 0) +		return ret; +	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 */ +		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 */ +		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); +	} + +	/* build and send the TPM request packet */ +	INIT_BUF(tb); +	store16(tb, TPM_TAG_RQU_AUTH1_COMMAND); +	store32(tb, TPM_SEAL_SIZE + pcrinfosize + datalen); +	store32(tb, TPM_ORD_SEAL); +	store32(tb, keyhandle); +	storebytes(tb, td->encauth, SHA1_DIGEST_SIZE); +	store32(tb, pcrinfosize); +	storebytes(tb, pcrinfo, pcrinfosize); +	store32(tb, datalen); +	storebytes(tb, data, datalen); +	store32(tb, sess.handle); +	storebytes(tb, td->nonceodd, TPM_NONCE_SIZE); +	store8(tb, cont); +	storebytes(tb, td->pubauth, SHA1_DIGEST_SIZE); + +	ret = trusted_tpm_send(TPM_ANY_NUM, tb->data, MAX_BUF_SIZE); +	if (ret < 0) +		return ret; + +	/* 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 */ +	memcpy(blob, tb->data + TPM_DATA_OFFSET, storedsize); +	*bloblen = storedsize; +	return ret; +} + +/* + * use the AUTH2_COMMAND form of unseal, to authorize both key and blob + */ +static int tpm_unseal(struct tpm_buf *tb, +		      const uint32_t keyhandle, const unsigned char *keyauth, +		      const unsigned char *blob, const 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; +	uint32_t keyhndl; +	int ret; + +	/* sessions for unsealing key and data */ +	ret = oiap(tb, &authhandle1, enonce1); +	if (ret < 0) { +		pr_info("trusted_key: oiap failed (%d)\n", ret); +		return ret; +	} +	ret = oiap(tb, &authhandle2, enonce2); +	if (ret < 0) { +		pr_info("trusted_key: oiap failed (%d)\n", ret); +		return ret; +	} + +	ordinal = htonl(TPM_ORD_UNSEAL); +	keyhndl = htonl(SRKHANDLE); +	ret = tpm_get_random(tb, nonceodd, TPM_NONCE_SIZE); +	if (ret < 0) { +		pr_info("trusted_key: tpm_get_random failed (%d)\n", ret); +		return ret; +	} +	TSS_authhmac(authdata1, keyauth, TPM_NONCE_SIZE, +		     enonce1, nonceodd, cont, sizeof(uint32_t), +		     &ordinal, bloblen, blob, 0, 0); +	TSS_authhmac(authdata2, blobauth, TPM_NONCE_SIZE, +		     enonce2, nonceodd, cont, sizeof(uint32_t), +		     &ordinal, bloblen, blob, 0, 0); + +	/* build and send TPM request packet */ +	INIT_BUF(tb); +	store16(tb, TPM_TAG_RQU_AUTH2_COMMAND); +	store32(tb, TPM_UNSEAL_SIZE + bloblen); +	store32(tb, TPM_ORD_UNSEAL); +	store32(tb, keyhandle); +	storebytes(tb, blob, bloblen); +	store32(tb, authhandle1); +	storebytes(tb, nonceodd, TPM_NONCE_SIZE); +	store8(tb, cont); +	storebytes(tb, authdata1, SHA1_DIGEST_SIZE); +	store32(tb, authhandle2); +	storebytes(tb, nonceodd, TPM_NONCE_SIZE); +	store8(tb, cont); +	storebytes(tb, authdata2, SHA1_DIGEST_SIZE); + +	ret = trusted_tpm_send(TPM_ANY_NUM, tb->data, MAX_BUF_SIZE); +	if (ret < 0) { +		pr_info("trusted_key: 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("trusted_key: TSS_checkhmac2 failed (%d)\n", ret); +	memcpy(data, tb->data + TPM_DATA_OFFSET + sizeof(uint32_t), *datalen); +	return ret; +} + +/* + * Have the TPM seal(encrypt) the symmetric key + */ +static int key_seal(struct trusted_key_payload *p, +		    struct trusted_key_options *o) +{ +	struct tpm_buf *tb; +	int ret; + +	tb = kzalloc(sizeof *tb, GFP_KERNEL); +	if (!tb) +		return -ENOMEM; + +	/* 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("trusted_key: srkseal failed (%d)\n", ret); + +	kfree(tb); +	return ret; +} + +/* + * Have the TPM unseal(decrypt) the symmetric key + */ +static int key_unseal(struct trusted_key_payload *p, +		      struct trusted_key_options *o) +{ +	struct tpm_buf *tb; +	int ret; + +	tb = kzalloc(sizeof *tb, GFP_KERNEL); +	if (!tb) +		return -ENOMEM; + +	ret = tpm_unseal(tb, o->keyhandle, o->keyauth, p->blob, p->blob_len, +			 o->blobauth, p->key, &p->key_len); +	/* pull migratable flag out of sealed key */ +	p->migratable = p->key[--p->key_len]; + +	if (ret < 0) +		pr_info("trusted_key: srkunseal failed (%d)\n", ret); + +	kfree(tb); +	return ret; +} + +enum { +	Opt_err = -1, +	Opt_new, Opt_load, Opt_update, +	Opt_keyhandle, Opt_keyauth, Opt_blobauth, +	Opt_pcrinfo, Opt_pcrlock, Opt_migratable +}; + +static const match_table_t key_tokens = { +	{Opt_new, "new"}, +	{Opt_load, "load"}, +	{Opt_update, "update"}, +	{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_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; + +	while ((p = strsep(&c, " \t"))) { +		if (*p == '\0' || *p == ' ' || *p == '\t') +			continue; +		token = match_token(p, key_tokens, args); + +		switch (token) { +		case Opt_pcrinfo: +			opt->pcrinfo_len = strlen(args[0].from) / 2; +			if (opt->pcrinfo_len > MAX_PCRINFO_SIZE) +				return -EINVAL; +			hex2bin(opt->pcrinfo, args[0].from, opt->pcrinfo_len); +			break; +		case Opt_keyhandle: +			res = strict_strtoul(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; +			hex2bin(opt->keyauth, args[0].from, SHA1_DIGEST_SIZE); +			break; +		case Opt_blobauth: +			if (strlen(args[0].from) != 2 * SHA1_DIGEST_SIZE) +				return -EINVAL; +			hex2bin(opt->blobauth, args[0].from, SHA1_DIGEST_SIZE); +			break; +		case Opt_migratable: +			if (*args[0].from == '0') +				pay->migratable = 0; +			else +				return -EINVAL; +			break; +		case Opt_pcrlock: +			res = strict_strtoul(args[0].from, 10, &lock); +			if (res < 0) +				return -EINVAL; +			opt->pcrlock = lock; +			break; +		default: +			return -EINVAL; +		} +	} +	return 0; +} + +/* + * datablob_parse - parse the keyctl data and fill in the + * 		    payload and options structures + * + * On success returns 0, otherwise -EINVAL. + */ +static int datablob_parse(char *datablob, struct trusted_key_payload *p, +			  struct trusted_key_options *o) +{ +	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 = strict_strtol(c, 10, &keylen); +		if (ret < 0 || keylen < MIN_KEY_SIZE || keylen > MAX_KEY_SIZE) +			return -EINVAL; +		p->key_len = keylen; +		ret = getoptions(datablob, p, o); +		if (ret < 0) +			return ret; +		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; +		hex2bin(p->blob, c, p->blob_len); +		ret = getoptions(datablob, p, o); +		if (ret < 0) +			return ret; +		ret = Opt_load; +		break; +	case Opt_update: +		/* all arguments are options */ +		ret = getoptions(datablob, p, o); +		if (ret < 0) +			return ret; +		ret = Opt_update; +		break; +	case Opt_err: +		return -EINVAL; +		break; +	} +	return ret; +} + +static struct trusted_key_options *trusted_options_alloc(void) +{ +	struct trusted_key_options *options; + +	options = kzalloc(sizeof *options, GFP_KERNEL); +	if (!options) +		return options; + +	/* set any non-zero defaults */ +	options->keytype = SRK_keytype; +	options->keyhandle = SRKHANDLE; +	return options; +} + +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) +		return p; +	p = kzalloc(sizeof *p, GFP_KERNEL); + +	/* migratable by default */ +	p->migratable = 1; +	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, const void *data, +			       const size_t datalen) +{ +	struct trusted_key_payload *payload = NULL; +	struct trusted_key_options *options = NULL; +	char *datablob; +	int ret = 0; +	int key_cmd; + +	if (datalen <= 0 || datalen > 32767 || !data) +		return -EINVAL; + +	datablob = kmalloc(datalen + 1, GFP_KERNEL); +	if (!datablob) +		return -ENOMEM; +	memcpy(datablob, data, datalen); +	datablob[datalen] = '\0'; + +	options = trusted_options_alloc(); +	if (!options) { +		ret = -ENOMEM; +		goto out; +	} +	payload = trusted_payload_alloc(key); +	if (!payload) { +		ret = -ENOMEM; +		goto out; +	} + +	key_cmd = datablob_parse(datablob, payload, options); +	if (key_cmd < 0) { +		ret = key_cmd; +		goto out; +	} + +	dump_payload(payload); +	dump_options(options); + +	switch (key_cmd) { +	case Opt_load: +		ret = key_unseal(payload, options); +		dump_payload(payload); +		dump_options(options); +		if (ret < 0) +			pr_info("trusted_key: key_unseal failed (%d)\n", ret); +		break; +	case Opt_new: +		ret = my_get_random(payload->key, payload->key_len); +		if (ret < 0) { +			pr_info("trusted_key: key_create failed (%d)\n", ret); +			goto out; +		} +		ret = key_seal(payload, options); +		if (ret < 0) +			pr_info("trusted_key: key_seal failed (%d)\n", ret); +		break; +	default: +		ret = -EINVAL; +		goto out; +	} +	if (!ret && options->pcrlock) +		ret = pcrlock(options->pcrlock); +out: +	kfree(datablob); +	kfree(options); +	if (!ret) +		rcu_assign_pointer(key->payload.data, payload); +	else +		kfree(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); +	memset(p->key, 0, p->key_len); +	kfree(p); +} + +/* + * trusted_update - reseal an existing key with new PCR values + */ +static int trusted_update(struct key *key, const void *data, +			  const size_t datalen) +{ +	struct trusted_key_payload *p = key->payload.data; +	struct trusted_key_payload *new_p; +	struct trusted_key_options *new_o; +	char *datablob; +	int ret = 0; + +	if (!p->migratable) +		return -EPERM; +	if (datalen <= 0 || datalen > 32767 || !data) +		return -EINVAL; + +	datablob = kmalloc(datalen + 1, GFP_KERNEL); +	if (!datablob) +		return -ENOMEM; +	new_o = trusted_options_alloc(); +	if (!new_o) { +		ret = -ENOMEM; +		goto out; +	} +	new_p = trusted_payload_alloc(key); +	if (!new_p) { +		ret = -ENOMEM; +		goto out; +	} + +	memcpy(datablob, data, datalen); +	datablob[datalen] = '\0'; +	ret = datablob_parse(datablob, new_p, new_o); +	if (ret != Opt_update) { +		ret = -EINVAL; +		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 = key_seal(new_p, new_o); +	if (ret < 0) { +		pr_info("trusted_key: key_seal failed (%d)\n", ret); +		kfree(new_p); +		goto out; +	} +	if (new_o->pcrlock) { +		ret = pcrlock(new_o->pcrlock); +		if (ret < 0) { +			pr_info("trusted_key: pcrlock failed (%d)\n", ret); +			kfree(new_p); +			goto out; +		} +	} +	rcu_assign_pointer(key->payload.data, new_p); +	call_rcu(&p->rcu, trusted_rcu_free); +out: +	kfree(datablob); +	kfree(new_o); +	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 __user *buffer, +			 size_t buflen) +{ +	struct trusted_key_payload *p; +	char *ascii_buf; +	char *bufp; +	int i; + +	p = rcu_dereference_protected(key->payload.data, +			rwsem_is_locked(&((struct key *)key)->sem)); +	if (!p) +		return -EINVAL; +	if (!buffer || buflen <= 0) +		return 2 * p->blob_len; +	ascii_buf = kmalloc(2 * p->blob_len, GFP_KERNEL); +	if (!ascii_buf) +		return -ENOMEM; + +	bufp = ascii_buf; +	for (i = 0; i < p->blob_len; i++) +		bufp = pack_hex_byte(bufp, p->blob[i]); +	if ((copy_to_user(buffer, ascii_buf, 2 * p->blob_len)) != 0) { +		kfree(ascii_buf); +		return -EFAULT; +	} +	kfree(ascii_buf); +	return 2 * p->blob_len; +} + +/* + * trusted_destroy - before freeing the key, clear the decrypted data + */ +static void trusted_destroy(struct key *key) +{ +	struct trusted_key_payload *p = key->payload.data; + +	if (!p) +		return; +	memset(p->key, 0, p->key_len); +	kfree(key->payload.data); +} + +struct key_type key_type_trusted = { +	.name = "trusted", +	.instantiate = trusted_instantiate, +	.update = trusted_update, +	.match = user_match, +	.destroy = trusted_destroy, +	.describe = user_describe, +	.read = trusted_read, +}; + +EXPORT_SYMBOL_GPL(key_type_trusted); + +static void trusted_shash_release(void) +{ +	if (hashalg) +		crypto_free_shash(hashalg); +	if (hmacalg) +		crypto_free_shash(hmacalg); +} + +static int __init trusted_shash_alloc(void) +{ +	int ret; + +	hmacalg = crypto_alloc_shash(hmac_alg, 0, CRYPTO_ALG_ASYNC); +	if (IS_ERR(hmacalg)) { +		pr_info("trusted_key: could not allocate crypto %s\n", +			hmac_alg); +		return PTR_ERR(hmacalg); +	} + +	hashalg = crypto_alloc_shash(hash_alg, 0, CRYPTO_ALG_ASYNC); +	if (IS_ERR(hashalg)) { +		pr_info("trusted_key: could not allocate crypto %s\n", +			hash_alg); +		ret = PTR_ERR(hashalg); +		goto hashalg_fail; +	} + +	return 0; + +hashalg_fail: +	crypto_free_shash(hmacalg); +	return ret; +} + +static int __init init_trusted(void) +{ +	int ret; + +	ret = trusted_shash_alloc(); +	if (ret < 0) +		return ret; +	ret = register_key_type(&key_type_trusted); +	if (ret < 0) +		trusted_shash_release(); +	return ret; +} + +static void __exit cleanup_trusted(void) +{ +	trusted_shash_release(); +	unregister_key_type(&key_type_trusted); +} + +late_initcall(init_trusted); +module_exit(cleanup_trusted); + +MODULE_LICENSE("GPL"); | 
