[RFC PATCH 1/2] drivers: crypto: caam: key: Add caam_tk key type

From: Franck LENORMAND
Date: Fri Mar 01 2019 - 11:10:35 EST


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 <black key encryption> <size in bytes>
- set <black key encryption> <hex of a key>
- load <black key encryption> <hex of a blob>
with <black key encryption> = 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 <franck.lenormand@xxxxxxx>
---
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 <linux/kernel.h>
+#include <linux/slab.h>
+#include <linux/parser.h>
+#include <linux/string.h>
+#include <linux/key-type.h>
+#include <linux/rcupdate.h>
+#include <linux/completion.h>
+#include <linux/module.h>
+
+#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 <linux/rcupdate.h>
+#include <linux/key-type.h>
+#include <keys/user-type.h>
+#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
--
2.7.4