From 4b174b6d281f5c87234fc65bafc02877f565c5cf Mon Sep 17 00:00:00 2001 From: Mimi Zohar Date: Tue, 18 Jan 2011 09:07:11 -0500 Subject: trusted-keys: rename trusted_defined files to trusted Rename trusted_defined.c and trusted_defined.h files to trusted.c and trusted.h, respectively. Based on request from David Howells. Signed-off-by: Mimi Zohar Acked-by: David Howells Signed-off-by: James Morris --- security/keys/Makefile | 2 +- security/keys/trusted.c | 1180 +++++++++++++++++++++++++++++++++++++++ security/keys/trusted.h | 134 +++++ security/keys/trusted_defined.c | 1180 --------------------------------------- security/keys/trusted_defined.h | 134 ----- 5 files changed, 1315 insertions(+), 1315 deletions(-) create mode 100644 security/keys/trusted.c create mode 100644 security/keys/trusted.h delete mode 100644 security/keys/trusted_defined.c delete mode 100644 security/keys/trusted_defined.h (limited to 'security') diff --git a/security/keys/Makefile b/security/keys/Makefile index 6c941050f57..ad8da87653c 100644 --- a/security/keys/Makefile +++ b/security/keys/Makefile @@ -13,7 +13,7 @@ obj-y := \ request_key_auth.o \ user_defined.o -obj-$(CONFIG_TRUSTED_KEYS) += trusted_defined.o +obj-$(CONFIG_TRUSTED_KEYS) += trusted.o obj-$(CONFIG_ENCRYPTED_KEYS) += encrypted_defined.o obj-$(CONFIG_KEYS_COMPAT) += compat.o obj-$(CONFIG_PROC_FS) += proc.o diff --git a/security/keys/trusted.c b/security/keys/trusted.c new file mode 100644 index 00000000000..3066f56c767 --- /dev/null +++ b/security/keys/trusted.c @@ -0,0 +1,1180 @@ +/* + * Copyright (C) 2010 IBM Corporation + * + * Author: + * David Safford + * + * 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "trusted.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, 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, + 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) { + ret = -EINVAL; + break; + } + ret = crypto_shash_update(&sdesc->shash, data, dlen); + if (ret < 0) + break; + } + va_end(argp); + if (!ret) + ret = crypto_shash_final(&sdesc->shash, digest); +out: + kfree(sdesc); + 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 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 *); + if (!data) { + ret = -EINVAL; + break; + } + ret = crypto_shash_update(&sdesc->shash, data, dlen); + if (ret < 0) + break; + } + va_end(argp); + if (!ret) + ret = crypto_shash_final(&sdesc->shash, 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); +out: + kfree(sdesc); + 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 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) + break; + } + va_end(argp); + if (!ret) + 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 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 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) + break; + } + va_end(argp); + if (!ret) + 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 (ret < 0) + goto out; + 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 (ret < 0) + goto out; + 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); + if (!ret) + 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 = kmalloc(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]; + int ret; + + if (!capable(CAP_SYS_ADMIN)) + return -EPERM; + ret = my_get_random(hash, SHA1_DIGEST_SIZE); + if (ret < 0) + return ret; + 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, uint16_t type, 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); + 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; + + 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 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(sizeof *td, GFP_KERNEL); + 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); + ret = TSS_sha1(td->xorwork, SHA1_DIGEST_SIZE * 2, td->xorhash); + if (ret < 0) + goto out; + + ret = tpm_get_random(tb, td->nonceodd, TPM_NONCE_SIZE); + if (ret < 0) + 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 */ + 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) + 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(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; + 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; + } + 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 */ + 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); + 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) +{ + 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); + if (ret < 0) + pr_info("trusted_key: srkunseal failed (%d)\n", ret); + else + /* pull migratable flag out of sealed key */ + p->migratable = p->key[--p->key_len]; + + 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) { + /* 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); + if (p) + p->migratable = 1; /* migratable by default */ + 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, + 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, 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"); diff --git a/security/keys/trusted.h b/security/keys/trusted.h new file mode 100644 index 00000000000..3249fbd2b65 --- /dev/null +++ b/security/keys/trusted.h @@ -0,0 +1,134 @@ +#ifndef __TRUSTED_KEY_H +#define __TRUSTED_KEY_H + +/* implementation specific TPM constants */ +#define MAX_PCRINFO_SIZE 64 +#define MAX_BUF_SIZE 512 +#define TPM_GETRANDOM_SIZE 14 +#define TPM_OSAP_SIZE 36 +#define TPM_OIAP_SIZE 10 +#define TPM_SEAL_SIZE 87 +#define TPM_UNSEAL_SIZE 104 +#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 tpm_buf { + int len; + unsigned char data[MAX_BUF_SIZE]; +}; + +#define INIT_BUF(tb) (tb->len = 0) + +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 +}; + +struct trusted_key_options { + uint16_t keytype; + uint32_t keyhandle; + unsigned char keyauth[SHA1_DIGEST_SIZE]; + unsigned char blobauth[SHA1_DIGEST_SIZE]; + uint32_t pcrinfo_len; + unsigned char pcrinfo[MAX_PCRINFO_SIZE]; + int pcrlock; +}; + +#define TPM_DEBUG 0 + +#if TPM_DEBUG +static inline void dump_options(struct trusted_key_options *o) +{ + pr_info("trusted_key: sealing key type %d\n", o->keytype); + pr_info("trusted_key: sealing key handle %0X\n", o->keyhandle); + pr_info("trusted_key: pcrlock %d\n", o->pcrlock); + pr_info("trusted_key: pcrinfo %d\n", o->pcrinfo_len); + print_hex_dump(KERN_INFO, "pcrinfo ", DUMP_PREFIX_NONE, + 16, 1, o->pcrinfo, o->pcrinfo_len, 0); +} + +static inline void dump_payload(struct trusted_key_payload *p) +{ + pr_info("trusted_key: key_len %d\n", p->key_len); + print_hex_dump(KERN_INFO, "key ", DUMP_PREFIX_NONE, + 16, 1, p->key, p->key_len, 0); + pr_info("trusted_key: bloblen %d\n", p->blob_len); + print_hex_dump(KERN_INFO, "blob ", DUMP_PREFIX_NONE, + 16, 1, p->blob, p->blob_len, 0); + pr_info("trusted_key: migratable %d\n", p->migratable); +} + +static inline void dump_sess(struct osapsess *s) +{ + print_hex_dump(KERN_INFO, "trusted-key: handle ", DUMP_PREFIX_NONE, + 16, 1, &s->handle, 4, 0); + pr_info("trusted-key: secret:\n"); + print_hex_dump(KERN_INFO, "", DUMP_PREFIX_NONE, + 16, 1, &s->secret, SHA1_DIGEST_SIZE, 0); + pr_info("trusted-key: enonce:\n"); + print_hex_dump(KERN_INFO, "", DUMP_PREFIX_NONE, + 16, 1, &s->enonce, SHA1_DIGEST_SIZE, 0); +} + +static inline void dump_tpm_buf(unsigned char *buf) +{ + int len; + + pr_info("\ntrusted-key: tpm buffer\n"); + len = LOAD32(buf, TPM_SIZE_OFFSET); + print_hex_dump(KERN_INFO, "", DUMP_PREFIX_NONE, 16, 1, buf, len, 0); +} +#else +static inline void dump_options(struct trusted_key_options *o) +{ +} + +static inline void dump_payload(struct trusted_key_payload *p) +{ +} + +static inline void dump_sess(struct osapsess *s) +{ +} + +static inline void dump_tpm_buf(unsigned char *buf) +{ +} +#endif + +static inline void store8(struct tpm_buf *buf, const unsigned char value) +{ + buf->data[buf->len++] = value; +} + +static inline void store16(struct tpm_buf *buf, const uint16_t value) +{ + *(uint16_t *) & buf->data[buf->len] = htons(value); + buf->len += sizeof value; +} + +static inline void store32(struct tpm_buf *buf, const uint32_t value) +{ + *(uint32_t *) & buf->data[buf->len] = htonl(value); + buf->len += sizeof value; +} + +static inline void storebytes(struct tpm_buf *buf, const unsigned char *in, + const int len) +{ + memcpy(buf->data + buf->len, in, len); + buf->len += len; +} +#endif diff --git a/security/keys/trusted_defined.c b/security/keys/trusted_defined.c deleted file mode 100644 index 2836c6dc18a..00000000000 --- a/security/keys/trusted_defined.c +++ /dev/null @@ -1,1180 +0,0 @@ -/* - * Copyright (C) 2010 IBM Corporation - * - * Author: - * David Safford - * - * 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 -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#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, 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, - 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) { - ret = -EINVAL; - break; - } - ret = crypto_shash_update(&sdesc->shash, data, dlen); - if (ret < 0) - break; - } - va_end(argp); - if (!ret) - ret = crypto_shash_final(&sdesc->shash, digest); -out: - kfree(sdesc); - 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 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 *); - if (!data) { - ret = -EINVAL; - break; - } - ret = crypto_shash_update(&sdesc->shash, data, dlen); - if (ret < 0) - break; - } - va_end(argp); - if (!ret) - ret = crypto_shash_final(&sdesc->shash, 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); -out: - kfree(sdesc); - 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 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) - break; - } - va_end(argp); - if (!ret) - 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 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 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) - break; - } - va_end(argp); - if (!ret) - 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 (ret < 0) - goto out; - 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 (ret < 0) - goto out; - 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); - if (!ret) - 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 = kmalloc(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]; - int ret; - - if (!capable(CAP_SYS_ADMIN)) - return -EPERM; - ret = my_get_random(hash, SHA1_DIGEST_SIZE); - if (ret < 0) - return ret; - 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, uint16_t type, 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); - 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; - - 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 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(sizeof *td, GFP_KERNEL); - 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); - ret = TSS_sha1(td->xorwork, SHA1_DIGEST_SIZE * 2, td->xorhash); - if (ret < 0) - goto out; - - ret = tpm_get_random(tb, td->nonceodd, TPM_NONCE_SIZE); - if (ret < 0) - 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 */ - 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) - 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(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; - 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; - } - 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 */ - 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); - 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) -{ - 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); - if (ret < 0) - pr_info("trusted_key: srkunseal failed (%d)\n", ret); - else - /* pull migratable flag out of sealed key */ - p->migratable = p->key[--p->key_len]; - - 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) { - /* 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); - if (p) - p->migratable = 1; /* migratable by default */ - 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, - 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, 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"); diff --git a/security/keys/trusted_defined.h b/security/keys/trusted_defined.h deleted file mode 100644 index 3249fbd2b65..00000000000 --- a/security/keys/trusted_defined.h +++ /dev/null @@ -1,134 +0,0 @@ -#ifndef __TRUSTED_KEY_H -#define __TRUSTED_KEY_H - -/* implementation specific TPM constants */ -#define MAX_PCRINFO_SIZE 64 -#define MAX_BUF_SIZE 512 -#define TPM_GETRANDOM_SIZE 14 -#define TPM_OSAP_SIZE 36 -#define TPM_OIAP_SIZE 10 -#define TPM_SEAL_SIZE 87 -#define TPM_UNSEAL_SIZE 104 -#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 tpm_buf { - int len; - unsigned char data[MAX_BUF_SIZE]; -}; - -#define INIT_BUF(tb) (tb->len = 0) - -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 -}; - -struct trusted_key_options { - uint16_t keytype; - uint32_t keyhandle; - unsigned char keyauth[SHA1_DIGEST_SIZE]; - unsigned char blobauth[SHA1_DIGEST_SIZE]; - uint32_t pcrinfo_len; - unsigned char pcrinfo[MAX_PCRINFO_SIZE]; - int pcrlock; -}; - -#define TPM_DEBUG 0 - -#if TPM_DEBUG -static inline void dump_options(struct trusted_key_options *o) -{ - pr_info("trusted_key: sealing key type %d\n", o->keytype); - pr_info("trusted_key: sealing key handle %0X\n", o->keyhandle); - pr_info("trusted_key: pcrlock %d\n", o->pcrlock); - pr_info("trusted_key: pcrinfo %d\n", o->pcrinfo_len); - print_hex_dump(KERN_INFO, "pcrinfo ", DUMP_PREFIX_NONE, - 16, 1, o->pcrinfo, o->pcrinfo_len, 0); -} - -static inline void dump_payload(struct trusted_key_payload *p) -{ - pr_info("trusted_key: key_len %d\n", p->key_len); - print_hex_dump(KERN_INFO, "key ", DUMP_PREFIX_NONE, - 16, 1, p->key, p->key_len, 0); - pr_info("trusted_key: bloblen %d\n", p->blob_len); - print_hex_dump(KERN_INFO, "blob ", DUMP_PREFIX_NONE, - 16, 1, p->blob, p->blob_len, 0); - pr_info("trusted_key: migratable %d\n", p->migratable); -} - -static inline void dump_sess(struct osapsess *s) -{ - print_hex_dump(KERN_INFO, "trusted-key: handle ", DUMP_PREFIX_NONE, - 16, 1, &s->handle, 4, 0); - pr_info("trusted-key: secret:\n"); - print_hex_dump(KERN_INFO, "", DUMP_PREFIX_NONE, - 16, 1, &s->secret, SHA1_DIGEST_SIZE, 0); - pr_info("trusted-key: enonce:\n"); - print_hex_dump(KERN_INFO, "", DUMP_PREFIX_NONE, - 16, 1, &s->enonce, SHA1_DIGEST_SIZE, 0); -} - -static inline void dump_tpm_buf(unsigned char *buf) -{ - int len; - - pr_info("\ntrusted-key: tpm buffer\n"); - len = LOAD32(buf, TPM_SIZE_OFFSET); - print_hex_dump(KERN_INFO, "", DUMP_PREFIX_NONE, 16, 1, buf, len, 0); -} -#else -static inline void dump_options(struct trusted_key_options *o) -{ -} - -static inline void dump_payload(struct trusted_key_payload *p) -{ -} - -static inline void dump_sess(struct osapsess *s) -{ -} - -static inline void dump_tpm_buf(unsigned char *buf) -{ -} -#endif - -static inline void store8(struct tpm_buf *buf, const unsigned char value) -{ - buf->data[buf->len++] = value; -} - -static inline void store16(struct tpm_buf *buf, const uint16_t value) -{ - *(uint16_t *) & buf->data[buf->len] = htons(value); - buf->len += sizeof value; -} - -static inline void store32(struct tpm_buf *buf, const uint32_t value) -{ - *(uint32_t *) & buf->data[buf->len] = htonl(value); - buf->len += sizeof value; -} - -static inline void storebytes(struct tpm_buf *buf, const unsigned char *in, - const int len) -{ - memcpy(buf->data + buf->len, in, len); - buf->len += len; -} -#endif -- cgit v1.2.3-70-g09d2 From b9703449347603289cac0bd04e574ac2e777275d Mon Sep 17 00:00:00 2001 From: Mimi Zohar Date: Tue, 18 Jan 2011 09:07:12 -0500 Subject: encrypted-keys: rename encrypted_defined files to encrypted Rename encrypted_defined.c and encrypted_defined.h files to encrypted.c and encrypted.h, respectively. Based on request from David Howells. Signed-off-by: Mimi Zohar Acked-by: David Howells Signed-off-by: James Morris --- security/keys/Makefile | 2 +- security/keys/encrypted.c | 903 ++++++++++++++++++++++++++++++++++++++ security/keys/encrypted.h | 54 +++ security/keys/encrypted_defined.c | 902 ------------------------------------- security/keys/encrypted_defined.h | 54 --- 5 files changed, 958 insertions(+), 957 deletions(-) create mode 100644 security/keys/encrypted.c create mode 100644 security/keys/encrypted.h delete mode 100644 security/keys/encrypted_defined.c delete mode 100644 security/keys/encrypted_defined.h (limited to 'security') diff --git a/security/keys/Makefile b/security/keys/Makefile index ad8da87653c..1bf090a885f 100644 --- a/security/keys/Makefile +++ b/security/keys/Makefile @@ -14,7 +14,7 @@ obj-y := \ user_defined.o obj-$(CONFIG_TRUSTED_KEYS) += trusted.o -obj-$(CONFIG_ENCRYPTED_KEYS) += encrypted_defined.o +obj-$(CONFIG_ENCRYPTED_KEYS) += encrypted.o obj-$(CONFIG_KEYS_COMPAT) += compat.o obj-$(CONFIG_PROC_FS) += proc.o obj-$(CONFIG_SYSCTL) += sysctl.o diff --git a/security/keys/encrypted.c b/security/keys/encrypted.c new file mode 100644 index 00000000000..9e7e4ce3fae --- /dev/null +++ b/security/keys/encrypted.c @@ -0,0 +1,903 @@ +/* + * Copyright (C) 2010 IBM Corporation + * + * Author: + * Mimi Zohar + * + * 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "encrypted.h" + +static const char KEY_TRUSTED_PREFIX[] = "trusted:"; +static const char KEY_USER_PREFIX[] = "user:"; +static const char hash_alg[] = "sha256"; +static const char hmac_alg[] = "hmac(sha256)"; +static const char blkcipher_alg[] = "cbc(aes)"; +static unsigned int ivsize; +static int blksize; + +#define KEY_TRUSTED_PREFIX_LEN (sizeof (KEY_TRUSTED_PREFIX) - 1) +#define KEY_USER_PREFIX_LEN (sizeof (KEY_USER_PREFIX) - 1) +#define HASH_SIZE SHA256_DIGEST_SIZE +#define MAX_DATA_SIZE 4096 +#define MIN_DATA_SIZE 20 + +struct sdesc { + struct shash_desc shash; + char ctx[]; +}; + +static struct crypto_shash *hashalg; +static struct crypto_shash *hmacalg; + +enum { + Opt_err = -1, 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} +}; + +static int aes_get_sizes(void) +{ + struct crypto_blkcipher *tfm; + + tfm = crypto_alloc_blkcipher(blkcipher_alg, 0, CRYPTO_ALG_ASYNC); + if (IS_ERR(tfm)) { + pr_err("encrypted_key: failed to alloc_cipher (%ld)\n", + PTR_ERR(tfm)); + return PTR_ERR(tfm); + } + ivsize = crypto_blkcipher_ivsize(tfm); + blksize = crypto_blkcipher_blocksize(tfm); + crypto_free_blkcipher(tfm); + return 0; +} + +/* + * valid_master_desc - verify the 'key-type:desc' of a new/updated master-key + * + * key-type:= "trusted:" | "encrypted:" + * desc:= master-key description + * + * Verify that 'key-type' is valid and that 'desc' exists. On key update, + * only the master key description is permitted to change, not the key-type. + * The key-type remains constant. + * + * On success returns 0, otherwise -EINVAL. + */ +static int valid_master_desc(const char *new_desc, const char *orig_desc) +{ + if (!memcmp(new_desc, KEY_TRUSTED_PREFIX, KEY_TRUSTED_PREFIX_LEN)) { + if (strlen(new_desc) == KEY_TRUSTED_PREFIX_LEN) + goto out; + if (orig_desc) + if (memcmp(new_desc, orig_desc, KEY_TRUSTED_PREFIX_LEN)) + goto out; + } else if (!memcmp(new_desc, KEY_USER_PREFIX, KEY_USER_PREFIX_LEN)) { + if (strlen(new_desc) == KEY_USER_PREFIX_LEN) + goto out; + if (orig_desc) + if (memcmp(new_desc, orig_desc, KEY_USER_PREFIX_LEN)) + goto out; + } else + goto out; + return 0; +out: + return -EINVAL; +} + +/* + * datablob_parse - parse the keyctl data + * + * datablob format: + * new + * load + * update + * + * Tokenizes a copy of the keyctl data, returning a pointer to each token, + * which is null terminated. + * + * On success returns 0, otherwise -EINVAL. + */ +static int datablob_parse(char *datablob, char **master_desc, + char **decrypted_datalen, char **hex_encoded_iv) +{ + substring_t args[MAX_OPT_ARGS]; + int ret = -EINVAL; + int key_cmd; + char *p; + + p = strsep(&datablob, " \t"); + if (!p) + return ret; + key_cmd = match_token(p, key_tokens, args); + + *master_desc = strsep(&datablob, " \t"); + if (!*master_desc) + goto out; + + if (valid_master_desc(*master_desc, NULL) < 0) + goto out; + + if (decrypted_datalen) { + *decrypted_datalen = strsep(&datablob, " \t"); + if (!*decrypted_datalen) + goto out; + } + + switch (key_cmd) { + case Opt_new: + if (!decrypted_datalen) + break; + ret = 0; + break; + case Opt_load: + if (!decrypted_datalen) + break; + *hex_encoded_iv = strsep(&datablob, " \t"); + if (!*hex_encoded_iv) + break; + ret = 0; + break; + case Opt_update: + if (decrypted_datalen) + break; + ret = 0; + break; + case Opt_err: + break; + } +out: + return ret; +} + +/* + * datablob_format - format as an ascii string, before copying to userspace + */ +static char *datablob_format(struct encrypted_key_payload *epayload, + size_t asciiblob_len) +{ + char *ascii_buf, *bufp; + u8 *iv = epayload->iv; + int len; + int i; + + ascii_buf = kmalloc(asciiblob_len + 1, GFP_KERNEL); + if (!ascii_buf) + goto out; + + ascii_buf[asciiblob_len] = '\0'; + + /* copy datablob master_desc and datalen strings */ + len = sprintf(ascii_buf, "%s %s ", epayload->master_desc, + epayload->datalen); + + /* convert the hex encoded iv, encrypted-data and HMAC to ascii */ + bufp = &ascii_buf[len]; + for (i = 0; i < (asciiblob_len - len) / 2; i++) + bufp = pack_hex_byte(bufp, iv[i]); +out: + return ascii_buf; +} + +/* + * request_trusted_key - request the trusted key + * + * Trusted keys are sealed to PCRs and other metadata. Although userspace + * manages both trusted/encrypted key-types, like the encrypted key type + * data, trusted key type data is not visible decrypted from userspace. + */ +static struct key *request_trusted_key(const char *trusted_desc, + u8 **master_key, size_t *master_keylen) +{ + struct trusted_key_payload *tpayload; + struct key *tkey; + + tkey = request_key(&key_type_trusted, trusted_desc, NULL); + if (IS_ERR(tkey)) + goto error; + + down_read(&tkey->sem); + tpayload = rcu_dereference(tkey->payload.data); + *master_key = tpayload->key; + *master_keylen = tpayload->key_len; +error: + return tkey; +} + +/* + * request_user_key - request the user key + * + * Use a user provided key to encrypt/decrypt an encrypted-key. + */ +static struct key *request_user_key(const char *master_desc, u8 **master_key, + size_t *master_keylen) +{ + struct user_key_payload *upayload; + struct key *ukey; + + ukey = request_key(&key_type_user, master_desc, NULL); + if (IS_ERR(ukey)) + goto error; + + down_read(&ukey->sem); + upayload = rcu_dereference(ukey->payload.data); + *master_key = upayload->data; + *master_keylen = upayload->datalen; +error: + return ukey; +} + +static struct sdesc *alloc_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 calc_hmac(u8 *digest, const u8 *key, unsigned int keylen, + const u8 *buf, unsigned int buflen) +{ + struct sdesc *sdesc; + int ret; + + sdesc = alloc_sdesc(hmacalg); + if (IS_ERR(sdesc)) { + pr_info("encrypted_key: can't alloc %s\n", hmac_alg); + return PTR_ERR(sdesc); + } + + ret = crypto_shash_setkey(hmacalg, key, keylen); + if (!ret) + ret = crypto_shash_digest(&sdesc->shash, buf, buflen, digest); + kfree(sdesc); + return ret; +} + +static int calc_hash(u8 *digest, const u8 *buf, unsigned int buflen) +{ + struct sdesc *sdesc; + int ret; + + sdesc = alloc_sdesc(hashalg); + if (IS_ERR(sdesc)) { + pr_info("encrypted_key: can't alloc %s\n", hash_alg); + return PTR_ERR(sdesc); + } + + ret = crypto_shash_digest(&sdesc->shash, buf, buflen, digest); + kfree(sdesc); + return ret; +} + +enum derived_key_type { ENC_KEY, AUTH_KEY }; + +/* Derive authentication/encryption key from trusted key */ +static int get_derived_key(u8 *derived_key, enum derived_key_type key_type, + const u8 *master_key, size_t master_keylen) +{ + u8 *derived_buf; + unsigned int derived_buf_len; + int ret; + + derived_buf_len = strlen("AUTH_KEY") + 1 + master_keylen; + if (derived_buf_len < HASH_SIZE) + derived_buf_len = HASH_SIZE; + + derived_buf = kzalloc(derived_buf_len, GFP_KERNEL); + if (!derived_buf) { + pr_err("encrypted_key: out of memory\n"); + return -ENOMEM; + } + if (key_type) + strcpy(derived_buf, "AUTH_KEY"); + else + strcpy(derived_buf, "ENC_KEY"); + + memcpy(derived_buf + strlen(derived_buf) + 1, master_key, + master_keylen); + ret = calc_hash(derived_key, derived_buf, derived_buf_len); + kfree(derived_buf); + return ret; +} + +static int init_blkcipher_desc(struct blkcipher_desc *desc, const u8 *key, + unsigned int key_len, const u8 *iv, + unsigned int ivsize) +{ + int ret; + + desc->tfm = crypto_alloc_blkcipher(blkcipher_alg, 0, CRYPTO_ALG_ASYNC); + if (IS_ERR(desc->tfm)) { + pr_err("encrypted_key: failed to load %s transform (%ld)\n", + blkcipher_alg, PTR_ERR(desc->tfm)); + return PTR_ERR(desc->tfm); + } + desc->flags = 0; + + ret = crypto_blkcipher_setkey(desc->tfm, key, key_len); + if (ret < 0) { + pr_err("encrypted_key: failed to setkey (%d)\n", ret); + crypto_free_blkcipher(desc->tfm); + return ret; + } + crypto_blkcipher_set_iv(desc->tfm, iv, ivsize); + return 0; +} + +static struct key *request_master_key(struct encrypted_key_payload *epayload, + u8 **master_key, size_t *master_keylen) +{ + struct key *mkey = NULL; + + if (!strncmp(epayload->master_desc, KEY_TRUSTED_PREFIX, + KEY_TRUSTED_PREFIX_LEN)) { + mkey = request_trusted_key(epayload->master_desc + + KEY_TRUSTED_PREFIX_LEN, + master_key, master_keylen); + } else if (!strncmp(epayload->master_desc, KEY_USER_PREFIX, + KEY_USER_PREFIX_LEN)) { + mkey = request_user_key(epayload->master_desc + + KEY_USER_PREFIX_LEN, + master_key, master_keylen); + } else + goto out; + + if (IS_ERR(mkey)) + pr_info("encrypted_key: key %s not found", + epayload->master_desc); + if (mkey) + dump_master_key(*master_key, *master_keylen); +out: + return mkey; +} + +/* Before returning data to userspace, encrypt decrypted data. */ +static int derived_key_encrypt(struct encrypted_key_payload *epayload, + const u8 *derived_key, + unsigned int derived_keylen) +{ + struct scatterlist sg_in[2]; + struct scatterlist sg_out[1]; + struct blkcipher_desc desc; + unsigned int encrypted_datalen; + unsigned int padlen; + char pad[16]; + int ret; + + encrypted_datalen = roundup(epayload->decrypted_datalen, blksize); + padlen = encrypted_datalen - epayload->decrypted_datalen; + + ret = init_blkcipher_desc(&desc, derived_key, derived_keylen, + epayload->iv, ivsize); + if (ret < 0) + goto out; + dump_decrypted_data(epayload); + + memset(pad, 0, sizeof pad); + sg_init_table(sg_in, 2); + sg_set_buf(&sg_in[0], epayload->decrypted_data, + epayload->decrypted_datalen); + sg_set_buf(&sg_in[1], pad, padlen); + + sg_init_table(sg_out, 1); + sg_set_buf(sg_out, epayload->encrypted_data, encrypted_datalen); + + ret = crypto_blkcipher_encrypt(&desc, sg_out, sg_in, encrypted_datalen); + crypto_free_blkcipher(desc.tfm); + if (ret < 0) + pr_err("encrypted_key: failed to encrypt (%d)\n", ret); + else + dump_encrypted_data(epayload, encrypted_datalen); +out: + return ret; +} + +static int datablob_hmac_append(struct encrypted_key_payload *epayload, + const u8 *master_key, size_t master_keylen) +{ + u8 derived_key[HASH_SIZE]; + u8 *digest; + int ret; + + ret = get_derived_key(derived_key, AUTH_KEY, master_key, master_keylen); + if (ret < 0) + goto out; + + digest = epayload->master_desc + epayload->datablob_len; + ret = calc_hmac(digest, derived_key, sizeof derived_key, + epayload->master_desc, epayload->datablob_len); + if (!ret) + dump_hmac(NULL, digest, HASH_SIZE); +out: + return ret; +} + +/* verify HMAC before decrypting encrypted key */ +static int datablob_hmac_verify(struct encrypted_key_payload *epayload, + const u8 *master_key, size_t master_keylen) +{ + u8 derived_key[HASH_SIZE]; + u8 digest[HASH_SIZE]; + int ret; + + ret = get_derived_key(derived_key, AUTH_KEY, master_key, master_keylen); + if (ret < 0) + goto out; + + ret = calc_hmac(digest, derived_key, sizeof derived_key, + epayload->master_desc, epayload->datablob_len); + if (ret < 0) + goto out; + ret = memcmp(digest, epayload->master_desc + epayload->datablob_len, + sizeof digest); + if (ret) { + ret = -EINVAL; + dump_hmac("datablob", + epayload->master_desc + epayload->datablob_len, + HASH_SIZE); + dump_hmac("calc", digest, HASH_SIZE); + } +out: + return ret; +} + +static int derived_key_decrypt(struct encrypted_key_payload *epayload, + const u8 *derived_key, + unsigned int derived_keylen) +{ + struct scatterlist sg_in[1]; + struct scatterlist sg_out[2]; + struct blkcipher_desc desc; + unsigned int encrypted_datalen; + char pad[16]; + int ret; + + encrypted_datalen = roundup(epayload->decrypted_datalen, blksize); + ret = init_blkcipher_desc(&desc, derived_key, derived_keylen, + epayload->iv, ivsize); + if (ret < 0) + goto out; + dump_encrypted_data(epayload, encrypted_datalen); + + memset(pad, 0, sizeof pad); + sg_init_table(sg_in, 1); + sg_init_table(sg_out, 2); + sg_set_buf(sg_in, epayload->encrypted_data, encrypted_datalen); + sg_set_buf(&sg_out[0], epayload->decrypted_data, + epayload->decrypted_datalen); + sg_set_buf(&sg_out[1], pad, sizeof pad); + + ret = crypto_blkcipher_decrypt(&desc, sg_out, sg_in, encrypted_datalen); + crypto_free_blkcipher(desc.tfm); + if (ret < 0) + goto out; + dump_decrypted_data(epayload); +out: + return ret; +} + +/* Allocate memory for decrypted key and datablob. */ +static struct encrypted_key_payload *encrypted_key_alloc(struct key *key, + const char *master_desc, + const char *datalen) +{ + struct encrypted_key_payload *epayload = NULL; + unsigned short datablob_len; + unsigned short decrypted_datalen; + unsigned int encrypted_datalen; + long dlen; + int ret; + + ret = strict_strtol(datalen, 10, &dlen); + if (ret < 0 || dlen < MIN_DATA_SIZE || dlen > MAX_DATA_SIZE) + return ERR_PTR(-EINVAL); + + decrypted_datalen = dlen; + encrypted_datalen = roundup(decrypted_datalen, blksize); + + datablob_len = strlen(master_desc) + 1 + strlen(datalen) + 1 + + ivsize + 1 + encrypted_datalen; + + ret = key_payload_reserve(key, decrypted_datalen + datablob_len + + HASH_SIZE + 1); + if (ret < 0) + return ERR_PTR(ret); + + epayload = kzalloc(sizeof(*epayload) + decrypted_datalen + + datablob_len + HASH_SIZE + 1, GFP_KERNEL); + if (!epayload) + return ERR_PTR(-ENOMEM); + + epayload->decrypted_datalen = decrypted_datalen; + epayload->datablob_len = datablob_len; + return epayload; +} + +static int encrypted_key_decrypt(struct encrypted_key_payload *epayload, + const char *hex_encoded_iv) +{ + struct key *mkey; + u8 derived_key[HASH_SIZE]; + u8 *master_key; + u8 *hmac; + const char *hex_encoded_data; + unsigned int encrypted_datalen; + size_t master_keylen; + size_t asciilen; + int ret; + + encrypted_datalen = roundup(epayload->decrypted_datalen, blksize); + asciilen = (ivsize + 1 + encrypted_datalen + HASH_SIZE) * 2; + if (strlen(hex_encoded_iv) != asciilen) + return -EINVAL; + + hex_encoded_data = hex_encoded_iv + (2 * ivsize) + 2; + hex2bin(epayload->iv, hex_encoded_iv, ivsize); + hex2bin(epayload->encrypted_data, hex_encoded_data, encrypted_datalen); + + hmac = epayload->master_desc + epayload->datablob_len; + hex2bin(hmac, hex_encoded_data + (encrypted_datalen * 2), HASH_SIZE); + + mkey = request_master_key(epayload, &master_key, &master_keylen); + if (IS_ERR(mkey)) + return PTR_ERR(mkey); + + ret = datablob_hmac_verify(epayload, master_key, master_keylen); + if (ret < 0) { + pr_err("encrypted_key: bad hmac (%d)\n", ret); + goto out; + } + + ret = get_derived_key(derived_key, ENC_KEY, master_key, master_keylen); + if (ret < 0) + goto out; + + ret = derived_key_decrypt(epayload, derived_key, sizeof derived_key); + if (ret < 0) + pr_err("encrypted_key: failed to decrypt key (%d)\n", ret); +out: + up_read(&mkey->sem); + key_put(mkey); + return ret; +} + +static void __ekey_init(struct encrypted_key_payload *epayload, + const char *master_desc, const char *datalen) +{ + epayload->master_desc = epayload->decrypted_data + + epayload->decrypted_datalen; + epayload->datalen = epayload->master_desc + strlen(master_desc) + 1; + epayload->iv = epayload->datalen + strlen(datalen) + 1; + epayload->encrypted_data = epayload->iv + ivsize + 1; + + memcpy(epayload->master_desc, master_desc, strlen(master_desc)); + memcpy(epayload->datalen, datalen, strlen(datalen)); +} + +/* + * encrypted_init - initialize an encrypted key + * + * For a new key, use a random number for both the iv and data + * itself. For an old key, decrypt the hex encoded data. + */ +static int encrypted_init(struct encrypted_key_payload *epayload, + const char *master_desc, const char *datalen, + const char *hex_encoded_iv) +{ + int ret = 0; + + __ekey_init(epayload, master_desc, datalen); + if (!hex_encoded_iv) { + get_random_bytes(epayload->iv, ivsize); + + get_random_bytes(epayload->decrypted_data, + epayload->decrypted_datalen); + } else + ret = encrypted_key_decrypt(epayload, hex_encoded_iv); + return ret; +} + +/* + * encrypted_instantiate - instantiate an encrypted key + * + * Decrypt an existing encrypted datablob or create a new encrypted key + * based on a kernel random number. + * + * On success, return 0. Otherwise return errno. + */ +static int encrypted_instantiate(struct key *key, const void *data, + size_t datalen) +{ + struct encrypted_key_payload *epayload = NULL; + char *datablob = NULL; + char *master_desc = NULL; + char *decrypted_datalen = NULL; + char *hex_encoded_iv = NULL; + int ret; + + if (datalen <= 0 || datalen > 32767 || !data) + return -EINVAL; + + datablob = kmalloc(datalen + 1, GFP_KERNEL); + if (!datablob) + return -ENOMEM; + datablob[datalen] = 0; + memcpy(datablob, data, datalen); + ret = datablob_parse(datablob, &master_desc, &decrypted_datalen, + &hex_encoded_iv); + if (ret < 0) + goto out; + + epayload = encrypted_key_alloc(key, master_desc, decrypted_datalen); + if (IS_ERR(epayload)) { + ret = PTR_ERR(epayload); + goto out; + } + ret = encrypted_init(epayload, master_desc, decrypted_datalen, + hex_encoded_iv); + if (ret < 0) { + kfree(epayload); + goto out; + } + + rcu_assign_pointer(key->payload.data, epayload); +out: + kfree(datablob); + return ret; +} + +static void encrypted_rcu_free(struct rcu_head *rcu) +{ + struct encrypted_key_payload *epayload; + + epayload = container_of(rcu, struct encrypted_key_payload, rcu); + memset(epayload->decrypted_data, 0, epayload->decrypted_datalen); + kfree(epayload); +} + +/* + * encrypted_update - update the master key description + * + * Change the master key description for an existing encrypted key. + * The next read will return an encrypted datablob using the new + * master key description. + * + * On success, return 0. Otherwise return errno. + */ +static int encrypted_update(struct key *key, const void *data, size_t datalen) +{ + struct encrypted_key_payload *epayload = key->payload.data; + struct encrypted_key_payload *new_epayload; + char *buf; + char *new_master_desc = NULL; + int ret = 0; + + if (datalen <= 0 || datalen > 32767 || !data) + return -EINVAL; + + buf = kmalloc(datalen + 1, GFP_KERNEL); + if (!buf) + return -ENOMEM; + + buf[datalen] = 0; + memcpy(buf, data, datalen); + ret = datablob_parse(buf, &new_master_desc, NULL, NULL); + if (ret < 0) + goto out; + + ret = valid_master_desc(new_master_desc, epayload->master_desc); + if (ret < 0) + goto out; + + new_epayload = encrypted_key_alloc(key, new_master_desc, + epayload->datalen); + if (IS_ERR(new_epayload)) { + ret = PTR_ERR(new_epayload); + goto out; + } + + __ekey_init(new_epayload, new_master_desc, epayload->datalen); + + memcpy(new_epayload->iv, epayload->iv, ivsize); + memcpy(new_epayload->decrypted_data, epayload->decrypted_data, + epayload->decrypted_datalen); + + rcu_assign_pointer(key->payload.data, new_epayload); + call_rcu(&epayload->rcu, encrypted_rcu_free); +out: + kfree(buf); + return ret; +} + +/* + * encrypted_read - format and copy the encrypted data to userspace + * + * The resulting datablob format is: + * + * + * On success, return to userspace the encrypted key datablob size. + */ +static long encrypted_read(const struct key *key, char __user *buffer, + size_t buflen) +{ + struct encrypted_key_payload *epayload; + struct key *mkey; + u8 *master_key; + size_t master_keylen; + char derived_key[HASH_SIZE]; + char *ascii_buf; + size_t asciiblob_len; + int ret; + + epayload = rcu_dereference_protected(key->payload.data, + rwsem_is_locked(&((struct key *)key)->sem)); + + /* returns the hex encoded iv, encrypted-data, and hmac as ascii */ + asciiblob_len = epayload->datablob_len + ivsize + 1 + + roundup(epayload->decrypted_datalen, blksize) + + (HASH_SIZE * 2); + + if (!buffer || buflen < asciiblob_len) + return asciiblob_len; + + mkey = request_master_key(epayload, &master_key, &master_keylen); + if (IS_ERR(mkey)) + return PTR_ERR(mkey); + + ret = get_derived_key(derived_key, ENC_KEY, master_key, master_keylen); + if (ret < 0) + goto out; + + ret = derived_key_encrypt(epayload, derived_key, sizeof derived_key); + if (ret < 0) + goto out; + + ret = datablob_hmac_append(epayload, master_key, master_keylen); + if (ret < 0) + goto out; + + ascii_buf = datablob_format(epayload, asciiblob_len); + if (!ascii_buf) { + ret = -ENOMEM; + goto out; + } + + up_read(&mkey->sem); + key_put(mkey); + + if (copy_to_user(buffer, ascii_buf, asciiblob_len) != 0) + ret = -EFAULT; + kfree(ascii_buf); + + return asciiblob_len; +out: + up_read(&mkey->sem); + key_put(mkey); + return ret; +} + +/* + * encrypted_destroy - before freeing the key, clear the decrypted data + * + * Before freeing the key, clear the memory containing the decrypted + * key data. + */ +static void encrypted_destroy(struct key *key) +{ + struct encrypted_key_payload *epayload = key->payload.data; + + if (!epayload) + return; + + memset(epayload->decrypted_data, 0, epayload->decrypted_datalen); + kfree(key->payload.data); +} + +struct key_type key_type_encrypted = { + .name = "encrypted", + .instantiate = encrypted_instantiate, + .update = encrypted_update, + .match = user_match, + .destroy = encrypted_destroy, + .describe = user_describe, + .read = encrypted_read, +}; +EXPORT_SYMBOL_GPL(key_type_encrypted); + +static void encrypted_shash_release(void) +{ + if (hashalg) + crypto_free_shash(hashalg); + if (hmacalg) + crypto_free_shash(hmacalg); +} + +static int __init encrypted_shash_alloc(void) +{ + int ret; + + hmacalg = crypto_alloc_shash(hmac_alg, 0, CRYPTO_ALG_ASYNC); + if (IS_ERR(hmacalg)) { + pr_info("encrypted_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("encrypted_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_encrypted(void) +{ + int ret; + + ret = encrypted_shash_alloc(); + if (ret < 0) + return ret; + ret = register_key_type(&key_type_encrypted); + if (ret < 0) + goto out; + return aes_get_sizes(); +out: + encrypted_shash_release(); + return ret; + +} + +static void __exit cleanup_encrypted(void) +{ + encrypted_shash_release(); + unregister_key_type(&key_type_encrypted); +} + +late_initcall(init_encrypted); +module_exit(cleanup_encrypted); + +MODULE_LICENSE("GPL"); diff --git a/security/keys/encrypted.h b/security/keys/encrypted.h new file mode 100644 index 00000000000..cef5e2f2b7d --- /dev/null +++ b/security/keys/encrypted.h @@ -0,0 +1,54 @@ +#ifndef __ENCRYPTED_KEY_H +#define __ENCRYPTED_KEY_H + +#define ENCRYPTED_DEBUG 0 + +#if ENCRYPTED_DEBUG +static inline void dump_master_key(const u8 *master_key, size_t master_keylen) +{ + print_hex_dump(KERN_ERR, "master key: ", DUMP_PREFIX_NONE, 32, 1, + master_key, master_keylen, 0); +} + +static inline void dump_decrypted_data(struct encrypted_key_payload *epayload) +{ + print_hex_dump(KERN_ERR, "decrypted data: ", DUMP_PREFIX_NONE, 32, 1, + epayload->decrypted_data, + epayload->decrypted_datalen, 0); +} + +static inline void dump_encrypted_data(struct encrypted_key_payload *epayload, + unsigned int encrypted_datalen) +{ + print_hex_dump(KERN_ERR, "encrypted data: ", DUMP_PREFIX_NONE, 32, 1, + epayload->encrypted_data, encrypted_datalen, 0); +} + +static inline void dump_hmac(const char *str, const u8 *digest, + unsigned int hmac_size) +{ + if (str) + pr_info("encrypted_key: %s", str); + print_hex_dump(KERN_ERR, "hmac: ", DUMP_PREFIX_NONE, 32, 1, digest, + hmac_size, 0); +} +#else +static inline void dump_master_key(const u8 *master_key, size_t master_keylen) +{ +} + +static inline void dump_decrypted_data(struct encrypted_key_payload *epayload) +{ +} + +static inline void dump_encrypted_data(struct encrypted_key_payload *epayload, + unsigned int encrypted_datalen) +{ +} + +static inline void dump_hmac(const char *str, const u8 *digest, + unsigned int hmac_size) +{ +} +#endif +#endif diff --git a/security/keys/encrypted_defined.c b/security/keys/encrypted_defined.c deleted file mode 100644 index 28791a65740..00000000000 --- a/security/keys/encrypted_defined.c +++ /dev/null @@ -1,902 +0,0 @@ -/* - * Copyright (C) 2010 IBM Corporation - * - * Author: - * Mimi Zohar - * - * 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 -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "encrypted_defined.h" - -static const char KEY_TRUSTED_PREFIX[] = "trusted:"; -static const char KEY_USER_PREFIX[] = "user:"; -static const char hash_alg[] = "sha256"; -static const char hmac_alg[] = "hmac(sha256)"; -static const char blkcipher_alg[] = "cbc(aes)"; -static unsigned int ivsize; -static int blksize; - -#define KEY_TRUSTED_PREFIX_LEN (sizeof (KEY_TRUSTED_PREFIX) - 1) -#define KEY_USER_PREFIX_LEN (sizeof (KEY_USER_PREFIX) - 1) -#define HASH_SIZE SHA256_DIGEST_SIZE -#define MAX_DATA_SIZE 4096 -#define MIN_DATA_SIZE 20 - -struct sdesc { - struct shash_desc shash; - char ctx[]; -}; - -static struct crypto_shash *hashalg; -static struct crypto_shash *hmacalg; - -enum { - Opt_err = -1, 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} -}; - -static int aes_get_sizes(void) -{ - struct crypto_blkcipher *tfm; - - tfm = crypto_alloc_blkcipher(blkcipher_alg, 0, CRYPTO_ALG_ASYNC); - if (IS_ERR(tfm)) { - pr_err("encrypted_key: failed to alloc_cipher (%ld)\n", - PTR_ERR(tfm)); - return PTR_ERR(tfm); - } - ivsize = crypto_blkcipher_ivsize(tfm); - blksize = crypto_blkcipher_blocksize(tfm); - crypto_free_blkcipher(tfm); - return 0; -} - -/* - * valid_master_desc - verify the 'key-type:desc' of a new/updated master-key - * - * key-type:= "trusted:" | "encrypted:" - * desc:= master-key description - * - * Verify that 'key-type' is valid and that 'desc' exists. On key update, - * only the master key description is permitted to change, not the key-type. - * The key-type remains constant. - * - * On success returns 0, otherwise -EINVAL. - */ -static int valid_master_desc(const char *new_desc, const char *orig_desc) -{ - if (!memcmp(new_desc, KEY_TRUSTED_PREFIX, KEY_TRUSTED_PREFIX_LEN)) { - if (strlen(new_desc) == KEY_TRUSTED_PREFIX_LEN) - goto out; - if (orig_desc) - if (memcmp(new_desc, orig_desc, KEY_TRUSTED_PREFIX_LEN)) - goto out; - } else if (!memcmp(new_desc, KEY_USER_PREFIX, KEY_USER_PREFIX_LEN)) { - if (strlen(new_desc) == KEY_USER_PREFIX_LEN) - goto out; - if (orig_desc) - if (memcmp(new_desc, orig_desc, KEY_USER_PREFIX_LEN)) - goto out; - } else - goto out; - return 0; -out: - return -EINVAL; -} - -/* - * datablob_parse - parse the keyctl data - * - * datablob format: - * new - * load - * update - * - * Tokenizes a copy of the keyctl data, returning a pointer to each token, - * which is null terminated. - * - * On success returns 0, otherwise -EINVAL. - */ -static int datablob_parse(char *datablob, char **master_desc, - char **decrypted_datalen, char **hex_encoded_iv) -{ - substring_t args[MAX_OPT_ARGS]; - int ret = -EINVAL; - int key_cmd; - char *p; - - p = strsep(&datablob, " \t"); - if (!p) - return ret; - key_cmd = match_token(p, key_tokens, args); - - *master_desc = strsep(&datablob, " \t"); - if (!*master_desc) - goto out; - - if (valid_master_desc(*master_desc, NULL) < 0) - goto out; - - if (decrypted_datalen) { - *decrypted_datalen = strsep(&datablob, " \t"); - if (!*decrypted_datalen) - goto out; - } - - switch (key_cmd) { - case Opt_new: - if (!decrypted_datalen) - break; - ret = 0; - break; - case Opt_load: - if (!decrypted_datalen) - break; - *hex_encoded_iv = strsep(&datablob, " \t"); - if (!*hex_encoded_iv) - break; - ret = 0; - break; - case Opt_update: - if (decrypted_datalen) - break; - ret = 0; - break; - case Opt_err: - break; - } -out: - return ret; -} - -/* - * datablob_format - format as an ascii string, before copying to userspace - */ -static char *datablob_format(struct encrypted_key_payload *epayload, - size_t asciiblob_len) -{ - char *ascii_buf, *bufp; - u8 *iv = epayload->iv; - int len; - int i; - - ascii_buf = kmalloc(asciiblob_len + 1, GFP_KERNEL); - if (!ascii_buf) - goto out; - - ascii_buf[asciiblob_len] = '\0'; - - /* copy datablob master_desc and datalen strings */ - len = sprintf(ascii_buf, "%s %s ", epayload->master_desc, - epayload->datalen); - - /* convert the hex encoded iv, encrypted-data and HMAC to ascii */ - bufp = &ascii_buf[len]; - for (i = 0; i < (asciiblob_len - len) / 2; i++) - bufp = pack_hex_byte(bufp, iv[i]); -out: - return ascii_buf; -} - -/* - * request_trusted_key - request the trusted key - * - * Trusted keys are sealed to PCRs and other metadata. Although userspace - * manages both trusted/encrypted key-types, like the encrypted key type - * data, trusted key type data is not visible decrypted from userspace. - */ -static struct key *request_trusted_key(const char *trusted_desc, - u8 **master_key, size_t *master_keylen) -{ - struct trusted_key_payload *tpayload; - struct key *tkey; - - tkey = request_key(&key_type_trusted, trusted_desc, NULL); - if (IS_ERR(tkey)) - goto error; - - down_read(&tkey->sem); - tpayload = rcu_dereference(tkey->payload.data); - *master_key = tpayload->key; - *master_keylen = tpayload->key_len; -error: - return tkey; -} - -/* - * request_user_key - request the user key - * - * Use a user provided key to encrypt/decrypt an encrypted-key. - */ -static struct key *request_user_key(const char *master_desc, u8 **master_key, - size_t *master_keylen) -{ - struct user_key_payload *upayload; - struct key *ukey; - - ukey = request_key(&key_type_user, master_desc, NULL); - if (IS_ERR(ukey)) - goto error; - - down_read(&ukey->sem); - upayload = rcu_dereference(ukey->payload.data); - *master_key = upayload->data; - *master_keylen = upayload->datalen; -error: - return ukey; -} - -static struct sdesc *alloc_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 calc_hmac(u8 *digest, const u8 *key, unsigned int keylen, - const u8 *buf, unsigned int buflen) -{ - struct sdesc *sdesc; - int ret; - - sdesc = alloc_sdesc(hmacalg); - if (IS_ERR(sdesc)) { - pr_info("encrypted_key: can't alloc %s\n", hmac_alg); - return PTR_ERR(sdesc); - } - - ret = crypto_shash_setkey(hmacalg, key, keylen); - if (!ret) - ret = crypto_shash_digest(&sdesc->shash, buf, buflen, digest); - kfree(sdesc); - return ret; -} - -static int calc_hash(u8 *digest, const u8 *buf, unsigned int buflen) -{ - struct sdesc *sdesc; - int ret; - - sdesc = alloc_sdesc(hashalg); - if (IS_ERR(sdesc)) { - pr_info("encrypted_key: can't alloc %s\n", hash_alg); - return PTR_ERR(sdesc); - } - - ret = crypto_shash_digest(&sdesc->shash, buf, buflen, digest); - kfree(sdesc); - return ret; -} - -enum derived_key_type { ENC_KEY, AUTH_KEY }; - -/* Derive authentication/encryption key from trusted key */ -static int get_derived_key(u8 *derived_key, enum derived_key_type key_type, - const u8 *master_key, size_t master_keylen) -{ - u8 *derived_buf; - unsigned int derived_buf_len; - int ret; - - derived_buf_len = strlen("AUTH_KEY") + 1 + master_keylen; - if (derived_buf_len < HASH_SIZE) - derived_buf_len = HASH_SIZE; - - derived_buf = kzalloc(derived_buf_len, GFP_KERNEL); - if (!derived_buf) { - pr_err("encrypted_key: out of memory\n"); - return -ENOMEM; - } - if (key_type) - strcpy(derived_buf, "AUTH_KEY"); - else - strcpy(derived_buf, "ENC_KEY"); - - memcpy(derived_buf + strlen(derived_buf) + 1, master_key, - master_keylen); - ret = calc_hash(derived_key, derived_buf, derived_buf_len); - kfree(derived_buf); - return ret; -} - -static int init_blkcipher_desc(struct blkcipher_desc *desc, const u8 *key, - unsigned int key_len, const u8 *iv, - unsigned int ivsize) -{ - int ret; - - desc->tfm = crypto_alloc_blkcipher(blkcipher_alg, 0, CRYPTO_ALG_ASYNC); - if (IS_ERR(desc->tfm)) { - pr_err("encrypted_key: failed to load %s transform (%ld)\n", - blkcipher_alg, PTR_ERR(desc->tfm)); - return PTR_ERR(desc->tfm); - } - desc->flags = 0; - - ret = crypto_blkcipher_setkey(desc->tfm, key, key_len); - if (ret < 0) { - pr_err("encrypted_key: failed to setkey (%d)\n", ret); - crypto_free_blkcipher(desc->tfm); - return ret; - } - crypto_blkcipher_set_iv(desc->tfm, iv, ivsize); - return 0; -} - -static struct key *request_master_key(struct encrypted_key_payload *epayload, - u8 **master_key, size_t *master_keylen) -{ - struct key *mkey = NULL; - - if (!strncmp(epayload->master_desc, KEY_TRUSTED_PREFIX, - KEY_TRUSTED_PREFIX_LEN)) { - mkey = request_trusted_key(epayload->master_desc + - KEY_TRUSTED_PREFIX_LEN, - master_key, master_keylen); - } else if (!strncmp(epayload->master_desc, KEY_USER_PREFIX, - KEY_USER_PREFIX_LEN)) { - mkey = request_user_key(epayload->master_desc + - KEY_USER_PREFIX_LEN, - master_key, master_keylen); - } else - goto out; - - if (IS_ERR(mkey)) - pr_info("encrypted_key: key %s not found", - epayload->master_desc); - if (mkey) - dump_master_key(*master_key, *master_keylen); -out: - return mkey; -} - -/* Before returning data to userspace, encrypt decrypted data. */ -static int derived_key_encrypt(struct encrypted_key_payload *epayload, - const u8 *derived_key, - unsigned int derived_keylen) -{ - struct scatterlist sg_in[2]; - struct scatterlist sg_out[1]; - struct blkcipher_desc desc; - unsigned int encrypted_datalen; - unsigned int padlen; - char pad[16]; - int ret; - - encrypted_datalen = roundup(epayload->decrypted_datalen, blksize); - padlen = encrypted_datalen - epayload->decrypted_datalen; - - ret = init_blkcipher_desc(&desc, derived_key, derived_keylen, - epayload->iv, ivsize); - if (ret < 0) - goto out; - dump_decrypted_data(epayload); - - memset(pad, 0, sizeof pad); - sg_init_table(sg_in, 2); - sg_set_buf(&sg_in[0], epayload->decrypted_data, - epayload->decrypted_datalen); - sg_set_buf(&sg_in[1], pad, padlen); - - sg_init_table(sg_out, 1); - sg_set_buf(sg_out, epayload->encrypted_data, encrypted_datalen); - - ret = crypto_blkcipher_encrypt(&desc, sg_out, sg_in, encrypted_datalen); - crypto_free_blkcipher(desc.tfm); - if (ret < 0) - pr_err("encrypted_key: failed to encrypt (%d)\n", ret); - else - dump_encrypted_data(epayload, encrypted_datalen); -out: - return ret; -} - -static int datablob_hmac_append(struct encrypted_key_payload *epayload, - const u8 *master_key, size_t master_keylen) -{ - u8 derived_key[HASH_SIZE]; - u8 *digest; - int ret; - - ret = get_derived_key(derived_key, AUTH_KEY, master_key, master_keylen); - if (ret < 0) - goto out; - - digest = epayload->master_desc + epayload->datablob_len; - ret = calc_hmac(digest, derived_key, sizeof derived_key, - epayload->master_desc, epayload->datablob_len); - if (!ret) - dump_hmac(NULL, digest, HASH_SIZE); -out: - return ret; -} - -/* verify HMAC before decrypting encrypted key */ -static int datablob_hmac_verify(struct encrypted_key_payload *epayload, - const u8 *master_key, size_t master_keylen) -{ - u8 derived_key[HASH_SIZE]; - u8 digest[HASH_SIZE]; - int ret; - - ret = get_derived_key(derived_key, AUTH_KEY, master_key, master_keylen); - if (ret < 0) - goto out; - - ret = calc_hmac(digest, derived_key, sizeof derived_key, - epayload->master_desc, epayload->datablob_len); - if (ret < 0) - goto out; - ret = memcmp(digest, epayload->master_desc + epayload->datablob_len, - sizeof digest); - if (ret) { - ret = -EINVAL; - dump_hmac("datablob", - epayload->master_desc + epayload->datablob_len, - HASH_SIZE); - dump_hmac("calc", digest, HASH_SIZE); - } -out: - return ret; -} - -static int derived_key_decrypt(struct encrypted_key_payload *epayload, - const u8 *derived_key, - unsigned int derived_keylen) -{ - struct scatterlist sg_in[1]; - struct scatterlist sg_out[2]; - struct blkcipher_desc desc; - unsigned int encrypted_datalen; - char pad[16]; - int ret; - - encrypted_datalen = roundup(epayload->decrypted_datalen, blksize); - ret = init_blkcipher_desc(&desc, derived_key, derived_keylen, - epayload->iv, ivsize); - if (ret < 0) - goto out; - dump_encrypted_data(epayload, encrypted_datalen); - - memset(pad, 0, sizeof pad); - sg_init_table(sg_in, 1); - sg_init_table(sg_out, 2); - sg_set_buf(sg_in, epayload->encrypted_data, encrypted_datalen); - sg_set_buf(&sg_out[0], epayload->decrypted_data, - epayload->decrypted_datalen); - sg_set_buf(&sg_out[1], pad, sizeof pad); - - ret = crypto_blkcipher_decrypt(&desc, sg_out, sg_in, encrypted_datalen); - crypto_free_blkcipher(desc.tfm); - if (ret < 0) - goto out; - dump_decrypted_data(epayload); -out: - return ret; -} - -/* Allocate memory for decrypted key and datablob. */ -static struct encrypted_key_payload *encrypted_key_alloc(struct key *key, - const char *master_desc, - const char *datalen) -{ - struct encrypted_key_payload *epayload = NULL; - unsigned short datablob_len; - unsigned short decrypted_datalen; - unsigned int encrypted_datalen; - long dlen; - int ret; - - ret = strict_strtol(datalen, 10, &dlen); - if (ret < 0 || dlen < MIN_DATA_SIZE || dlen > MAX_DATA_SIZE) - return ERR_PTR(-EINVAL); - - decrypted_datalen = dlen; - encrypted_datalen = roundup(decrypted_datalen, blksize); - - datablob_len = strlen(master_desc) + 1 + strlen(datalen) + 1 - + ivsize + 1 + encrypted_datalen; - - ret = key_payload_reserve(key, decrypted_datalen + datablob_len - + HASH_SIZE + 1); - if (ret < 0) - return ERR_PTR(ret); - - epayload = kzalloc(sizeof(*epayload) + decrypted_datalen + - datablob_len + HASH_SIZE + 1, GFP_KERNEL); - if (!epayload) - return ERR_PTR(-ENOMEM); - - epayload->decrypted_datalen = decrypted_datalen; - epayload->datablob_len = datablob_len; - return epayload; -} - -static int encrypted_key_decrypt(struct encrypted_key_payload *epayload, - const char *hex_encoded_iv) -{ - struct key *mkey; - u8 derived_key[HASH_SIZE]; - u8 *master_key; - u8 *hmac; - const char *hex_encoded_data; - unsigned int encrypted_datalen; - size_t master_keylen; - size_t asciilen; - int ret; - - encrypted_datalen = roundup(epayload->decrypted_datalen, blksize); - asciilen = (ivsize + 1 + encrypted_datalen + HASH_SIZE) * 2; - if (strlen(hex_encoded_iv) != asciilen) - return -EINVAL; - - hex_encoded_data = hex_encoded_iv + (2 * ivsize) + 2; - hex2bin(epayload->iv, hex_encoded_iv, ivsize); - hex2bin(epayload->encrypted_data, hex_encoded_data, encrypted_datalen); - - hmac = epayload->master_desc + epayload->datablob_len; - hex2bin(hmac, hex_encoded_data + (encrypted_datalen * 2), HASH_SIZE); - - mkey = request_master_key(epayload, &master_key, &master_keylen); - if (IS_ERR(mkey)) - return PTR_ERR(mkey); - - ret = datablob_hmac_verify(epayload, master_key, master_keylen); - if (ret < 0) { - pr_err("encrypted_key: bad hmac (%d)\n", ret); - goto out; - } - - ret = get_derived_key(derived_key, ENC_KEY, master_key, master_keylen); - if (ret < 0) - goto out; - - ret = derived_key_decrypt(epayload, derived_key, sizeof derived_key); - if (ret < 0) - pr_err("encrypted_key: failed to decrypt key (%d)\n", ret); -out: - up_read(&mkey->sem); - key_put(mkey); - return ret; -} - -static void __ekey_init(struct encrypted_key_payload *epayload, - const char *master_desc, const char *datalen) -{ - epayload->master_desc = epayload->decrypted_data - + epayload->decrypted_datalen; - epayload->datalen = epayload->master_desc + strlen(master_desc) + 1; - epayload->iv = epayload->datalen + strlen(datalen) + 1; - epayload->encrypted_data = epayload->iv + ivsize + 1; - - memcpy(epayload->master_desc, master_desc, strlen(master_desc)); - memcpy(epayload->datalen, datalen, strlen(datalen)); -} - -/* - * encrypted_init - initialize an encrypted key - * - * For a new key, use a random number for both the iv and data - * itself. For an old key, decrypt the hex encoded data. - */ -static int encrypted_init(struct encrypted_key_payload *epayload, - const char *master_desc, const char *datalen, - const char *hex_encoded_iv) -{ - int ret = 0; - - __ekey_init(epayload, master_desc, datalen); - if (!hex_encoded_iv) { - get_random_bytes(epayload->iv, ivsize); - - get_random_bytes(epayload->decrypted_data, - epayload->decrypted_datalen); - } else - ret = encrypted_key_decrypt(epayload, hex_encoded_iv); - return ret; -} - -/* - * encrypted_instantiate - instantiate an encrypted key - * - * Decrypt an existing encrypted datablob or create a new encrypted key - * based on a kernel random number. - * - * On success, return 0. Otherwise return errno. - */ -static int encrypted_instantiate(struct key *key, const void *data, - size_t datalen) -{ - struct encrypted_key_payload *epayload = NULL; - char *datablob = NULL; - char *master_desc = NULL; - char *decrypted_datalen = NULL; - char *hex_encoded_iv = NULL; - int ret; - - if (datalen <= 0 || datalen > 32767 || !data) - return -EINVAL; - - datablob = kmalloc(datalen + 1, GFP_KERNEL); - if (!datablob) - return -ENOMEM; - datablob[datalen] = 0; - memcpy(datablob, data, datalen); - ret = datablob_parse(datablob, &master_desc, &decrypted_datalen, - &hex_encoded_iv); - if (ret < 0) - goto out; - - epayload = encrypted_key_alloc(key, master_desc, decrypted_datalen); - if (IS_ERR(epayload)) { - ret = PTR_ERR(epayload); - goto out; - } - ret = encrypted_init(epayload, master_desc, decrypted_datalen, - hex_encoded_iv); - if (ret < 0) { - kfree(epayload); - goto out; - } - - rcu_assign_pointer(key->payload.data, epayload); -out: - kfree(datablob); - return ret; -} - -static void encrypted_rcu_free(struct rcu_head *rcu) -{ - struct encrypted_key_payload *epayload; - - epayload = container_of(rcu, struct encrypted_key_payload, rcu); - memset(epayload->decrypted_data, 0, epayload->decrypted_datalen); - kfree(epayload); -} - -/* - * encrypted_update - update the master key description - * - * Change the master key description for an existing encrypted key. - * The next read will return an encrypted datablob using the new - * master key description. - * - * On success, return 0. Otherwise return errno. - */ -static int encrypted_update(struct key *key, const void *data, size_t datalen) -{ - struct encrypted_key_payload *epayload = key->payload.data; - struct encrypted_key_payload *new_epayload; - char *buf; - char *new_master_desc = NULL; - int ret = 0; - - if (datalen <= 0 || datalen > 32767 || !data) - return -EINVAL; - - buf = kmalloc(datalen + 1, GFP_KERNEL); - if (!buf) - return -ENOMEM; - - buf[datalen] = 0; - memcpy(buf, data, datalen); - ret = datablob_parse(buf, &new_master_desc, NULL, NULL); - if (ret < 0) - goto out; - - ret = valid_master_desc(new_master_desc, epayload->master_desc); - if (ret < 0) - goto out; - - new_epayload = encrypted_key_alloc(key, new_master_desc, - epayload->datalen); - if (IS_ERR(new_epayload)) { - ret = PTR_ERR(new_epayload); - goto out; - } - - __ekey_init(new_epayload, new_master_desc, epayload->datalen); - - memcpy(new_epayload->iv, epayload->iv, ivsize); - memcpy(new_epayload->decrypted_data, epayload->decrypted_data, - epayload->decrypted_datalen); - - rcu_assign_pointer(key->payload.data, new_epayload); - call_rcu(&epayload->rcu, encrypted_rcu_free); -out: - kfree(buf); - return ret; -} - -/* - * encrypted_read - format and copy the encrypted data to userspace - * - * The resulting datablob format is: - * - * - * On success, return to userspace the encrypted key datablob size. - */ -static long encrypted_read(const struct key *key, char __user *buffer, - size_t buflen) -{ - struct encrypted_key_payload *epayload; - struct key *mkey; - u8 *master_key; - size_t master_keylen; - char derived_key[HASH_SIZE]; - char *ascii_buf; - size_t asciiblob_len; - int ret; - - epayload = rcu_dereference_protected(key->payload.data, - rwsem_is_locked(&((struct key *)key)->sem)); - - /* returns the hex encoded iv, encrypted-data, and hmac as ascii */ - asciiblob_len = epayload->datablob_len + ivsize + 1 - + roundup(epayload->decrypted_datalen, blksize) - + (HASH_SIZE * 2); - - if (!buffer || buflen < asciiblob_len) - return asciiblob_len; - - mkey = request_master_key(epayload, &master_key, &master_keylen); - if (IS_ERR(mkey)) - return PTR_ERR(mkey); - - ret = get_derived_key(derived_key, ENC_KEY, master_key, master_keylen); - if (ret < 0) - goto out; - - ret = derived_key_encrypt(epayload, derived_key, sizeof derived_key); - if (ret < 0) - goto out; - - ret = datablob_hmac_append(epayload, master_key, master_keylen); - if (ret < 0) - goto out; - - ascii_buf = datablob_format(epayload, asciiblob_len); - if (!ascii_buf) { - ret = -ENOMEM; - goto out; - } - - up_read(&mkey->sem); - key_put(mkey); - - if (copy_to_user(buffer, ascii_buf, asciiblob_len) != 0) - ret = -EFAULT; - kfree(ascii_buf); - - return asciiblob_len; -out: - up_read(&mkey->sem); - key_put(mkey); - return ret; -} - -/* - * encrypted_destroy - before freeing the key, clear the decrypted data - * - * Before freeing the key, clear the memory containing the decrypted - * key data. - */ -static void encrypted_destroy(struct key *key) -{ - struct encrypted_key_payload *epayload = key->payload.data; - - if (!epayload) - return; - - memset(epayload->decrypted_data, 0, epayload->decrypted_datalen); - kfree(key->payload.data); -} - -struct key_type key_type_encrypted = { - .name = "encrypted", - .instantiate = encrypted_instantiate, - .update = encrypted_update, - .match = user_match, - .destroy = encrypted_destroy, - .describe = user_describe, - .read = encrypted_read, -}; -EXPORT_SYMBOL_GPL(key_type_encrypted); - -static void encrypted_shash_release(void) -{ - if (hashalg) - crypto_free_shash(hashalg); - if (hmacalg) - crypto_free_shash(hmacalg); -} - -static int __init encrypted_shash_alloc(void) -{ - int ret; - - hmacalg = crypto_alloc_shash(hmac_alg, 0, CRYPTO_ALG_ASYNC); - if (IS_ERR(hmacalg)) { - pr_info("encrypted_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("encrypted_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_encrypted(void) -{ - int ret; - - ret = encrypted_shash_alloc(); - if (ret < 0) - return ret; - ret = register_key_type(&key_type_encrypted); - if (ret < 0) - goto out; - return aes_get_sizes(); -out: - encrypted_shash_release(); - return ret; -} - -static void __exit cleanup_encrypted(void) -{ - encrypted_shash_release(); - unregister_key_type(&key_type_encrypted); -} - -late_initcall(init_encrypted); -module_exit(cleanup_encrypted); - -MODULE_LICENSE("GPL"); diff --git a/security/keys/encrypted_defined.h b/security/keys/encrypted_defined.h deleted file mode 100644 index cef5e2f2b7d..00000000000 --- a/security/keys/encrypted_defined.h +++ /dev/null @@ -1,54 +0,0 @@ -#ifndef __ENCRYPTED_KEY_H -#define __ENCRYPTED_KEY_H - -#define ENCRYPTED_DEBUG 0 - -#if ENCRYPTED_DEBUG -static inline void dump_master_key(const u8 *master_key, size_t master_keylen) -{ - print_hex_dump(KERN_ERR, "master key: ", DUMP_PREFIX_NONE, 32, 1, - master_key, master_keylen, 0); -} - -static inline void dump_decrypted_data(struct encrypted_key_payload *epayload) -{ - print_hex_dump(KERN_ERR, "decrypted data: ", DUMP_PREFIX_NONE, 32, 1, - epayload->decrypted_data, - epayload->decrypted_datalen, 0); -} - -static inline void dump_encrypted_data(struct encrypted_key_payload *epayload, - unsigned int encrypted_datalen) -{ - print_hex_dump(KERN_ERR, "encrypted data: ", DUMP_PREFIX_NONE, 32, 1, - epayload->encrypted_data, encrypted_datalen, 0); -} - -static inline void dump_hmac(const char *str, const u8 *digest, - unsigned int hmac_size) -{ - if (str) - pr_info("encrypted_key: %s", str); - print_hex_dump(KERN_ERR, "hmac: ", DUMP_PREFIX_NONE, 32, 1, digest, - hmac_size, 0); -} -#else -static inline void dump_master_key(const u8 *master_key, size_t master_keylen) -{ -} - -static inline void dump_decrypted_data(struct encrypted_key_payload *epayload) -{ -} - -static inline void dump_encrypted_data(struct encrypted_key_payload *epayload, - unsigned int encrypted_datalen) -{ -} - -static inline void dump_hmac(const char *str, const u8 *digest, - unsigned int hmac_size) -{ -} -#endif -#endif -- cgit v1.2.3-70-g09d2 From 5403110943a2dcf1f96416d7a412a8b46895facd Mon Sep 17 00:00:00 2001 From: Jesper Juhl Date: Sun, 23 Jan 2011 22:40:42 +0100 Subject: trusted keys: Fix a memory leak in trusted_update(). One failure path in security/keys/trusted.c::trusted_update() does not free 'new_p' while the others do. This patch makes sure we also free it in the remaining path (if datablob_parse() returns different from Opt_update). Signed-off-by: Jesper Juhl Signed-off-by: James Morris --- security/keys/trusted.c | 1 + 1 file changed, 1 insertion(+) (limited to 'security') diff --git a/security/keys/trusted.c b/security/keys/trusted.c index 3066f56c767..83fc92e297c 100644 --- a/security/keys/trusted.c +++ b/security/keys/trusted.c @@ -1032,6 +1032,7 @@ static int trusted_update(struct key *key, const void *data, size_t datalen) ret = datablob_parse(datablob, new_p, new_o); if (ret != Opt_update) { ret = -EINVAL; + kfree(new_p); goto out; } /* copy old key values, and reseal with new pcrs */ -- cgit v1.2.3-70-g09d2 From 3ac285ff23cd6e1bc402b6db836521bce006eb89 Mon Sep 17 00:00:00 2001 From: Davidlohr Bueso Date: Fri, 21 Jan 2011 12:28:04 -0300 Subject: selinux: return -ENOMEM when memory allocation fails Return -ENOMEM when memory allocation fails in cond_init_bool_indexes, correctly propagating error code to caller. Signed-off-by: Davidlohr Bueso Signed-off-by: James Morris --- security/selinux/ss/conditional.c | 2 +- security/selinux/ss/policydb.c | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) (limited to 'security') diff --git a/security/selinux/ss/conditional.c b/security/selinux/ss/conditional.c index c3f845cbcd4..a53373207fb 100644 --- a/security/selinux/ss/conditional.c +++ b/security/selinux/ss/conditional.c @@ -178,7 +178,7 @@ int cond_init_bool_indexes(struct policydb *p) p->bool_val_to_struct = (struct cond_bool_datum **) kmalloc(p->p_bools.nprim * sizeof(struct cond_bool_datum *), GFP_KERNEL); if (!p->bool_val_to_struct) - return -1; + return -ENOMEM; return 0; } diff --git a/security/selinux/ss/policydb.c b/security/selinux/ss/policydb.c index be9de387283..57363562f0f 100644 --- a/security/selinux/ss/policydb.c +++ b/security/selinux/ss/policydb.c @@ -501,8 +501,8 @@ static int policydb_index(struct policydb *p) if (rc) goto out; - rc = -ENOMEM; - if (cond_init_bool_indexes(p)) + rc = cond_init_bool_indexes(p); + if (rc) goto out; for (i = 0; i < SYM_NUM; i++) { -- cgit v1.2.3-70-g09d2 From ceb73c12047b8d543570b23353e7848eb7c540a1 Mon Sep 17 00:00:00 2001 From: David Howells Date: Tue, 25 Jan 2011 16:34:28 +0000 Subject: KEYS: Fix __key_link_end() quota fixup on error Fix __key_link_end()'s attempt to fix up the quota if an error occurs. There are two erroneous cases: Firstly, we always decrease the quota if the preallocated replacement keyring needs cleaning up, irrespective of whether or not we should (we may have replaced a pointer rather than adding another pointer). Secondly, we never clean up the quota if we added a pointer without the keyring storage being extended (we allocate multiple pointers at a time, even if we're not going to use them all immediately). We handle this by setting the bottom bit of the preallocation pointer in __key_link_begin() to indicate that the quota needs fixing up, which is then passed to __key_link() (which clears the whole thing) and __key_link_end(). Signed-off-by: David Howells Signed-off-by: Linus Torvalds --- security/keys/internal.h | 6 +++--- security/keys/key.c | 8 ++++---- security/keys/keyring.c | 31 +++++++++++++++++++------------ security/keys/request_key.c | 2 +- 4 files changed, 27 insertions(+), 20 deletions(-) (limited to 'security') diff --git a/security/keys/internal.h b/security/keys/internal.h index edfa50dbd6f..a52aa7c88b4 100644 --- a/security/keys/internal.h +++ b/security/keys/internal.h @@ -87,13 +87,13 @@ extern void key_type_put(struct key_type *ktype); extern int __key_link_begin(struct key *keyring, const struct key_type *type, const char *description, - struct keyring_list **_prealloc); + unsigned long *_prealloc); extern int __key_link_check_live_key(struct key *keyring, struct key *key); extern void __key_link(struct key *keyring, struct key *key, - struct keyring_list **_prealloc); + unsigned long *_prealloc); extern void __key_link_end(struct key *keyring, struct key_type *type, - struct keyring_list *prealloc); + unsigned long prealloc); extern key_ref_t __keyring_search_one(key_ref_t keyring_ref, const struct key_type *type, diff --git a/security/keys/key.c b/security/keys/key.c index 84d4eb568b0..1c2d43dc510 100644 --- a/security/keys/key.c +++ b/security/keys/key.c @@ -415,7 +415,7 @@ static int __key_instantiate_and_link(struct key *key, size_t datalen, struct key *keyring, struct key *authkey, - struct keyring_list **_prealloc) + unsigned long *_prealloc) { int ret, awaken; @@ -481,7 +481,7 @@ int key_instantiate_and_link(struct key *key, struct key *keyring, struct key *authkey) { - struct keyring_list *prealloc; + unsigned long prealloc; int ret; if (keyring) { @@ -526,7 +526,7 @@ int key_negate_and_link(struct key *key, struct key *keyring, struct key *authkey) { - struct keyring_list *prealloc; + unsigned long prealloc; struct timespec now; int ret, awaken, link_ret = 0; @@ -814,7 +814,7 @@ key_ref_t key_create_or_update(key_ref_t keyring_ref, key_perm_t perm, unsigned long flags) { - struct keyring_list *prealloc; + unsigned long prealloc; const struct cred *cred = current_cred(); struct key_type *ktype; struct key *keyring, *key = NULL; diff --git a/security/keys/keyring.c b/security/keys/keyring.c index 92024ed12e0..5620f084ded 100644 --- a/security/keys/keyring.c +++ b/security/keys/keyring.c @@ -25,6 +25,8 @@ (keyring)->payload.subscriptions, \ rwsem_is_locked((struct rw_semaphore *)&(keyring)->sem))) +#define KEY_LINK_FIXQUOTA 1UL + /* * When plumbing the depths of the key tree, this sets a hard limit * set on how deep we're willing to go. @@ -699,11 +701,11 @@ static void keyring_unlink_rcu_disposal(struct rcu_head *rcu) * Preallocate memory so that a key can be linked into to a keyring. */ int __key_link_begin(struct key *keyring, const struct key_type *type, - const char *description, - struct keyring_list **_prealloc) + const char *description, unsigned long *_prealloc) __acquires(&keyring->sem) { struct keyring_list *klist, *nklist; + unsigned long prealloc; unsigned max; size_t size; int loop, ret; @@ -746,6 +748,7 @@ int __key_link_begin(struct key *keyring, const struct key_type *type, /* note replacement slot */ klist->delkey = nklist->delkey = loop; + prealloc = (unsigned long)nklist; goto done; } } @@ -760,6 +763,7 @@ int __key_link_begin(struct key *keyring, const struct key_type *type, if (klist && klist->nkeys < klist->maxkeys) { /* there's sufficient slack space to append directly */ nklist = NULL; + prealloc = KEY_LINK_FIXQUOTA; } else { /* grow the key list */ max = 4; @@ -794,8 +798,9 @@ int __key_link_begin(struct key *keyring, const struct key_type *type, nklist->keys[nklist->delkey] = NULL; } + prealloc = (unsigned long)nklist | KEY_LINK_FIXQUOTA; done: - *_prealloc = nklist; + *_prealloc = prealloc; kleave(" = 0"); return 0; @@ -836,12 +841,12 @@ int __key_link_check_live_key(struct key *keyring, struct key *key) * combination. */ void __key_link(struct key *keyring, struct key *key, - struct keyring_list **_prealloc) + unsigned long *_prealloc) { struct keyring_list *klist, *nklist; - nklist = *_prealloc; - *_prealloc = NULL; + nklist = (struct keyring_list *)(*_prealloc & ~KEY_LINK_FIXQUOTA); + *_prealloc = 0; kenter("%d,%d,%p", keyring->serial, key->serial, nklist); @@ -881,20 +886,22 @@ void __key_link(struct key *keyring, struct key *key, * Must be called with __key_link_begin() having being called. */ void __key_link_end(struct key *keyring, struct key_type *type, - struct keyring_list *prealloc) + unsigned long prealloc) __releases(&keyring->sem) { BUG_ON(type == NULL); BUG_ON(type->name == NULL); - kenter("%d,%s,%p", keyring->serial, type->name, prealloc); + kenter("%d,%s,%lx", keyring->serial, type->name, prealloc); if (type == &key_type_keyring) up_write(&keyring_serialise_link_sem); if (prealloc) { - kfree(prealloc); - key_payload_reserve(keyring, - keyring->datalen - KEYQUOTA_LINK_BYTES); + if (prealloc & KEY_LINK_FIXQUOTA) + key_payload_reserve(keyring, + keyring->datalen - + KEYQUOTA_LINK_BYTES); + kfree((struct keyring_list *)(prealloc & ~KEY_LINK_FIXQUOTA)); } up_write(&keyring->sem); } @@ -921,7 +928,7 @@ void __key_link_end(struct key *keyring, struct key_type *type, */ int key_link(struct key *keyring, struct key *key) { - struct keyring_list *prealloc; + unsigned long prealloc; int ret; key_check(keyring); diff --git a/security/keys/request_key.c b/security/keys/request_key.c index 9a7fb3914b2..a3dc0d460de 100644 --- a/security/keys/request_key.c +++ b/security/keys/request_key.c @@ -352,8 +352,8 @@ static int construct_alloc_key(struct key_type *type, struct key_user *user, struct key **_key) { - struct keyring_list *prealloc; const struct cred *cred = current_cred(); + unsigned long prealloc; struct key *key; key_ref_t key_ref; int ret; -- cgit v1.2.3-70-g09d2 From 2edeaa34a6e3f2c43b667f6c4f7b27944b811695 Mon Sep 17 00:00:00 2001 From: Tetsuo Handa Date: Mon, 7 Feb 2011 13:36:10 +0000 Subject: CRED: Fix BUG() upon security_cred_alloc_blank() failure In cred_alloc_blank() since 2.6.32, abort_creds(new) is called with new->security == NULL and new->magic == 0 when security_cred_alloc_blank() returns an error. As a result, BUG() will be triggered if SELinux is enabled or CONFIG_DEBUG_CREDENTIALS=y. If CONFIG_DEBUG_CREDENTIALS=y, BUG() is called from __invalid_creds() because cred->magic == 0. Failing that, BUG() is called from selinux_cred_free() because selinux_cred_free() is not expecting cred->security == NULL. This does not affect smack_cred_free(), tomoyo_cred_free() or apparmor_cred_free(). Fix these bugs by (1) Set new->magic before calling security_cred_alloc_blank(). (2) Handle null cred->security in creds_are_invalid() and selinux_cred_free(). Signed-off-by: Tetsuo Handa Signed-off-by: David Howells Signed-off-by: Linus Torvalds --- kernel/cred.c | 12 ++++++++---- security/selinux/hooks.c | 6 +++++- 2 files changed, 13 insertions(+), 5 deletions(-) (limited to 'security') diff --git a/kernel/cred.c b/kernel/cred.c index 6a1aa004e37..fcf104bb5aa 100644 --- a/kernel/cred.c +++ b/kernel/cred.c @@ -252,13 +252,13 @@ struct cred *cred_alloc_blank(void) #endif atomic_set(&new->usage, 1); +#ifdef CONFIG_DEBUG_CREDENTIALS + new->magic = CRED_MAGIC; +#endif if (security_cred_alloc_blank(new, GFP_KERNEL) < 0) goto error; -#ifdef CONFIG_DEBUG_CREDENTIALS - new->magic = CRED_MAGIC; -#endif return new; error: @@ -748,7 +748,11 @@ bool creds_are_invalid(const struct cred *cred) if (cred->magic != CRED_MAGIC) return true; #ifdef CONFIG_SECURITY_SELINUX - if (selinux_is_enabled()) { + /* + * cred->security == NULL if security_cred_alloc_blank() or + * security_prepare_creds() returned an error. + */ + if (selinux_is_enabled() && cred->security) { if ((unsigned long) cred->security < PAGE_SIZE) return true; if ((*(u32 *)cred->security & 0xffffff00) == diff --git a/security/selinux/hooks.c b/security/selinux/hooks.c index e276eb46853..c8d69927068 100644 --- a/security/selinux/hooks.c +++ b/security/selinux/hooks.c @@ -3198,7 +3198,11 @@ static void selinux_cred_free(struct cred *cred) { struct task_security_struct *tsec = cred->security; - BUG_ON((unsigned long) cred->security < PAGE_SIZE); + /* + * cred->security == NULL if security_cred_alloc_blank() or + * security_prepare_creds() returned an error. + */ + BUG_ON(cred->security && (unsigned long) cred->security < PAGE_SIZE); cred->security = (void *) 0x7UL; kfree(tsec); } -- cgit v1.2.3-70-g09d2 From 6037b715d6fab139742c3df8851db4c823081561 Mon Sep 17 00:00:00 2001 From: Chris Wright Date: Wed, 9 Feb 2011 22:11:51 -0800 Subject: security: add cred argument to security_capable() Expand security_capable() to include cred, so that it can be usable in a wider range of call sites. Signed-off-by: Chris Wright Acked-by: Serge Hallyn Signed-off-by: James Morris --- include/linux/security.h | 6 +++--- kernel/capability.c | 2 +- security/security.c | 5 ++--- 3 files changed, 6 insertions(+), 7 deletions(-) (limited to 'security') diff --git a/include/linux/security.h b/include/linux/security.h index c642bb8b8f5..b2b7f9749f5 100644 --- a/include/linux/security.h +++ b/include/linux/security.h @@ -1662,7 +1662,7 @@ int security_capset(struct cred *new, const struct cred *old, const kernel_cap_t *effective, const kernel_cap_t *inheritable, const kernel_cap_t *permitted); -int security_capable(int cap); +int security_capable(const struct cred *cred, int cap); int security_real_capable(struct task_struct *tsk, int cap); int security_real_capable_noaudit(struct task_struct *tsk, int cap); int security_sysctl(struct ctl_table *table, int op); @@ -1856,9 +1856,9 @@ static inline int security_capset(struct cred *new, return cap_capset(new, old, effective, inheritable, permitted); } -static inline int security_capable(int cap) +static inline int security_capable(const struct cred *cred, int cap) { - return cap_capable(current, current_cred(), cap, SECURITY_CAP_AUDIT); + return cap_capable(current, cred, cap, SECURITY_CAP_AUDIT); } static inline int security_real_capable(struct task_struct *tsk, int cap) diff --git a/kernel/capability.c b/kernel/capability.c index 2f05303715a..9e9385f132c 100644 --- a/kernel/capability.c +++ b/kernel/capability.c @@ -306,7 +306,7 @@ int capable(int cap) BUG(); } - if (security_capable(cap) == 0) { + if (security_capable(current_cred(), cap) == 0) { current->flags |= PF_SUPERPRIV; return 1; } diff --git a/security/security.c b/security/security.c index 739e40362f4..7b7308ace8c 100644 --- a/security/security.c +++ b/security/security.c @@ -154,10 +154,9 @@ int security_capset(struct cred *new, const struct cred *old, effective, inheritable, permitted); } -int security_capable(int cap) +int security_capable(const struct cred *cred, int cap) { - return security_ops->capable(current, current_cred(), cap, - SECURITY_CAP_AUDIT); + return security_ops->capable(current, cred, cap, SECURITY_CAP_AUDIT); } int security_real_capable(struct task_struct *tsk, int cap) -- cgit v1.2.3-70-g09d2