From patchwork Fri Mar 1 16:09:58 2019 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Franck Lenormand X-Patchwork-Id: 10835631 Return-Path: Received: from mail.wl.linuxfoundation.org (pdx-wl-mail.web.codeaurora.org [172.30.200.125]) by pdx-korg-patchwork-2.web.codeaurora.org (Postfix) with ESMTP id DB3D5139A for ; Fri, 1 Mar 2019 16:10:40 +0000 (UTC) Received: from mail.wl.linuxfoundation.org (localhost [127.0.0.1]) by mail.wl.linuxfoundation.org (Postfix) with ESMTP id C7AF52FE8C for ; Fri, 1 Mar 2019 16:10:40 +0000 (UTC) Received: by mail.wl.linuxfoundation.org (Postfix, from userid 486) id BBB4A2FEC8; Fri, 1 Mar 2019 16:10:40 +0000 (UTC) X-Spam-Checker-Version: SpamAssassin 3.3.1 (2010-03-16) on pdx-wl-mail.web.codeaurora.org X-Spam-Level: X-Spam-Status: No, score=-6.9 required=2.0 tests=BAYES_00,RCVD_IN_DNSWL_HI autolearn=ham version=3.3.1 Received: from vger.kernel.org (vger.kernel.org [209.132.180.67]) by mail.wl.linuxfoundation.org (Postfix) with ESMTP id 4716B2FE5D for ; Fri, 1 Mar 2019 16:10:39 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S2389064AbfCAQKc (ORCPT ); Fri, 1 Mar 2019 11:10:32 -0500 Received: from inva021.nxp.com ([92.121.34.21]:55402 "EHLO inva021.nxp.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1725966AbfCAQKa (ORCPT ); Fri, 1 Mar 2019 11:10:30 -0500 Received: from inva021.nxp.com (localhost [127.0.0.1]) by inva021.eu-rdc02.nxp.com (Postfix) with ESMTP id 6458920004A; Fri, 1 Mar 2019 17:10:26 +0100 (CET) Received: from inva024.eu-rdc02.nxp.com (inva024.eu-rdc02.nxp.com [134.27.226.22]) by inva021.eu-rdc02.nxp.com (Postfix) with ESMTP id 50C0C200002; Fri, 1 Mar 2019 17:10:26 +0100 (CET) Received: from sopsvulinux.ea.freescale.net (sopsvulinux.ea.freescale.net [10.161.69.178]) by inva024.eu-rdc02.nxp.com (Postfix) with ESMTP id 35EA620634; Fri, 1 Mar 2019 17:10:26 +0100 (CET) Received: from SOPDPUATS05.ea.freescale.net (SOPDPUATS05.ea.freescale.net [10.161.66.238]) by sopsvulinux.ea.freescale.net (Postfix) with ESMTP id 5433482C7C; Fri, 1 Mar 2019 17:08:45 +0100 (CET) From: Franck LENORMAND To: linux-kernel@vger.kernel.org, linux-security-module@vger.kernel.org, keyrings@vger.kernel.org Cc: franck.lenormand@nxp.com, horia.geanta@nxp.com, silvano.dininno@nxp.com, agk@redhat.com, snitzer@redhat.com, dm-devel@redhat.com, dhowells@redhat.com, jmorris@namei.org, serge@hallyn.com Subject: [RFC PATCH 1/2] drivers: crypto: caam: key: Add caam_tk key type Date: Fri, 1 Mar 2019 17:09:58 +0100 Message-Id: <1551456599-10603-2-git-send-email-franck.lenormand@nxp.com> X-Mailer: git-send-email 2.7.4 In-Reply-To: <1551456599-10603-1-git-send-email-franck.lenormand@nxp.com> References: <1551456599-10603-1-git-send-email-franck.lenormand@nxp.com> X-Virus-Scanned: ClamAV using ClamSMTP Sender: owner-linux-security-module@vger.kernel.org Precedence: bulk List-ID: X-Virus-Scanned: ClamAV using ClamSMTP This patch adds a module which creates a new key type which can be used by the user with the linux key retention service. The key created by this module are black keys appended with a tag to create a tag key. Such a key can be passed to the linux crypto API for the transforms: - tk(cbc(aes)) The configuration string passed to the key service has 3 forms: - new - set - load with = ecb | ccm When reading or printing a key, it will return a binary blob which can be saved to a file through powercycle. The blob can then be loaded. V2: Expect the data to be loaded to be prepended by ':hex:' Signed-off-by: Franck LENORMAND --- drivers/crypto/caam/caam_key.c | 623 +++++++++++++++++++++++++++++++++++++++++ drivers/crypto/caam/caam_key.h | 58 ++++ 2 files changed, 681 insertions(+) create mode 100644 drivers/crypto/caam/caam_key.c create mode 100644 drivers/crypto/caam/caam_key.h diff --git a/drivers/crypto/caam/caam_key.c b/drivers/crypto/caam/caam_key.c new file mode 100644 index 0000000..5d89c9d --- /dev/null +++ b/drivers/crypto/caam/caam_key.c @@ -0,0 +1,623 @@ +// SPDX-License-Identifier: GPL-2.0 +/* Copyright (C) 2018 NXP + * caam key is generated using NXP CAAM hardware block. CAAM generates the + * random number (used as a key) and creates its blob for the user. + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "desc.h" +#include "desc_constr.h" +#include "caam_desc.h" +#include "caam_key.h" +#include "caam_util.h" + +/* Key modifier for CAAM key blobbing */ +static const char caam_key_modifier[KEYMOD_SIZE_GM] = { + 'C', 'A', 'A', 'M', '_', 'K', 'E', 'Y', + '_', 'T', 'Y', 'P', 'E', '_', 'V', '1', +}; + +/* Operation supported */ +enum caam_key_op { + OP_ERROR = -1, + OP_NEW_KEY, + OP_SET_KEY, + OP_LOAD_BLOB, +}; + +/* Tokens for the operation to do */ +static const match_table_t key_cmd_tokens = { + {OP_NEW_KEY, "new"}, + {OP_SET_KEY, "set"}, + {OP_LOAD_BLOB, "load"}, + {OP_ERROR, NULL} +}; + +enum caam_key_fmt { + FMT_ERROR = -1, + FMT_ECB, + FMT_CCM, +}; + +/* Tokens for the type of encryption of the black key */ +static const char FMT_ECB_txt[] = "ecb"; +static const char FMT_CCM_txt[] = "ccm"; + +static const match_table_t key_fmt_tokens = { + {FMT_ECB, FMT_ECB_txt}, + {FMT_CCM, FMT_CCM_txt}, + {FMT_ERROR, NULL} +}; + +int caam_key_tag_black_key(struct caam_key_payload *ckpayload, + size_t black_key_max_len, u8 auth, u8 trusted) +{ + struct tag_object_conf tag; + enum tag_type type; + int ret; + u32 size_tagged = black_key_max_len; + + if (!ckpayload) + return -EINVAL; + + if (!is_auth(auth) || !is_trusted_key(trusted)) + return -EINVAL; + + if (auth == KEY_COVER_ECB) { + if (trusted == UNTRUSTED_KEY) + type = TAG_TYPE_BLACK_KEY_ECB; + else + type = TAG_TYPE_BLACK_KEY_ECB_TRUSTED; + } else { + if (trusted == UNTRUSTED_KEY) + type = TAG_TYPE_BLACK_KEY_CCM; + else + type = TAG_TYPE_BLACK_KEY_CCM_TRUSTED; + } + + /* Prepare the tag */ + init_tag_object_header(&tag.header, type); + init_blackey_conf(&tag.conf.bk_conf, ckpayload->key_len, + auth == KEY_COVER_CCM, + trusted == TRUSTED_KEY); + + ret = set_tag_object_conf(&tag, ckpayload->black_key, + ckpayload->black_key_len, &size_tagged); + if (ret) { + pr_err("Tagging fail: %d\n", ret); + goto exit; + } + + /* Update the size of the black key tagged */ + ckpayload->black_key_len = size_tagged; + +exit: + return ret; +} + +static int caam_transform(enum caam_key_op key_cmd, + struct caam_key_payload *ckpayload) +{ + int ret; + struct device *jrdev; + u8 key_cover; + + /* Allocate caam job ring for operation to be performed from CAAM */ + jrdev = caam_jr_alloc(); + if (!jrdev) { + pr_info("caam_jr_alloc failed\n"); + ret = -ENODEV; + goto out; + } + + if (ckpayload->key_fmt_val == FMT_ECB) + key_cover = KEY_COVER_ECB; + else + key_cover = KEY_COVER_CCM; + + switch (key_cmd) { + case OP_LOAD_BLOB: +#ifdef DEBUG + print_hex_dump(KERN_ERR, "input blob: ", + DUMP_PREFIX_OFFSET, 16, 4, ckpayload->blob, + ckpayload->blob_len, 0); +#endif + /* Decapsulate the black blob into a black key */ + ret = caam_blob_decap(jrdev, + ckpayload->blob, ckpayload->blob_len, + DATA_GENMEM, BLACK_BLOB, + ckpayload->key_mod, + &ckpayload->key_mod_len, DATA_GENMEM, + ckpayload->black_key, + &ckpayload->black_key_len, DATA_GENMEM, + BLACK_KEY, &ckpayload->key_len, + key_cover, UNTRUSTED_KEY); + if (ret) { + pr_info("key_blob decap fail: %d\n", ret); + goto free_jr; + } + + break; + case OP_SET_KEY: + +#ifdef DEBUG + print_hex_dump(KERN_ERR, "input key: ", + DUMP_PREFIX_OFFSET, 16, 4, ckpayload->key, + ckpayload->key_len, 0); +#endif + + /* Cover the input key */ + ret = caam_black_key(jrdev, + ckpayload->key, ckpayload->key_len, + DATA_GENMEM, + ckpayload->black_key, + &ckpayload->black_key_len, DATA_GENMEM, + key_cover, UNTRUSTED_KEY); + /* + * Clear the input key + * TODO: Make it secure to not be removed by compiler + */ + memset(ckpayload->key, 0, ckpayload->key_len); + + if (ret) { + pr_info("key covering fail: (%d)\n", ret); + goto free_jr; + } + + /* Encapsulate the key */ + ret = caam_blob_encap(jrdev, + ckpayload->black_key, + ckpayload->black_key_len, DATA_GENMEM, + BLACK_KEY, ckpayload->key_len, key_cover, + UNTRUSTED_KEY, + ckpayload->key_mod, + &ckpayload->key_mod_len, DATA_GENMEM, + ckpayload->blob, &ckpayload->blob_len, + DATA_GENMEM, BLACK_BLOB); + if (ret) { + pr_info("Blob encapsulation of key fail: %d\n", ret); + goto free_jr; + } + + break; + case OP_NEW_KEY: + /* + * We need random data to create a key however we do not + * want + */ + ret = caam_random_black_key(jrdev, + ckpayload->key_len, + ckpayload->black_key, + &ckpayload->black_key_len, + DATA_GENMEM, key_cover, + UNTRUSTED_KEY); + + if (ret) { + pr_info("Random key covering fail: %d\n", ret); + goto free_jr; + } + + /* Encapsulate the key */ + ret = caam_blob_encap(jrdev, + ckpayload->black_key, + ckpayload->black_key_len, DATA_GENMEM, + BLACK_KEY, ckpayload->key_len, key_cover, + UNTRUSTED_KEY, + ckpayload->key_mod, + &ckpayload->key_mod_len, DATA_GENMEM, + ckpayload->blob, &ckpayload->blob_len, + DATA_GENMEM, BLACK_BLOB); + if (ret) { + pr_info("Blob encapsulation of random fail: %d\n", ret); + goto free_jr; + } + + break; + default: + ret = -EINVAL; + } + +#ifdef DEBUG + print_hex_dump(KERN_ERR, "black key: ", + DUMP_PREFIX_OFFSET, 16, 4, ckpayload->black_key, + ckpayload->black_key_len, 0); + print_hex_dump(KERN_ERR, "blob: ", + DUMP_PREFIX_OFFSET, 16, 4, ckpayload->blob, + ckpayload->blob_len, 0); +#endif + + /* Tag the black key so it can be passed to CAAM crypto API */ + ret = caam_key_tag_black_key(ckpayload, + ARRAY_SIZE(ckpayload->black_key), + key_cover, UNTRUSTED_KEY); + if (ret) { + pr_info("Black key tagging fail: %d\n", ret); + goto free_jr; + } + +#ifdef DEBUG + print_hex_dump(KERN_ERR, "tagged black key: ", + DUMP_PREFIX_OFFSET, 16, 4, ckpayload->black_key, + ckpayload->black_key_len, 0); +#endif + + /* Update the aliased user_key_payload */ + ckpayload->upayload.datalen = ckpayload->black_key_len; + memcpy(ckpayload->upayload.data, ckpayload->black_key, + ckpayload->upayload.datalen); + +free_jr: + caam_jr_free(jrdev); + +out: + if (ret) + pr_err("Operation %s(%d) failed\n", + key_cmd_tokens[key_cmd].pattern, key_cmd); + + return ret; +} + +/* + * parse_inputdata - parse the keyctl input data and fill in the + * payload structure for key or its blob. + * param[in]: data pointer to the data to be parsed for creating key. + * param[in]: p pointer to caam key payload structure to fill parsed data + * On success returns 0, otherwise -EINVAL. + */ +static enum caam_key_op parse_inputdata(char *data, + struct caam_key_payload *ckpayload) +{ + substring_t args[MAX_OPT_ARGS]; + long keylen = 0; + int ret = 0; + enum caam_key_op op_to_do = OP_ERROR; + int key_cmd = -EINVAL; + int key_fmt = -EINVAL; + char *c = NULL; + const char *hex_format = ":hex:"; + u32 hex_format_size; + + c = strsep(&data, " \t"); + if (!c) { + ret = -EINVAL; + pr_err("Failed to find 1st arg\n"); + goto out; + } + + /* Get the keyctl command i.e. new_key or load_blob etc */ + key_cmd = match_token(c, key_cmd_tokens, args); + + /* Skip spaces to get the 1st argument */ + c = strsep(&data, " \t"); + if (!c) { + ret = -EINVAL; + pr_err("Failed to find 2nd arg\n"); + goto out; + } + + /* Get the keyctl format i.e. ecb or ccm etc */ + key_fmt = match_token(c, key_fmt_tokens, args); + + /* Skip spaces to get second argument */ + c = strsep(&data, " \t"); + if (!c) { + ret = -EINVAL; + pr_err("Failed to find 3rd arg\n"); + goto out; + } + + switch (key_fmt) { + case FMT_ECB: + ckpayload->key_fmt_val = KEY_COVER_ECB; + break; + case FMT_CCM: + ckpayload->key_fmt_val = KEY_COVER_CCM; + break; + case FMT_ERROR: + ret = -EINVAL; + pr_err("Format %d not supported\n", key_fmt); + goto out; + } + + /* Prepare arguments */ + switch (key_cmd) { + case OP_NEW_KEY: + /* Second argument is key size */ + ret = kstrtol(c, 10, &keylen); + if (ret < 0 || keylen < MIN_KEY_SIZE || + keylen > MAX_KEY_SIZE) { + ret = -EINVAL; + pr_err("Failed to retrieve key length\n"); + goto out; + } + + ckpayload->key_len = keylen; + + ckpayload->black_key_len = ARRAY_SIZE(ckpayload->black_key); + ckpayload->blob_len = ARRAY_SIZE(ckpayload->blob); + + op_to_do = OP_NEW_KEY; + + break; + case OP_SET_KEY: + /* Second argument is key data for CAAM*/ + + /* key_len = No of characters in key/2 */ + ckpayload->key_len = strlen(c) / 2; + if (ckpayload->blob_len > MAX_KEY_SIZE) { + ret = -EINVAL; + pr_err("Failed to compute key length\n"); + goto out; + } + + ret = hex2bin(ckpayload->key, c, ckpayload->key_len); + if (ret < 0) { + ret = -EINVAL; + pr_err("Failed to retrieve key data\n"); + goto out; + } + + ckpayload->black_key_len = ARRAY_SIZE(ckpayload->black_key); + ckpayload->blob_len = ARRAY_SIZE(ckpayload->blob); + + op_to_do = OP_SET_KEY; + + break; + case OP_LOAD_BLOB: + /* Second argument is blob data for CAAM */ + hex_format_size = strlen(hex_format); + + /* The blob is prepended by the format */ + if (strncmp(c, hex_format, hex_format_size) != 0) { + ret = -EINVAL; + pr_err("Failed to match blob format\n"); + goto out; + } + + /* Advance the pointer */ + c += hex_format_size; + + /* Blob_len = No of characters in blob/2 */ + ckpayload->blob_len = strlen(c) / 2; + if (ckpayload->blob_len > MAX_BLOB_SIZE) { + ret = -EINVAL; + pr_err("Failed to compute blob length\n"); + goto out; + } + + ret = hex2bin(ckpayload->blob, c, ckpayload->blob_len); + if (ret < 0) { + ret = -EINVAL; + pr_err("Failed to retrieve blob data\n"); + goto out; + } + + ckpayload->key_len = ARRAY_SIZE(ckpayload->key); + ckpayload->black_key_len = ARRAY_SIZE(ckpayload->black_key); + + op_to_do = OP_LOAD_BLOB; + + break; + case OP_ERROR: + ret = -EINVAL; + pr_err("Command %d not supported\n", key_cmd); + break; + } + + ckpayload->key_mod = caam_key_modifier; + ckpayload->key_mod_len = ARRAY_SIZE(caam_key_modifier); + +out: + return (ret == 0) ? op_to_do : OP_ERROR; +} + +static struct caam_key_payload *caam_payload_alloc(struct key *key) +{ + struct caam_key_payload *ckpayload = NULL; + int ret = 0; + + ret = key_payload_reserve(key, sizeof(*ckpayload)); + if (ret < 0) { + pr_err("Failed to reserve payload\n"); + goto out; + } + + ckpayload = kzalloc(sizeof(*ckpayload), GFP_KERNEL); + if (!ckpayload) + goto out; + +out: + return ckpayload; +} + +/* + * caam_destroy - clear and free the key's payload + */ +static void caam_destroy(struct key *key) +{ + struct caam_key_payload *ckpayload = NULL; + + /* Retrieve the payload */ + ckpayload = dereference_key_locked(key); + if (!ckpayload) + pr_err("Fail to retrieve key payload\n"); + + kzfree(ckpayload); +} + +/* + * caam_instantiate - create a new caam type key. + * Supports the operation to generate a new key. A random number + * is generated from CAAM as key data and the corresponding red blob + * is formed and stored as key_blob. + * Also supports the operation to load the blob and key is derived using + * that blob from CAAM. + * On success, return 0. Otherwise return errno. + */ +static int caam_instantiate(struct key *key, + struct key_preparsed_payload *prep) +{ + struct caam_key_payload *ckpayload; + size_t datalen; + char *data = NULL; + int key_cmd = 0; + int ret = 0; + + if (!key || !prep) { + ret = -EINVAL; + pr_err("Input data incorrect\n"); + goto out; + } + + datalen = prep->datalen; + + if (datalen <= 0 || datalen > 32767) { + ret = -EINVAL; + pr_err("Payload data size incorrect\n"); + goto out; + } + + /* Allocate memory to get a parsable string */ + data = kmalloc(datalen + 1, GFP_KERNEL); + if (!data) { + ret = -ENOMEM; + goto out; + } + + memcpy(data, prep->data, datalen); + data[datalen] = '\0'; + + ckpayload = caam_payload_alloc(key); + if (!ckpayload) { + pr_err("Fail to allocate payload\n"); + ret = -ENOMEM; + goto free_data; + } + + /* Initialize and fill the payload */ + key_cmd = parse_inputdata(data, ckpayload); + if (key_cmd == OP_ERROR) { + pr_err("Fail to parse data\n"); + ret = key_cmd; + goto free_payload; + } + + /* Create the black key and/or the blob */ + caam_transform(key_cmd, ckpayload); + if (ret != 0) { + pr_info("transform fail (%d)\n", ret); + goto free_payload; + } + + /* Store the payload to the key */ + rcu_assign_keypointer(key, ckpayload); + + goto out; + +free_payload: + kzfree(ckpayload); + +free_data: + kzfree(data); + +out: + return ret; +} + +/* + * caam_read - copy the blob data to userspace. + * param[in]: key pointer to key struct + * param[in]: buffer pointer to user data for creating key + * param[in]: buflen is the length of the buffer + * On success, return to userspace the caam key data size. + */ +static long caam_read(const struct key *key, char __user *buffer, size_t buflen) +{ + const struct caam_key_payload *ckpayload = NULL; + size_t size_to_copy; + size_t size_copied = 0; + unsigned long not_copied; + char *to = buffer; + + /* Retrieve the payload */ + ckpayload = dereference_key_locked(key); + if (!ckpayload) { + pr_err("Fail to retrieve key payload\n"); + return -EINVAL; + } + + /* Check all the data can be copied */ + size_to_copy = ckpayload->blob_len; + + /* If buflen == 0, the user request the size needed */ + if (buflen == 0) + return size_to_copy; + + /* Check the buffer */ + if (!buffer) { + pr_err("Buffer not set\n"); + return -EINVAL; + } + + /* Check the buffer is big enough */ + if (size_to_copy > buflen) { + pr_err("Buffer length too short\n"); + return -ENOMEM; + } + + /* Copy blob */ + not_copied = copy_to_user(to, ckpayload->blob, ckpayload->blob_len); + if (not_copied != 0) { + pr_err("Copy of black blob failed\n"); + return -EIO; + } + size_copied += ckpayload->blob_len; + + if (size_to_copy != size_copied) + pr_info("Mismatch between size computed and copied\n"); + + return size_copied; +} + +/* Description of the key type for CAAM keys */ +struct key_type key_type_caam_tk = { + .name = "caam_tk", + .instantiate = caam_instantiate, + .destroy = caam_destroy, + .read = caam_read, +}; +EXPORT_SYMBOL_GPL(key_type_caam_tk); + +static int __init init_caam_key(void) +{ + int ret; + + ret = register_key_type(&key_type_caam_tk); + if (ret) { + pr_err("Failed to register key storage %s\n", + key_type_caam_tk.name); + return -EIO; + } + + return ret; +} + +static void __exit cleanup_caam_key(void) +{ + unregister_key_type(&key_type_caam_tk); +} + +late_initcall(init_caam_key); +module_exit(cleanup_caam_key); + +MODULE_LICENSE("GPL"); diff --git a/drivers/crypto/caam/caam_key.h b/drivers/crypto/caam/caam_key.h new file mode 100644 index 0000000..93273ea --- /dev/null +++ b/drivers/crypto/caam/caam_key.h @@ -0,0 +1,58 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright (C) 2018 NXP. + * + */ + +#ifndef _KEYS_caam_TYPE_H +#define _KEYS_caam_TYPE_H + +#include +#include +#include +#include "caam_desc.h" +#include "tag_object.h" + +extern struct key_type key_type_caam_tk; + +/* Minimum key size to be used is 32 bytes and maximum key size fixed + * is 128 bytes. + * Blob size to be kept is Maximum key size + blob header added by CAAM. + */ + +#define MIN_KEY_SIZE 16 +#define MAX_KEY_SIZE 128 + +#define MAX_BLACK_KEY_SIZE (MAX_KEY_SIZE + CCM_OVERHEAD +\ + TAG_OVERHEAD) + +#define MAX_BLOB_SIZE (MAX_KEY_SIZE + BLOB_OVERHEAD) + +struct caam_key_payload { + /* + * The aliasing of the structure allow user to see this payload + * as a user defined payload + * + * The structure has to be set during execution + */ + struct aliased_user_key_payload { + struct rcu_head rcu; + unsigned short datalen; + char data[MAX_BLACK_KEY_SIZE]; + } upayload; + + size_t key_len; + unsigned char key[MAX_KEY_SIZE + 1]; + int key_fmt_val; + + size_t black_key_len; + unsigned char black_key[MAX_BLACK_KEY_SIZE]; + + size_t blob_len; + unsigned char blob[MAX_BLOB_SIZE]; + + size_t key_mod_len; + const void *key_mod; +}; + +#endif