diff options
Diffstat (limited to 'drivers/crypto/caam/caamkeygen.c')
-rw-r--r-- | drivers/crypto/caam/caamkeygen.c | 629 |
1 files changed, 629 insertions, 0 deletions
diff --git a/drivers/crypto/caam/caamkeygen.c b/drivers/crypto/caam/caamkeygen.c new file mode 100644 index 000000000000..04db6e044b8a --- /dev/null +++ b/drivers/crypto/caam/caamkeygen.c @@ -0,0 +1,629 @@ +// SPDX-License-Identifier: (GPL-2.0+ OR BSD-3-Clause) +/* + * Copyright 2020 NXP + */ + +#include <linux/types.h> +#include <linux/kernel.h> +#include <linux/fs.h> +#include <linux/miscdevice.h> +#include <linux/device.h> +#include <linux/slab.h> +#include <linux/uaccess.h> +#include <linux/ioctl.h> + +#include "caamkeyblob.h" +#include "intern.h" +#include <linux/caam_keygen.h> + +#define DEVICE_NAME "caam-keygen" + +static long caam_keygen_ioctl(struct file *file, unsigned int cmd, + unsigned long arg); + +/** + * tag_black_obj - Tag a black object (key/blob) with a tag object header. + * + * @info : keyblob_info structure, which contains + * the black key/blob, obtained from CAAM, + * that needs to be tagged + * @black_max_len : The maximum size of the black object (blob/key) + * @blob : Used to determine if it's a blob or key object + * + * Return : '0' on success, error code otherwise + */ +static int tag_black_obj(struct keyblob_info *info, size_t black_max_len, + bool blob) +{ + struct header_conf tag; + u32 type; + int ret; + u32 size_tagged = black_max_len; + + if (!info) + return -EINVAL; + + type = info->type; + + /* Prepare and set the tag */ + if (blob) { + init_tag_object_header(&tag, 0, type, info->key_len, + info->blob_len); + ret = set_tag_object_header_conf(&tag, info->blob, + info->blob_len, + &size_tagged); + } else { + init_tag_object_header(&tag, 0, type, info->key_len, + info->black_key_len); + ret = set_tag_object_header_conf(&tag, info->black_key, + info->black_key_len, + &size_tagged); + } + if (ret) + return ret; + + /* Update the size of the black key tagged */ + if (blob) + info->blob_len = size_tagged; + else + info->black_key_len = size_tagged; + + return ret; +} +/** + * send_err_msg - Send the error message from kernel to user-space + * + * @msg : The message to be sent + * @output : The output buffer where we want to copy the error msg + * @size : The size of output buffer + */ +static void send_err_msg(char *msg, void __user *output, size_t size) +{ + size_t min_s; + char null_ch = 0; + + /* Not enough space to copy any message */ + if (size <= 1) + return; + + min_s = min(size - 1, strlen(msg)); + /* + * Avoid compile and checkpatch warnings, since we don't + * care about return value from copy_to_user + */ + (void)(copy_to_user(output, msg, min_s) + 1); + /* Copy null terminator */ + (void)(copy_to_user((output + min_s), &null_ch, 1) + 1); +} + +/** + * validate_key_size - Validate the key size from user. + * This can be the exact size given by user when + * generating a black key from random (with -s), + * or the size of the plaintext (with -t). + * + * @key_len : The size of key we want to validate + * @output : The output buffer where we want to copy the error msg + * @size : The size of output buffer + * + *Return : '0' on success, error code otherwise + */ +static int validate_key_size(size_t key_len, void __user *output, size_t size) +{ + char *msg = NULL; + + if (key_len < MIN_KEY_SIZE || key_len > MAX_KEY_SIZE) { + msg = "Invalid key size, expected values are between 16 and 64 bytes.\n"; + send_err_msg(msg, output, size); + return -EINVAL; + } + + return 0; +} + +/** + * validate_input - Validate the input from user and set the + * keyblob_info structure. + * This contains the input key in case of black key + * generated from plaintext or size for random + * black key. + * + * @key_crt : Structure with data from user + * @arg : User-space argument from ioctl call + * @info : keyblob_info structure, will be updated with all the + * data from user-space + * @create_key_op : Used to determine if it's a create or import operation + * + * Return : '0' on success, error code otherwise + */ +static int validate_input(struct caam_keygen_cmd *key_crt, unsigned long arg, + struct keyblob_info *info, bool create_key_op) +{ + char *tmp, *msg; + size_t tmp_size; + bool random = false; + int ret = 0; + u32 tmp_len = 0; + char null_ch = 0; + + /* + * So far, we only support Black keys, encrypted with JDKEK, + * kept in general memory, non-secure state. + * Therefore, default value for type is 1. + */ + u32 type = 1; + + if (copy_from_user(key_crt, (void __user *)arg, + sizeof(struct caam_keygen_cmd))) + return -EFAULT; + + /* Get blob_len from user. */ + info->blob_len = key_crt->blob_len; + /* Get black_key_len from user. */ + info->black_key_len = key_crt->black_key_len; + + /* + * Based on operation type validate a different set of input data. + * + * For key creation, validate the Encrypted Key Type, + * the Key Mode and Key Value + */ + if (create_key_op) { + /* + * Validate arguments received from user. + * These must be at least 1 since + * they have null terminator. + */ + if (key_crt->key_enc_len < 1 || key_crt->key_mode_len < 1 || + key_crt->key_value_len < 1) { + msg = "Invalid arguments.\n"; + send_err_msg(msg, u64_to_user_ptr(key_crt->blob), + key_crt->blob_len); + return -EFAULT; + } + /* + * Allocate memory for temporary buffer used to + * get the user arguments from user-space + */ + tmp_size = max_t(size_t, key_crt->key_enc_len, + max_t(size_t, key_crt->key_mode_len, + key_crt->key_value_len)) + 1; + tmp = kmalloc(tmp_size, GFP_KERNEL); + if (!tmp) { + msg = "Unable to allocate memory for temporary buffer.\n"; + send_err_msg(msg, u64_to_user_ptr(key_crt->blob), + key_crt->blob_len); + return -ENOMEM; + } + /* Add null terminator */ + tmp[tmp_size - 1] = null_ch; + /* + * Validate and set, in type, the Encrypted Key Type + * given from user-space. + * This must be ecb or ccm. + */ + if (copy_from_user(tmp, u64_to_user_ptr(key_crt->key_enc), + key_crt->key_enc_len)) { + msg = "Unable to copy from user the Encrypted Key Type.\n"; + send_err_msg(msg, u64_to_user_ptr(key_crt->blob), + key_crt->blob_len); + ret = -EFAULT; + goto free_resource; + } + if (!strcmp(tmp, "ccm")) { + type |= BIT(TAG_OBJ_EKT_OFFSET); + } else if (strcmp(tmp, "ecb")) { + msg = "Invalid argument for Encrypted Key Type, expected ecb or ccm.\n"; + send_err_msg(msg, u64_to_user_ptr(key_crt->blob), + key_crt->blob_len); + ret = -EINVAL; + goto free_resource; + } + /* + * Validate the Key Mode given from user-space. + * This must be -t (text), for a black key generated + * from a plaintext, or -s (size) for a black key + * generated from random. + */ + if (copy_from_user(tmp, u64_to_user_ptr(key_crt->key_mode), + key_crt->key_mode_len)) { + msg = "Unable to copy from user the Key Mode: random (-s) or plaintext (-t).\n"; + send_err_msg(msg, u64_to_user_ptr(key_crt->blob), + key_crt->blob_len); + ret = -EFAULT; + goto free_resource; + } + if (!strcmp(tmp, "-s")) { + random = true; /* black key generated from random */ + } else if (strcmp(tmp, "-t")) { + msg = "Invalid argument for Key Mode, expected -s or -t.\n"; + send_err_msg(msg, u64_to_user_ptr(key_crt->blob), + key_crt->blob_len); + ret = -EINVAL; + goto free_resource; + } + /* + * Validate and set, into keyblob_info structure, + * the plaintext or key size, based on Key Mode. + */ + if (copy_from_user(tmp, u64_to_user_ptr(key_crt->key_value), + key_crt->key_value_len)) { + msg = "Unable to copy from user the Key Value: size or plaintext.\n"; + send_err_msg(msg, u64_to_user_ptr(key_crt->blob), + key_crt->blob_len); + ret = -EFAULT; + goto free_resource; + } + /* Black key generated from random, get its size */ + if (random) { + info->key = NULL; + ret = kstrtou32(tmp, 10, &tmp_len); + if (ret != 0) { + msg = "Invalid key size.\n"; + send_err_msg(msg, u64_to_user_ptr(key_crt->blob), + key_crt->blob_len); + goto free_resource; + } + ret = validate_key_size(tmp_len, + u64_to_user_ptr(key_crt->blob), + key_crt->blob_len); + if (ret) + goto free_resource; + + info->key_len = tmp_len; + } else { + /* + * Black key generated from plaintext, + * get the plaintext (input key) and its size + */ + ret = validate_key_size(strlen(tmp), + u64_to_user_ptr(key_crt->blob), + key_crt->blob_len); + if (ret) + goto free_resource; + + info->key = tmp; + info->key_len = strlen(tmp); + } + info->type = type; + } else { + /* For key import, get the blob from user-space */ + if (copy_from_user(info->blob, u64_to_user_ptr(key_crt->blob), + info->blob_len)) { + msg = "Unable to copy from user the blob.\n"; + send_err_msg(msg, u64_to_user_ptr(key_crt->black_key), + key_crt->black_key_len); + return -EFAULT; + } + } + + goto exit; + +free_resource: + kfree(tmp); + +exit: + return ret; +} + +/** + * keygen_create_keyblob - Generate key and blob + * + * @info : keyblob_info structure, will be updated with + * the black key and blob data from CAAM + * + * Return : '0' on success, error code otherwise + */ +static int keygen_create_keyblob(struct keyblob_info *info) +{ + int ret = 0; + struct device *jrdev; + + /* Allocate caam job ring for operation to be performed from CAAM */ + jrdev = caam_jr_alloc(); + if (IS_ERR(jrdev)) { + pr_err("Job Ring Device allocation failed\n"); + return PTR_ERR(jrdev); + } + + /* Create a black key */ + ret = generate_black_key(jrdev, info); + if (ret) { + dev_err(jrdev, "Black key generation failed: (%d)\n", ret); + goto free_jr; + } + + /* Clear the input key, if exists */ + if (info->key) + memset(info->key, 0, info->key_len); + + /* Set key modifier, used as revision number, for blob */ + info->key_mod = caam_key_modifier; + info->key_mod_len = ARRAY_SIZE(caam_key_modifier); + + /* + * Encapsulate the key, into a black blob, in general memory + * (the only memory type supported, right now) + */ + ret = caam_blob_encap(jrdev, info); + if (ret) { + dev_err(jrdev, "Blob encapsulation of black key failed: %d\n", + ret); + goto free_jr; + } + + /* Tag the black key so it can be passed to CAAM Crypto API */ + ret = tag_black_obj(info, sizeof(info->black_key), false); + if (ret) { + dev_err(jrdev, "Black key tagging failed: %d\n", ret); + goto free_jr; + } + + /* Tag the black blob so it can be passed to CAAM Crypto API */ + ret = tag_black_obj(info, sizeof(info->blob), true); + if (ret) { + dev_err(jrdev, "Black blob tagging failed: %d\n", ret); + goto free_jr; + } + +free_jr: + caam_jr_free(jrdev); + + return ret; +} + +/** + * keygen_import_key - Import a black key from a blob + * + * @info : keyblob_info structure, will be updated with + * the black key obtained after blob decapsulation by CAAM + * + * Return : '0' on success, error code otherwise + */ +static int keygen_import_key(struct keyblob_info *info) +{ + int ret = 0; + struct device *jrdev; + struct header_conf *header; + struct tagged_object *tag_obj; + + /* Allocate CAAM Job Ring for operation to be performed from CAAM */ + jrdev = caam_jr_alloc(); + if (IS_ERR(jrdev)) { + pr_err("Job Ring Device allocation failed\n"); + return PTR_ERR(jrdev); + } + + /* Set key modifier, used as revision number, for blob */ + info->key_mod = caam_key_modifier; + info->key_mod_len = ARRAY_SIZE(caam_key_modifier); + + print_hex_dump_debug("input blob @ " __stringify(__LINE__) " : ", + DUMP_PREFIX_ADDRESS, 16, 4, info->blob, + info->blob_len, 1); + + /* Check if one can retrieve the tag object header configuration */ + if (info->blob_len <= TAG_OVERHEAD_SIZE) { + dev_err(jrdev, "Invalid blob length\n"); + ret = -EINVAL; + goto free_jr; + } + + /* Retrieve the tag object */ + tag_obj = (struct tagged_object *)info->blob; + + /* + * Check tag object header configuration + * and retrieve the tag object header configuration + */ + if (is_valid_header_conf(&tag_obj->header)) { + header = &tag_obj->header; + } else { + dev_err(jrdev, + "Unable to get tag object header configuration for blob\n"); + ret = -EINVAL; + goto free_jr; + } + + info->key_len = header->red_key_len; + + /* Validate the red key size extracted from blob */ + if (info->key_len < MIN_KEY_SIZE || info->key_len > MAX_KEY_SIZE) { + dev_err(jrdev, + "Invalid red key length extracted from blob, expected values are between 16 and 64 bytes\n"); + ret = -EINVAL; + goto free_jr; + } + + info->type = header->type; + + /* Update blob length by removing the header size */ + info->blob_len -= TAG_OVERHEAD_SIZE; + + /* + * Check the received, from user, blob length + * with the one from tag header + */ + if (info->blob_len != header->obj_len) { + dev_err(jrdev, "Mismatch between received blob length and the one from tag header\n"); + ret = -EINVAL; + goto free_jr; + } + + /* + * Decapsulate the blob into a black key, + * in general memory (the only memory type supported, right now) + */ + ret = caam_blob_decap(jrdev, info); + if (ret) { + dev_err(jrdev, "Blob decapsulation failed: %d\n", ret); + goto free_jr; + } + + /* Tag the black key so it can be passed to CAAM Crypto API */ + ret = tag_black_obj(info, sizeof(info->black_key), false); + if (ret) + dev_err(jrdev, "Black key tagging failed: %d\n", ret); + +free_jr: + caam_jr_free(jrdev); + + return ret; +} + +/** + * send_output - Send the output data (tagged key and blob) + * from kernel to user-space. + * + * @key_crt : Structure used to transfer data + * from user-space to kernel + * @info : keyblob_info structure, which contains all + * the data obtained from CAAM that needs to + * be transferred to user-space + * @create_key_op : Used to determine if it's a create or import operation + * @err : Error code received from previous operations + * + * Return : '0' on success, error code otherwise + */ +static int send_output(struct caam_keygen_cmd *key_crt, unsigned long arg, + struct keyblob_info *info, bool create_key_op, int err) +{ + int ret = 0; + char *msg; + + /* Free resource used on validate_input */ + kfree(info->key); + + if (err) + return err; + + /* Check if there's enough space to copy black key to user */ + if (key_crt->black_key_len < info->black_key_len) { + msg = "Not enough space for black key.\n"; + send_err_msg(msg, u64_to_user_ptr(key_crt->blob), + key_crt->blob_len); + /* Send, to user, the necessary size for key */ + key_crt->black_key_len = info->black_key_len; + + ret = -EINVAL; + goto exit; + } + key_crt->black_key_len = info->black_key_len; + + /* For key import, copy to user only the black key */ + if (copy_to_user(u64_to_user_ptr(key_crt->black_key), + info->black_key, info->black_key_len)) + return -EFAULT; + + /* For key creation, copy to user, also, the blob */ + if (create_key_op) { + /* Check if there's enough space to copy blob user */ + if (key_crt->blob_len < info->blob_len) { + msg = "Not enough space for blob key.\n"; + send_err_msg(msg, u64_to_user_ptr(key_crt->blob), + key_crt->blob_len); + /* Send, to user, the necessary size for blob */ + key_crt->blob_len = info->blob_len; + + ret = -EINVAL; + goto exit; + } + + key_crt->blob_len = info->blob_len; + + if (copy_to_user(u64_to_user_ptr(key_crt->blob), info->blob, + info->blob_len)) + return -EFAULT; + } + +exit: + if (copy_to_user((void __user *)arg, key_crt, + sizeof(struct caam_keygen_cmd))) + return -EFAULT; + + return ret; +} + +static long caam_keygen_ioctl(struct file *file, unsigned int cmd, + unsigned long arg) +{ + int ret = 0; + struct keyblob_info info = {.key = NULL}; + struct caam_keygen_cmd key_crt; + /* Used to determine if it's a create or import operation */ + bool create_key_op = false; + + switch (cmd) { + case CAAM_KEYGEN_IOCTL_CREATE: + { + create_key_op = true; + + /* Validate user-space input */ + ret = validate_input(&key_crt, arg, &info, create_key_op); + if (ret) + break; + + /* Create tagged key and blob */ + ret = keygen_create_keyblob(&info); + + /* Send data from kernel to user-space */ + ret = send_output(&key_crt, arg, &info, create_key_op, ret); + + break; + } + case CAAM_KEYGEN_IOCTL_IMPORT: + { + /* Validate user-space input */ + ret = validate_input(&key_crt, arg, &info, create_key_op); + if (ret) + break; + + /* Import tagged key from blob */ + ret = keygen_import_key(&info); + + /* Send data from kernel to user-space */ + ret = send_output(&key_crt, arg, &info, create_key_op, ret); + + break; + } + default: + ret = -ENOTTY; + } + + return ret; +} + +static const struct file_operations fops = { + .owner = THIS_MODULE, + .unlocked_ioctl = caam_keygen_ioctl, + .compat_ioctl = compat_ptr_ioctl, +}; + +static struct miscdevice caam_keygen_dev = { + .minor = MISC_DYNAMIC_MINOR, + .name = DEVICE_NAME, + .fops = &fops +}; + +int caam_keygen_init(void) +{ + int ret; + + ret = misc_register(&caam_keygen_dev); + if (ret) { + pr_err("Failed to register device %s\n", + caam_keygen_dev.name); + return ret; + } + + pr_info("Device %s registered\n", caam_keygen_dev.name); + + return 0; +} + +void caam_keygen_exit(void) +{ + misc_deregister(&caam_keygen_dev); + + pr_info("caam_keygen unregistered\n"); +} |