diff options
Diffstat (limited to 'crypto/userspace/ncr-key.c')
-rw-r--r-- | crypto/userspace/ncr-key.c | 618 |
1 files changed, 618 insertions, 0 deletions
diff --git a/crypto/userspace/ncr-key.c b/crypto/userspace/ncr-key.c new file mode 100644 index 00000000000..18cb38718a3 --- /dev/null +++ b/crypto/userspace/ncr-key.c @@ -0,0 +1,618 @@ +/* + * New driver for /dev/crypto device (aka CryptoDev) + + * Copyright (c) 2010 Nikos Mavrogiannopoulos <nmav@gnutls.org> + * + * This file is part of linux cryptodev. + * + * cryptodev is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * cryptodev is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include <linux/mm.h> +#include <linux/random.h> +#include "cryptodev.h" +#include <asm/uaccess.h> +#include <asm/ioctl.h> +#include <linux/scatterlist.h> +#include "ncr.h" +#include "ncr_int.h" + +static void ncr_key_clear(struct key_item_st* item); + +/* must be called with data semaphore down */ +static void _ncr_key_unlink_item(struct key_item_st *item) +{ + list_del(&item->list); + _ncr_key_item_put( item); /* decrement ref count */ +} + +void ncr_key_list_deinit(struct list_sem_st* lst) +{ + if(lst) { + struct key_item_st * item, *tmp; + + down(&lst->sem); + + list_for_each_entry_safe(item, tmp, &lst->list, list) { + _ncr_key_unlink_item(item); + } + up(&lst->sem); + } +} + +/* must be called with data semaphore down + */ +static ncr_key_t _ncr_key_get_new_desc( struct list_sem_st* lst) +{ +struct key_item_st* item; +int mx = 1; + + list_for_each_entry(item, &lst->list, list) { + mx = max(mx, item->desc); + } + mx++; + + return mx; +} + +/* returns the data item corresponding to desc */ +int ncr_key_item_get_read(struct key_item_st**st, struct list_sem_st* lst, + ncr_key_t desc) +{ +struct key_item_st* item; +int ret; + + *st = NULL; + + down(&lst->sem); + list_for_each_entry(item, &lst->list, list) { + if (item->desc == desc) { + atomic_inc(&item->refcnt); + + if (atomic_read(&item->writer) != 0) { + /* writer in place busy */ + atomic_dec(&item->refcnt); + ret = -EBUSY; + goto exit; + } + + *st = item; + ret = 0; + goto exit; + } + } + + err(); + ret = -EINVAL; +exit: + up(&lst->sem); + return ret; +} + +/* as above but will never return anything that + * is in use. + */ +int ncr_key_item_get_write( struct key_item_st** st, + struct list_sem_st* lst, ncr_key_t desc) +{ +struct key_item_st* item; +int ret; + + *st = NULL; + + down(&lst->sem); + list_for_each_entry(item, &lst->list, list) { + if (item->desc == desc) { + /* do not return items that are in use already */ + + if (atomic_add_unless(&item->writer, 1, 1)==0) { + /* another writer so busy */ + ret = -EBUSY; + goto exit; + } + + if (atomic_add_unless(&item->refcnt, 1, 2)==0) { + /* some reader is active so busy */ + atomic_dec(&item->writer); + ret = -EBUSY; + goto exit; + } + + *st = item; + ret = 0; + goto exit; + } + } + + err(); + ret = -EINVAL; + +exit: + up(&lst->sem); + return ret; +} + +void _ncr_key_item_put( struct key_item_st* item) +{ + if (atomic_read(&item->writer) > 0) + atomic_dec(&item->writer); + if (atomic_dec_and_test(&item->refcnt)) { + ncr_limits_remove(item->uid, item->pid, LIMIT_TYPE_KEY); + ncr_key_clear(item); + kfree(item); + } +} + +int ncr_key_init(struct list_sem_st* lst, void __user* arg) +{ + ncr_key_t desc; + struct key_item_st* key; + int ret; + + ret = ncr_limits_add_and_check(current_euid(), task_pid_nr(current), LIMIT_TYPE_KEY); + if (ret < 0) { + err(); + return ret; + } + + key = kmalloc(sizeof(*key), GFP_KERNEL); + if (key == NULL) { + err(); + ret = -ENOMEM; + goto err_limits; + } + + memset(key, 0, sizeof(*key)); + + atomic_set(&key->refcnt, 1); + atomic_set(&key->writer, 0); + + down(&lst->sem); + + key->desc = _ncr_key_get_new_desc(lst); + key->uid = current_euid(); + key->pid = task_pid_nr(current); + + list_add(&key->list, &lst->list); + + up(&lst->sem); + + desc = key->desc; + ret = copy_to_user(arg, &desc, sizeof(desc)); + if (unlikely(ret)) { + down(&lst->sem); + _ncr_key_unlink_item(key); + up(&lst->sem); + return -EFAULT; + } + return ret; + +err_limits: + ncr_limits_remove(current_euid(), task_pid_nr(current), LIMIT_TYPE_KEY); + return ret; +} + + +int ncr_key_deinit(struct list_sem_st* lst, void __user* arg) +{ + ncr_key_t desc; + struct key_item_st * item, *tmp; + + if (unlikely(copy_from_user(&desc, arg, sizeof(desc)))) { + err(); + return -EFAULT; + } + + down(&lst->sem); + + list_for_each_entry_safe(item, tmp, &lst->list, list) { + if(item->desc == desc) { + _ncr_key_unlink_item(item); + break; + } + } + + up(&lst->sem); + + return 0; +} + +/* "exports" a key to a data item. If the key is not exportable + * to userspace then the data item will also not be. + */ +int ncr_key_export(struct list_sem_st* data_lst, + struct list_sem_st* key_lst, void __user* arg) +{ +struct ncr_key_data_st data; +struct key_item_st* item = NULL; +struct data_item_st* ditem = NULL; +uint32_t size; +uint32_t data_flags; +int ret; +uint8_t* tmp = NULL; +size_t max_data_size; + + if (unlikely(copy_from_user(&data, arg, sizeof(data)))) { + err(); + return -EFAULT; + } + + ret = ncr_key_item_get_read( &item, key_lst, data.key); + if (ret < 0) { + err(); + return ret; + } + + ditem = ncr_data_item_get( data_lst, data.data); + if (ditem == NULL) { + err(); + ret = -EINVAL; + goto fail; + } + + data_flags = key_flags_to_data(item->flags); + + ret = ncr_data_item_size(ditem, 1); + if (ret < 0) { + err(); + goto fail; + } + max_data_size = ret; + + switch (item->type) { + case NCR_KEY_TYPE_SECRET: + if (item->key.secret.size > max_data_size) { + err(); + ret = -EINVAL; + goto fail; + } + + /* found */ + if (item->key.secret.size > 0) { + ret = ncr_data_item_setd( ditem, + item->key.secret.data, item->key.secret.size, + data_flags); + if (ret < 0) { + err(); + goto fail; + } + } + break; + case NCR_KEY_TYPE_PUBLIC: + case NCR_KEY_TYPE_PRIVATE: + size = max_data_size; + + tmp = kmalloc(size, GFP_KERNEL); + if (tmp == NULL) { + err(); + ret = -ENOMEM; + goto fail; + } + + ret = ncr_pk_pack(item, tmp, &size); + if (ret < 0) { + err(); + goto fail; + } + + ret = ncr_data_item_setd( ditem, tmp, size, data_flags); + if (ret < 0) { + err(); + goto fail; + } + + kfree(tmp); + + break; + default: + err(); + ret = -EINVAL; + goto fail; + } + + _ncr_key_item_put( item); + _ncr_data_item_put( ditem); + + return 0; + +fail: + kfree(tmp); + if (item) + _ncr_key_item_put(item); + if (ditem) + _ncr_data_item_put(ditem); + return ret; + +} + +/* "imports" a key from a data item. If the key is not exportable + * to userspace then the key item will also not be. + */ +int ncr_key_import(struct list_sem_st* data_lst, + struct list_sem_st* key_lst, void __user* arg) +{ +struct ncr_key_data_st data; +struct key_item_st* item = NULL; +struct data_item_st* ditem = NULL; +uint8_t *tmp = NULL; +int ret; +size_t data_size; + + if (unlikely(copy_from_user(&data, arg, sizeof(data)))) { + err(); + return -EFAULT; + } + + ret = ncr_key_item_get_write( &item, key_lst, data.key); + if (ret < 0) { + err(); + return ret; + } + + ditem = ncr_data_item_get( data_lst, data.data); + if (ditem == NULL) { + err(); + ret = -EINVAL; + goto fail; + } + + item->type = data.type; + item->algorithm = data.algorithm; + item->flags = data.flags; + /* if data cannot be exported then the flags above + * should be overriden */ + if (!(ditem->flags & NCR_DATA_FLAG_EXPORTABLE)) { + item->flags &= ~NCR_KEY_FLAG_EXPORTABLE; + } + + if (data.key_id_size > MAX_KEY_ID_SIZE) { + err(); + ret = -EINVAL; + goto fail; + } + + item->key_id_size = data.key_id_size; + if (data.key_id_size > 0) + memcpy(item->key_id, data.key_id, data.key_id_size); + + ret = ncr_data_item_size(ditem, 0); + if (ret < 0) { + err(); + goto fail; + } + data_size = ret; + + switch(item->type) { + case NCR_KEY_TYPE_SECRET: + if (data_size > NCR_CIPHER_MAX_KEY_LEN) { + err(); + ret = -EINVAL; + goto fail; + } + + ret = ncr_data_item_getd(ditem, item->key.secret.data, data_size, item->flags); + if (ret < 0) { + err(); + goto fail; + } + item->key.secret.size = data_size; + break; + case NCR_KEY_TYPE_PRIVATE: + case NCR_KEY_TYPE_PUBLIC: + tmp = kmalloc(data_size, GFP_KERNEL); + if (tmp == NULL) { + err(); + return -ENOMEM; + } + + ret = ncr_data_item_getd(ditem, tmp, data_size, item->flags); + if (ret < 0) { + err(); + goto fail; + } + + ret = ncr_pk_unpack( item, tmp, data_size); + if (ret < 0) { + err(); + goto fail; + } + kfree(tmp); + break; + + default: + err(); + ret = -EINVAL; + goto fail; + } + + _ncr_key_item_put( item); + _ncr_data_item_put( ditem); + + return 0; + +fail: + kfree(tmp); + if (item) + _ncr_key_item_put(item); + if (ditem) + _ncr_data_item_put(ditem); + return ret; +} + +static void ncr_key_clear(struct key_item_st* item) +{ + /* clears any previously allocated parameters */ + if (item->type == NCR_KEY_TYPE_PRIVATE || + item->type == NCR_KEY_TYPE_PUBLIC) { + + ncr_pk_clear(item); + } + memset(&item->key, 0, sizeof(item->key)); + + return; +} + +/* Generate a secret key + */ +int ncr_key_generate(struct list_sem_st* lst, void __user* arg) +{ +struct ncr_key_generate_st gen; +struct key_item_st* item = NULL; +int ret; +size_t size; + + if (unlikely(copy_from_user(&gen, arg, sizeof(gen)))) { + err(); + return -EFAULT; + } + + ret = ncr_key_item_get_write( &item, lst, gen.desc); + if (ret < 0) { + err(); + return ret; + } + + ncr_key_clear(item); + + /* we generate only secret keys */ + item->flags = gen.params.keyflags; + item->type = ncr_algorithm_to_key_type(gen.params.algorithm); + if (item->type == NCR_KEY_TYPE_SECRET) { + item->algorithm = /* arbitrary */ NCR_ALG_AES_CBC; + + size = gen.params.params.secret.bits/8; + if ((gen.params.params.secret.bits % 8 != 0) || + (size > NCR_CIPHER_MAX_KEY_LEN)) { + err(); + ret = -EINVAL; + goto fail; + } + + get_random_bytes(item->key.secret.data, size); + item->key.secret.size = size; + + /* generate random key id */ + item->key_id_size = 5; + get_random_bytes(item->key_id, item->key_id_size); + } else { + err(); + ret = -EINVAL; + goto fail; + } + + ret = 0; + +fail: + if (item) { + if (ret < 0) item->type = NCR_KEY_TYPE_INVALID; + _ncr_key_item_put(item); + } + return ret; +} + +int ncr_key_info(struct list_sem_st* lst, void __user* arg) +{ +struct ncr_key_info_st info; +struct key_item_st* item = NULL; +int ret; + + if (unlikely(copy_from_user(&info, arg, sizeof(info)))) { + err(); + return -EFAULT; + } + + ret = ncr_key_item_get_read(&item, lst, info.key); + if (ret < 0) { + err(); + return ret; + } + + info.flags = item->flags; + info.type = item->type; + info.algorithm = item->algorithm; + + _ncr_key_item_put( item); + + return 0; +} + +int ncr_key_generate_pair(struct list_sem_st* lst, void __user* arg) +{ +struct ncr_key_generate_st gen; +struct key_item_st* private = NULL; +struct key_item_st* public = NULL; +int ret; + + if (unlikely(copy_from_user(&gen, arg, sizeof(gen)))) { + err(); + return -EFAULT; + } + + ret = ncr_key_item_get_write( &private, lst, gen.desc); + if (ret < 0) { + err(); + goto fail; + } + + ret = ncr_key_item_get_write( &public, lst, gen.desc2); + if (ret < 0) { + err(); + goto fail; + } + + ncr_key_clear(public); + ncr_key_clear(private); + + /* we generate only secret keys */ + private->flags = public->flags = gen.params.keyflags; + public->type = ncr_algorithm_to_key_type(gen.params.algorithm); + private->type = NCR_KEY_TYPE_PRIVATE; + private->algorithm = public->algorithm = gen.params.algorithm; + public->flags |= (NCR_KEY_FLAG_EXPORTABLE|NCR_KEY_FLAG_WRAPPABLE); + + if (public->type == NCR_KEY_TYPE_PUBLIC) { + ret = ncr_pk_generate(gen.params.algorithm, &gen.params, private, public); + if (ret < 0) { + err(); + goto fail; + } + } else { + err(); + ret = -EINVAL; + goto fail; + } + + ret = 0; +fail: + if (public) { + if (ret < 0) public->type = NCR_KEY_TYPE_INVALID; + _ncr_key_item_put(public); + } + if (private) { + if (ret < 0) private->type = NCR_KEY_TYPE_INVALID; + _ncr_key_item_put(private); + } + return ret; +} + +int ncr_key_derive(struct list_sem_st* lst, void __user* arg) +{ + return -EINVAL; +} + +int ncr_key_get_public(struct list_sem_st* lst, void __user* arg) +{ + return -EINVAL; +} + |