| // SPDX-License-Identifier: GPL-2.0-only |
| /* |
| * Copyright (C) 2004 IBM Corporation |
| * Copyright (C) 2014 Intel Corporation |
| */ |
| |
| #include <linux/asn1_encoder.h> |
| #include <linux/oid_registry.h> |
| #include <linux/string.h> |
| #include <linux/err.h> |
| #include <linux/tpm.h> |
| #include <linux/tpm_command.h> |
| |
| #include <keys/trusted-type.h> |
| #include <keys/trusted_tpm.h> |
| |
| #include <asm/unaligned.h> |
| |
| #include "tpm2key.asn1.h" |
| |
| static struct tpm2_hash tpm2_hash_map[] = { |
| {HASH_ALGO_SHA1, TPM_ALG_SHA1}, |
| {HASH_ALGO_SHA256, TPM_ALG_SHA256}, |
| {HASH_ALGO_SHA384, TPM_ALG_SHA384}, |
| {HASH_ALGO_SHA512, TPM_ALG_SHA512}, |
| {HASH_ALGO_SM3_256, TPM_ALG_SM3_256}, |
| }; |
| |
| static u32 tpm2key_oid[] = { 2, 23, 133, 10, 1, 5 }; |
| |
| static int tpm2_key_encode(struct trusted_key_payload *payload, |
| struct trusted_key_options *options, |
| u8 *src, u32 len) |
| { |
| const int SCRATCH_SIZE = PAGE_SIZE; |
| u8 *scratch = kmalloc(SCRATCH_SIZE, GFP_KERNEL); |
| u8 *work = scratch, *work1; |
| u8 *end_work = scratch + SCRATCH_SIZE; |
| u8 *priv, *pub; |
| u16 priv_len, pub_len; |
| int ret; |
| |
| priv_len = get_unaligned_be16(src) + 2; |
| priv = src; |
| |
| src += priv_len; |
| |
| pub_len = get_unaligned_be16(src) + 2; |
| pub = src; |
| |
| if (!scratch) |
| return -ENOMEM; |
| |
| work = asn1_encode_oid(work, end_work, tpm2key_oid, |
| asn1_oid_len(tpm2key_oid)); |
| |
| if (options->blobauth_len == 0) { |
| unsigned char bool[3], *w = bool; |
| /* tag 0 is emptyAuth */ |
| w = asn1_encode_boolean(w, w + sizeof(bool), true); |
| if (WARN(IS_ERR(w), "BUG: Boolean failed to encode")) { |
| ret = PTR_ERR(w); |
| goto err; |
| } |
| work = asn1_encode_tag(work, end_work, 0, bool, w - bool); |
| } |
| |
| /* |
| * Assume both octet strings will encode to a 2 byte definite length |
| * |
| * Note: For a well behaved TPM, this warning should never |
| * trigger, so if it does there's something nefarious going on |
| */ |
| if (WARN(work - scratch + pub_len + priv_len + 14 > SCRATCH_SIZE, |
| "BUG: scratch buffer is too small")) { |
| ret = -EINVAL; |
| goto err; |
| } |
| |
| work = asn1_encode_integer(work, end_work, options->keyhandle); |
| work = asn1_encode_octet_string(work, end_work, pub, pub_len); |
| work = asn1_encode_octet_string(work, end_work, priv, priv_len); |
| |
| work1 = payload->blob; |
| work1 = asn1_encode_sequence(work1, work1 + sizeof(payload->blob), |
| scratch, work - scratch); |
| if (IS_ERR(work1)) { |
| ret = PTR_ERR(work1); |
| pr_err("BUG: ASN.1 encoder failed with %d\n", ret); |
| goto err; |
| } |
| |
| kfree(scratch); |
| return work1 - payload->blob; |
| |
| err: |
| kfree(scratch); |
| return ret; |
| } |
| |
| struct tpm2_key_context { |
| u32 parent; |
| const u8 *pub; |
| u32 pub_len; |
| const u8 *priv; |
| u32 priv_len; |
| }; |
| |
| static int tpm2_key_decode(struct trusted_key_payload *payload, |
| struct trusted_key_options *options, |
| u8 **buf) |
| { |
| int ret; |
| struct tpm2_key_context ctx; |
| u8 *blob; |
| |
| memset(&ctx, 0, sizeof(ctx)); |
| |
| ret = asn1_ber_decoder(&tpm2key_decoder, &ctx, payload->blob, |
| payload->blob_len); |
| if (ret < 0) |
| return ret; |
| |
| if (ctx.priv_len + ctx.pub_len > MAX_BLOB_SIZE) |
| return -EINVAL; |
| |
| blob = kmalloc(ctx.priv_len + ctx.pub_len + 4, GFP_KERNEL); |
| if (!blob) |
| return -ENOMEM; |
| |
| *buf = blob; |
| options->keyhandle = ctx.parent; |
| |
| memcpy(blob, ctx.priv, ctx.priv_len); |
| blob += ctx.priv_len; |
| |
| memcpy(blob, ctx.pub, ctx.pub_len); |
| |
| return 0; |
| } |
| |
| int tpm2_key_parent(void *context, size_t hdrlen, |
| unsigned char tag, |
| const void *value, size_t vlen) |
| { |
| struct tpm2_key_context *ctx = context; |
| const u8 *v = value; |
| int i; |
| |
| ctx->parent = 0; |
| for (i = 0; i < vlen; i++) { |
| ctx->parent <<= 8; |
| ctx->parent |= v[i]; |
| } |
| |
| return 0; |
| } |
| |
| int tpm2_key_type(void *context, size_t hdrlen, |
| unsigned char tag, |
| const void *value, size_t vlen) |
| { |
| enum OID oid = look_up_OID(value, vlen); |
| |
| if (oid != OID_TPMSealedData) { |
| char buffer[50]; |
| |
| sprint_oid(value, vlen, buffer, sizeof(buffer)); |
| pr_debug("OID is \"%s\" which is not TPMSealedData\n", |
| buffer); |
| return -EINVAL; |
| } |
| |
| return 0; |
| } |
| |
| int tpm2_key_pub(void *context, size_t hdrlen, |
| unsigned char tag, |
| const void *value, size_t vlen) |
| { |
| struct tpm2_key_context *ctx = context; |
| |
| ctx->pub = value; |
| ctx->pub_len = vlen; |
| |
| return 0; |
| } |
| |
| int tpm2_key_priv(void *context, size_t hdrlen, |
| unsigned char tag, |
| const void *value, size_t vlen) |
| { |
| struct tpm2_key_context *ctx = context; |
| |
| ctx->priv = value; |
| ctx->priv_len = vlen; |
| |
| return 0; |
| } |
| |
| /** |
| * tpm2_buf_append_auth() - append TPMS_AUTH_COMMAND to the buffer. |
| * |
| * @buf: an allocated tpm_buf instance |
| * @session_handle: session handle |
| * @nonce: the session nonce, may be NULL if not used |
| * @nonce_len: the session nonce length, may be 0 if not used |
| * @attributes: the session attributes |
| * @hmac: the session HMAC or password, may be NULL if not used |
| * @hmac_len: the session HMAC or password length, maybe 0 if not used |
| */ |
| static void tpm2_buf_append_auth(struct tpm_buf *buf, u32 session_handle, |
| const u8 *nonce, u16 nonce_len, |
| u8 attributes, |
| const u8 *hmac, u16 hmac_len) |
| { |
| tpm_buf_append_u32(buf, 9 + nonce_len + hmac_len); |
| tpm_buf_append_u32(buf, session_handle); |
| tpm_buf_append_u16(buf, nonce_len); |
| |
| if (nonce && nonce_len) |
| tpm_buf_append(buf, nonce, nonce_len); |
| |
| tpm_buf_append_u8(buf, attributes); |
| tpm_buf_append_u16(buf, hmac_len); |
| |
| if (hmac && hmac_len) |
| tpm_buf_append(buf, hmac, hmac_len); |
| } |
| |
| /** |
| * tpm2_seal_trusted() - seal the payload of a trusted key |
| * |
| * @chip: TPM chip to use |
| * @payload: the key data in clear and encrypted form |
| * @options: authentication values and other options |
| * |
| * Return: < 0 on error and 0 on success. |
| */ |
| int tpm2_seal_trusted(struct tpm_chip *chip, |
| struct trusted_key_payload *payload, |
| struct trusted_key_options *options) |
| { |
| off_t offset = TPM_HEADER_SIZE; |
| struct tpm_buf buf, sized; |
| int blob_len = 0; |
| u32 hash; |
| u32 flags; |
| int i; |
| int rc; |
| |
| for (i = 0; i < ARRAY_SIZE(tpm2_hash_map); i++) { |
| if (options->hash == tpm2_hash_map[i].crypto_id) { |
| hash = tpm2_hash_map[i].tpm_id; |
| break; |
| } |
| } |
| |
| if (i == ARRAY_SIZE(tpm2_hash_map)) |
| return -EINVAL; |
| |
| if (!options->keyhandle) |
| return -EINVAL; |
| |
| rc = tpm_try_get_ops(chip); |
| if (rc) |
| return rc; |
| |
| rc = tpm2_start_auth_session(chip); |
| if (rc) |
| goto out_put; |
| |
| rc = tpm_buf_init(&buf, TPM2_ST_SESSIONS, TPM2_CC_CREATE); |
| if (rc) { |
| tpm2_end_auth_session(chip); |
| goto out_put; |
| } |
| |
| rc = tpm_buf_init_sized(&sized); |
| if (rc) { |
| tpm_buf_destroy(&buf); |
| tpm2_end_auth_session(chip); |
| goto out_put; |
| } |
| |
| tpm_buf_append_name(chip, &buf, options->keyhandle, NULL); |
| tpm_buf_append_hmac_session(chip, &buf, TPM2_SA_DECRYPT, |
| options->keyauth, TPM_DIGEST_SIZE); |
| |
| /* sensitive */ |
| tpm_buf_append_u16(&sized, options->blobauth_len); |
| |
| if (options->blobauth_len) |
| tpm_buf_append(&sized, options->blobauth, options->blobauth_len); |
| |
| tpm_buf_append_u16(&sized, payload->key_len); |
| tpm_buf_append(&sized, payload->key, payload->key_len); |
| tpm_buf_append(&buf, sized.data, sized.length); |
| |
| /* public */ |
| tpm_buf_reset_sized(&sized); |
| tpm_buf_append_u16(&sized, TPM_ALG_KEYEDHASH); |
| tpm_buf_append_u16(&sized, hash); |
| |
| /* key properties */ |
| flags = 0; |
| flags |= options->policydigest_len ? 0 : TPM2_OA_USER_WITH_AUTH; |
| flags |= payload->migratable ? 0 : (TPM2_OA_FIXED_TPM | TPM2_OA_FIXED_PARENT); |
| tpm_buf_append_u32(&sized, flags); |
| |
| /* policy */ |
| tpm_buf_append_u16(&sized, options->policydigest_len); |
| if (options->policydigest_len) |
| tpm_buf_append(&sized, options->policydigest, options->policydigest_len); |
| |
| /* public parameters */ |
| tpm_buf_append_u16(&sized, TPM_ALG_NULL); |
| tpm_buf_append_u16(&sized, 0); |
| |
| tpm_buf_append(&buf, sized.data, sized.length); |
| |
| /* outside info */ |
| tpm_buf_append_u16(&buf, 0); |
| |
| /* creation PCR */ |
| tpm_buf_append_u32(&buf, 0); |
| |
| if (buf.flags & TPM_BUF_OVERFLOW) { |
| rc = -E2BIG; |
| tpm2_end_auth_session(chip); |
| goto out; |
| } |
| |
| tpm_buf_fill_hmac_session(chip, &buf); |
| rc = tpm_transmit_cmd(chip, &buf, 4, "sealing data"); |
| rc = tpm_buf_check_hmac_response(chip, &buf, rc); |
| if (rc) |
| goto out; |
| |
| blob_len = tpm_buf_read_u32(&buf, &offset); |
| if (blob_len > MAX_BLOB_SIZE || buf.flags & TPM_BUF_BOUNDARY_ERROR) { |
| rc = -E2BIG; |
| goto out; |
| } |
| if (buf.length - offset < blob_len) { |
| rc = -EFAULT; |
| goto out; |
| } |
| |
| blob_len = tpm2_key_encode(payload, options, &buf.data[offset], blob_len); |
| |
| out: |
| tpm_buf_destroy(&sized); |
| tpm_buf_destroy(&buf); |
| |
| if (rc > 0) { |
| if (tpm2_rc_value(rc) == TPM2_RC_HASH) |
| rc = -EINVAL; |
| else |
| rc = -EPERM; |
| } |
| if (blob_len < 0) |
| rc = blob_len; |
| else |
| payload->blob_len = blob_len; |
| |
| out_put: |
| tpm_put_ops(chip); |
| return rc; |
| } |
| |
| /** |
| * tpm2_load_cmd() - execute a TPM2_Load command |
| * |
| * @chip: TPM chip to use |
| * @payload: the key data in clear and encrypted form |
| * @options: authentication values and other options |
| * @blob_handle: returned blob handle |
| * |
| * Return: 0 on success. |
| * -E2BIG on wrong payload size. |
| * -EPERM on tpm error status. |
| * < 0 error from tpm_send. |
| */ |
| static int tpm2_load_cmd(struct tpm_chip *chip, |
| struct trusted_key_payload *payload, |
| struct trusted_key_options *options, |
| u32 *blob_handle) |
| { |
| struct tpm_buf buf; |
| unsigned int private_len; |
| unsigned int public_len; |
| unsigned int blob_len; |
| u8 *blob, *pub; |
| int rc; |
| u32 attrs; |
| |
| rc = tpm2_key_decode(payload, options, &blob); |
| if (rc) { |
| /* old form */ |
| blob = payload->blob; |
| payload->old_format = 1; |
| } |
| |
| /* new format carries keyhandle but old format doesn't */ |
| if (!options->keyhandle) |
| return -EINVAL; |
| |
| /* must be big enough for at least the two be16 size counts */ |
| if (payload->blob_len < 4) |
| return -EINVAL; |
| |
| private_len = get_unaligned_be16(blob); |
| |
| /* must be big enough for following public_len */ |
| if (private_len + 2 + 2 > (payload->blob_len)) |
| return -E2BIG; |
| |
| public_len = get_unaligned_be16(blob + 2 + private_len); |
| if (private_len + 2 + public_len + 2 > payload->blob_len) |
| return -E2BIG; |
| |
| pub = blob + 2 + private_len + 2; |
| /* key attributes are always at offset 4 */ |
| attrs = get_unaligned_be32(pub + 4); |
| |
| if ((attrs & (TPM2_OA_FIXED_TPM | TPM2_OA_FIXED_PARENT)) == |
| (TPM2_OA_FIXED_TPM | TPM2_OA_FIXED_PARENT)) |
| payload->migratable = 0; |
| else |
| payload->migratable = 1; |
| |
| blob_len = private_len + public_len + 4; |
| if (blob_len > payload->blob_len) |
| return -E2BIG; |
| |
| rc = tpm2_start_auth_session(chip); |
| if (rc) |
| return rc; |
| |
| rc = tpm_buf_init(&buf, TPM2_ST_SESSIONS, TPM2_CC_LOAD); |
| if (rc) { |
| tpm2_end_auth_session(chip); |
| return rc; |
| } |
| |
| tpm_buf_append_name(chip, &buf, options->keyhandle, NULL); |
| tpm_buf_append_hmac_session(chip, &buf, 0, options->keyauth, |
| TPM_DIGEST_SIZE); |
| |
| tpm_buf_append(&buf, blob, blob_len); |
| |
| if (buf.flags & TPM_BUF_OVERFLOW) { |
| rc = -E2BIG; |
| tpm2_end_auth_session(chip); |
| goto out; |
| } |
| |
| tpm_buf_fill_hmac_session(chip, &buf); |
| rc = tpm_transmit_cmd(chip, &buf, 4, "loading blob"); |
| rc = tpm_buf_check_hmac_response(chip, &buf, rc); |
| if (!rc) |
| *blob_handle = be32_to_cpup( |
| (__be32 *) &buf.data[TPM_HEADER_SIZE]); |
| |
| out: |
| if (blob != payload->blob) |
| kfree(blob); |
| tpm_buf_destroy(&buf); |
| |
| if (rc > 0) |
| rc = -EPERM; |
| |
| return rc; |
| } |
| |
| /** |
| * tpm2_unseal_cmd() - execute a TPM2_Unload command |
| * |
| * @chip: TPM chip to use |
| * @payload: the key data in clear and encrypted form |
| * @options: authentication values and other options |
| * @blob_handle: blob handle |
| * |
| * Return: 0 on success |
| * -EPERM on tpm error status |
| * < 0 error from tpm_send |
| */ |
| static int tpm2_unseal_cmd(struct tpm_chip *chip, |
| struct trusted_key_payload *payload, |
| struct trusted_key_options *options, |
| u32 blob_handle) |
| { |
| struct tpm_buf buf; |
| u16 data_len; |
| u8 *data; |
| int rc; |
| |
| rc = tpm2_start_auth_session(chip); |
| if (rc) |
| return rc; |
| |
| rc = tpm_buf_init(&buf, TPM2_ST_SESSIONS, TPM2_CC_UNSEAL); |
| if (rc) { |
| tpm2_end_auth_session(chip); |
| return rc; |
| } |
| |
| tpm_buf_append_name(chip, &buf, blob_handle, NULL); |
| |
| if (!options->policyhandle) { |
| tpm_buf_append_hmac_session(chip, &buf, TPM2_SA_ENCRYPT, |
| options->blobauth, |
| options->blobauth_len); |
| } else { |
| /* |
| * FIXME: The policy session was generated outside the |
| * kernel so we don't known the nonce and thus can't |
| * calculate a HMAC on it. Therefore, the user can |
| * only really use TPM2_PolicyPassword and we must |
| * send down the plain text password, which could be |
| * intercepted. We can still encrypt the returned |
| * key, but that's small comfort since the interposer |
| * could repeat our actions with the exfiltrated |
| * password. |
| */ |
| tpm2_buf_append_auth(&buf, options->policyhandle, |
| NULL /* nonce */, 0, 0, |
| options->blobauth, options->blobauth_len); |
| tpm_buf_append_hmac_session_opt(chip, &buf, TPM2_SA_ENCRYPT, |
| NULL, 0); |
| } |
| |
| tpm_buf_fill_hmac_session(chip, &buf); |
| rc = tpm_transmit_cmd(chip, &buf, 6, "unsealing"); |
| rc = tpm_buf_check_hmac_response(chip, &buf, rc); |
| if (rc > 0) |
| rc = -EPERM; |
| |
| if (!rc) { |
| data_len = be16_to_cpup( |
| (__be16 *) &buf.data[TPM_HEADER_SIZE + 4]); |
| if (data_len < MIN_KEY_SIZE || data_len > MAX_KEY_SIZE) { |
| rc = -EFAULT; |
| goto out; |
| } |
| |
| if (tpm_buf_length(&buf) < TPM_HEADER_SIZE + 6 + data_len) { |
| rc = -EFAULT; |
| goto out; |
| } |
| data = &buf.data[TPM_HEADER_SIZE + 6]; |
| |
| if (payload->old_format) { |
| /* migratable flag is at the end of the key */ |
| memcpy(payload->key, data, data_len - 1); |
| payload->key_len = data_len - 1; |
| payload->migratable = data[data_len - 1]; |
| } else { |
| /* |
| * migratable flag already collected from key |
| * attributes |
| */ |
| memcpy(payload->key, data, data_len); |
| payload->key_len = data_len; |
| } |
| } |
| |
| out: |
| tpm_buf_destroy(&buf); |
| return rc; |
| } |
| |
| /** |
| * tpm2_unseal_trusted() - unseal the payload of a trusted key |
| * |
| * @chip: TPM chip to use |
| * @payload: the key data in clear and encrypted form |
| * @options: authentication values and other options |
| * |
| * Return: Same as with tpm_send. |
| */ |
| int tpm2_unseal_trusted(struct tpm_chip *chip, |
| struct trusted_key_payload *payload, |
| struct trusted_key_options *options) |
| { |
| u32 blob_handle; |
| int rc; |
| |
| rc = tpm_try_get_ops(chip); |
| if (rc) |
| return rc; |
| |
| rc = tpm2_load_cmd(chip, payload, options, &blob_handle); |
| if (rc) |
| goto out; |
| |
| rc = tpm2_unseal_cmd(chip, payload, options, blob_handle); |
| tpm2_flush_context(chip, blob_handle); |
| |
| out: |
| tpm_put_ops(chip); |
| |
| return rc; |
| } |