| // SPDX-License-Identifier: GPL-2.0-only |
| /* |
| * Copyright (C) 2004 IBM Corporation |
| * Copyright (C) 2014 Intel Corporation |
| */ |
| |
| #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> |
| |
| 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}, |
| }; |
| |
| /** |
| * tpm_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) |
| { |
| unsigned int blob_len; |
| struct tpm_buf buf; |
| u32 hash; |
| 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; |
| |
| rc = tpm_buf_init(&buf, TPM2_ST_SESSIONS, TPM2_CC_CREATE); |
| if (rc) |
| return rc; |
| |
| tpm_buf_append_u32(&buf, options->keyhandle); |
| tpm2_buf_append_auth(&buf, TPM2_RS_PW, |
| NULL /* nonce */, 0, |
| 0 /* session_attributes */, |
| options->keyauth /* hmac */, |
| TPM_DIGEST_SIZE); |
| |
| /* sensitive */ |
| tpm_buf_append_u16(&buf, 4 + TPM_DIGEST_SIZE + payload->key_len + 1); |
| |
| tpm_buf_append_u16(&buf, TPM_DIGEST_SIZE); |
| tpm_buf_append(&buf, options->blobauth, TPM_DIGEST_SIZE); |
| tpm_buf_append_u16(&buf, payload->key_len + 1); |
| tpm_buf_append(&buf, payload->key, payload->key_len); |
| tpm_buf_append_u8(&buf, payload->migratable); |
| |
| /* public */ |
| tpm_buf_append_u16(&buf, 14 + options->policydigest_len); |
| tpm_buf_append_u16(&buf, TPM_ALG_KEYEDHASH); |
| tpm_buf_append_u16(&buf, hash); |
| |
| /* policy */ |
| if (options->policydigest_len) { |
| tpm_buf_append_u32(&buf, 0); |
| tpm_buf_append_u16(&buf, options->policydigest_len); |
| tpm_buf_append(&buf, options->policydigest, |
| options->policydigest_len); |
| } else { |
| tpm_buf_append_u32(&buf, TPM2_OA_USER_WITH_AUTH); |
| tpm_buf_append_u16(&buf, 0); |
| } |
| |
| /* public parameters */ |
| tpm_buf_append_u16(&buf, TPM_ALG_NULL); |
| tpm_buf_append_u16(&buf, 0); |
| |
| /* outside info */ |
| tpm_buf_append_u16(&buf, 0); |
| |
| /* creation PCR */ |
| tpm_buf_append_u32(&buf, 0); |
| |
| if (buf.flags & TPM_BUF_OVERFLOW) { |
| rc = -E2BIG; |
| goto out; |
| } |
| |
| rc = tpm_send(chip, buf.data, tpm_buf_length(&buf)); |
| if (rc) |
| goto out; |
| |
| blob_len = be32_to_cpup((__be32 *) &buf.data[TPM_HEADER_SIZE]); |
| if (blob_len > MAX_BLOB_SIZE) { |
| rc = -E2BIG; |
| goto out; |
| } |
| if (tpm_buf_length(&buf) < TPM_HEADER_SIZE + 4 + blob_len) { |
| rc = -EFAULT; |
| goto out; |
| } |
| |
| memcpy(payload->blob, &buf.data[TPM_HEADER_SIZE + 4], blob_len); |
| payload->blob_len = blob_len; |
| |
| out: |
| tpm_buf_destroy(&buf); |
| |
| if (rc > 0) { |
| if (tpm2_rc_value(rc) == TPM2_RC_HASH) |
| rc = -EINVAL; |
| else |
| rc = -EPERM; |
| } |
| |
| 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; |
| int rc; |
| |
| private_len = be16_to_cpup((__be16 *) &payload->blob[0]); |
| if (private_len > (payload->blob_len - 2)) |
| return -E2BIG; |
| |
| public_len = be16_to_cpup((__be16 *) &payload->blob[2 + private_len]); |
| blob_len = private_len + public_len + 4; |
| if (blob_len > payload->blob_len) |
| return -E2BIG; |
| |
| rc = tpm_buf_init(&buf, TPM2_ST_SESSIONS, TPM2_CC_LOAD); |
| if (rc) |
| return rc; |
| |
| tpm_buf_append_u32(&buf, options->keyhandle); |
| tpm2_buf_append_auth(&buf, TPM2_RS_PW, |
| NULL /* nonce */, 0, |
| 0 /* session_attributes */, |
| options->keyauth /* hmac */, |
| TPM_DIGEST_SIZE); |
| |
| tpm_buf_append(&buf, payload->blob, blob_len); |
| |
| if (buf.flags & TPM_BUF_OVERFLOW) { |
| rc = -E2BIG; |
| goto out; |
| } |
| |
| rc = tpm_send(chip, buf.data, tpm_buf_length(&buf)); |
| if (!rc) |
| *blob_handle = be32_to_cpup( |
| (__be32 *) &buf.data[TPM_HEADER_SIZE]); |
| |
| out: |
| 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 = tpm_buf_init(&buf, TPM2_ST_SESSIONS, TPM2_CC_UNSEAL); |
| if (rc) |
| return rc; |
| |
| tpm_buf_append_u32(&buf, blob_handle); |
| tpm2_buf_append_auth(&buf, |
| options->policyhandle ? |
| options->policyhandle : TPM2_RS_PW, |
| NULL /* nonce */, 0, |
| TPM2_SA_CONTINUE_SESSION, |
| options->blobauth /* hmac */, |
| TPM_DIGEST_SIZE); |
| |
| rc = tpm_send(chip, buf.data, tpm_buf_length(&buf)); |
| 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 + 1) { |
| 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]; |
| |
| memcpy(payload->key, data, data_len - 1); |
| payload->key_len = data_len - 1; |
| payload->migratable = data[data_len - 1]; |
| } |
| |
| 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 = tpm2_load_cmd(chip, payload, options, &blob_handle); |
| if (rc) |
| return rc; |
| |
| rc = tpm2_unseal_cmd(chip, payload, options, blob_handle); |
| tpm2_flush_context(chip, blob_handle); |
| |
| return rc; |
| } |