diff options
Diffstat (limited to 'crypto/userspace/ncr-key.c')
-rw-r--r-- | crypto/userspace/ncr-key.c | 832 |
1 files changed, 832 insertions, 0 deletions
diff --git a/crypto/userspace/ncr-key.c b/crypto/userspace/ncr-key.c new file mode 100644 index 00000000000..e6f7f025848 --- /dev/null +++ b/crypto/userspace/ncr-key.c @@ -0,0 +1,832 @@ +/* + * New driver for /dev/crypto device (aka CryptoDev) + * + * Copyright (c) 2010 Katholieke Universiteit Leuven + * + * Author: Nikos Mavrogiannopoulos <nmav@gnutls.org> + * + * This file is part of linux cryptodev. + * + * This program 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 2 + * of the License, or (at your option) any later version. + * + * This program 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, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include <linux/audit.h> +#include <linux/ioctl.h> +#include <linux/mm.h> +#include <linux/ncr.h> +#include <linux/slab.h> +#include <linux/random.h> +#include <linux/uaccess.h> +#include <linux/scatterlist.h> +#include <net/netlink.h> +#include "ncr-int.h" +#include "utils.h" + +static int key_list_deinit_fn(int id, void *item, void *unused) +{ + (void)unused; + _ncr_key_item_put(item); + return 0; +} + +void ncr_key_list_deinit(struct ncr_lists *lst) +{ + /* The mutex is not necessary, but doesn't hurt and makes it easier to + verify locking correctness. */ + mutex_lock(&lst->key_idr_mutex); + idr_for_each(&lst->key_idr, key_list_deinit_fn, NULL); + idr_remove_all(&lst->key_idr); + idr_destroy(&lst->key_idr); + mutex_unlock(&lst->key_idr_mutex); +} + +/* returns the data item corresponding to desc */ +int ncr_key_item_get_read(struct key_item_st**st, struct ncr_lists *lst, + ncr_key_t desc) +{ +struct key_item_st* item; +int ret; + + *st = NULL; + + mutex_lock(&lst->key_idr_mutex); + item = idr_find(&lst->key_idr, desc); + if (item == NULL) { + err(); + ret = -EINVAL; + goto exit; + } + 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; + +exit: + mutex_unlock(&lst->key_idr_mutex); + 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 ncr_lists *lst, ncr_key_t desc) +{ +struct key_item_st* item; +int ret; + + *st = NULL; + + mutex_lock(&lst->key_idr_mutex); + item = idr_find(&lst->key_idr, desc); + if (item == NULL) { + err(); + ret = -EINVAL; + goto exit; + } + /* 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; + +exit: + mutex_unlock(&lst->key_idr_mutex); + 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); + } +} + +static void _ncr_key_remove(struct ncr_lists *lst, ncr_key_t desc) +{ + struct key_item_st * item; + + mutex_lock(&lst->key_idr_mutex); + item = idr_find(&lst->key_idr, desc); + if (item != NULL) + idr_remove(&lst->key_idr, desc); /* Steal the reference */ + mutex_unlock(&lst->key_idr_mutex); + + if (item != NULL) + _ncr_key_item_put(item); +} + +int ncr_key_init(struct ncr_lists *lst) +{ + 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); + key->uid = current_euid(); + key->pid = task_pid_nr(current); + key->context_id = lst->id; + + mutex_lock(&lst->key_idr_mutex); + /* idr_pre_get() should preallocate enough, and, due to key_idr_mutex, + nobody else can use the preallocated data. Therefore the loop + recommended in idr_get_new() documentation is not necessary. */ + if (idr_pre_get(&lst->key_idr, GFP_KERNEL) == 0 || + idr_get_new(&lst->key_idr, key, &key->desc) != 0) { + mutex_unlock(&lst->key_idr_mutex); + _ncr_key_item_put(key); + return -ENOMEM; + } + desc = key->desc; + mutex_unlock(&lst->key_idr_mutex); + + return desc; + +err_limits: + ncr_limits_remove(current_euid(), task_pid_nr(current), LIMIT_TYPE_KEY); + return ret; +} + +int ncr_key_deinit(struct ncr_lists *lst, ncr_key_t desc) +{ + _ncr_key_remove(lst, desc); + return 0; +} + +int ncr_key_export(struct ncr_lists *lst, const struct ncr_key_export *data, + struct nlattr *tb[]) +{ +struct key_item_st* item = NULL; +void* tmp = NULL; +uint32_t tmp_size; +int ret; + + if (data->buffer_size < 0) { + err(); + return -EINVAL; + } + + ret = ncr_key_item_get_read(&item, lst, data->key); + if (ret < 0) { + err(); + return ret; + } + + if (!(item->flags & NCR_KEY_FLAG_EXPORTABLE)) { + err(); + ret = -EPERM; + goto fail; + } + + switch (item->type) { + case NCR_KEY_TYPE_SECRET: + if (item->key.secret.size > data->buffer_size) { + err(); + ret = -ERANGE; + goto fail; + } + + /* found */ + if (item->key.secret.size > 0) { + ret = copy_to_user(data->buffer, item->key.secret.data, item->key.secret.size); + if (unlikely(ret)) { + err(); + ret = -EFAULT; + goto fail; + } + } + + ret = item->key.secret.size; + break; + case NCR_KEY_TYPE_PUBLIC: + case NCR_KEY_TYPE_PRIVATE: + tmp_size = data->buffer_size; + + tmp = kmalloc(tmp_size, GFP_KERNEL); + if (tmp == NULL) { + err(); + ret = -ENOMEM; + goto fail; + } + + ret = ncr_pk_pack(item, tmp, &tmp_size); + if (ret < 0) { + err(); + goto fail; + } + + ret = copy_to_user(data->buffer, tmp, tmp_size); + if (unlikely(ret)) { + err(); + ret = -EFAULT; + goto fail; + } + + ret = tmp_size; + break; + default: + err(); + ret = -EINVAL; + goto fail; + } + +fail: + audit_log_crypto_op(AUDIT_CRYPTO_OP_KEY_EXPORT, lst->id, -1, NULL, + ncr_algorithm_name(item->algorithm), item->desc, + item->key_id, item->key_id_size, -1, NULL, 0); + + kfree(tmp); + _ncr_key_item_put(item); + return ret; + +} + +int ncr_key_update_flags(struct key_item_st* item, const struct nlattr *nla) +{ + uint32_t flags; + + if (nla == NULL) + return 0; + flags = nla_get_u32(nla); + if (!capable(CAP_SYS_ADMIN) + && (flags & (NCR_KEY_FLAG_WRAPPING | NCR_KEY_FLAG_UNWRAPPING)) != 0) + return -EPERM; + item->flags = flags; + return 0; +} + +int ncr_key_import(struct ncr_lists *lst, const struct ncr_key_import *data, + struct nlattr *tb[]) +{ +const struct nlattr *nla; +struct key_item_st* item = NULL; +int ret; +void* tmp = NULL; +size_t tmp_size; + + ret = ncr_key_item_get_write( &item, lst, data->key); + if (ret < 0) { + err(); + return ret; + } + + ncr_key_clear(item); + + tmp = kmalloc(data->data_size, GFP_KERNEL); + if (tmp == NULL) { + err(); + ret = -ENOMEM; + goto fail; + } + + if (unlikely(copy_from_user(tmp, data->data, data->data_size))) { + err(); + ret = -EFAULT; + goto fail; + } + tmp_size = data->data_size; + + nla = tb[NCR_ATTR_KEY_TYPE]; + if (tb == NULL) { + err(); + ret = -EINVAL; + goto fail; + } + item->type = nla_get_u32(nla); + + item->algorithm = _ncr_nla_to_properties(tb[NCR_ATTR_ALGORITHM]); + if (item->algorithm == NULL) { + err(); + ret = -EINVAL; + goto fail; + } + + ret = ncr_key_update_flags(item, tb[NCR_ATTR_KEY_FLAGS]); + if (ret < 0) { + err(); + goto fail; + } + + nla = tb[NCR_ATTR_KEY_ID]; + if (nla != NULL) { + if (nla_len(nla) > MAX_KEY_ID_SIZE) { + err(); + ret = -EOVERFLOW; + goto fail; + } + + item->key_id_size = nla_len(nla); + memcpy(item->key_id, nla_data(nla), item->key_id_size); + } + + switch(item->type) { + case NCR_KEY_TYPE_SECRET: + if (tmp_size > NCR_CIPHER_MAX_KEY_LEN) { + err(); + ret = -EINVAL; + goto fail; + } + + memcpy(item->key.secret.data, tmp, tmp_size); + item->key.secret.size = tmp_size; + break; + case NCR_KEY_TYPE_PRIVATE: + case NCR_KEY_TYPE_PUBLIC: + ret = ncr_pk_unpack( item, tmp, tmp_size); + if (ret < 0) { + err(); + goto fail; + } + break; + + default: + err(); + ret = -EINVAL; + goto fail; + } + + ret = 0; + +fail: + audit_log_crypto_op(AUDIT_CRYPTO_OP_KEY_IMPORT, lst->id, -1, NULL, + ncr_algorithm_name(item->algorithm), item->desc, + item->key_id, item->key_id_size, -1, NULL, 0); + + _ncr_key_item_put(item); + kfree(tmp); + + return ret; +} + +void ncr_key_clear(struct key_item_st* item) +{ + audit_log_crypto_op(AUDIT_CRYPTO_OP_KEY_ZEROIZE, item->context_id, -1, + NULL, ncr_algorithm_name(item->algorithm), + item->desc, item->key_id, item->key_id_size, -1, + NULL, 0); + if (item->type == NCR_KEY_TYPE_PRIVATE || + item->type == NCR_KEY_TYPE_PUBLIC) + ncr_pk_audit_values(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)); + memset(item->key_id, 0, sizeof(item->key_id)); + item->key_id_size = 0; + item->flags = 0; + + return; +} + +/* Generate a secret key + */ +int ncr_key_generate(struct ncr_lists *lst, const struct ncr_key_generate *gen, + struct nlattr *tb[]) +{ +const struct nlattr *nla; +struct key_item_st* item = NULL; +const struct algo_properties_st *algo = NULL; +int ret; +size_t size; + + ret = ncr_key_item_get_write(&item, lst, gen->key); + if (ret < 0) { + err(); + return ret; + } + + ncr_key_clear(item); + + /* we generate only secret keys */ + ret = ncr_key_update_flags(item, tb[NCR_ATTR_KEY_FLAGS]); + if (ret < 0) { + err(); + goto fail; + } + + algo = _ncr_nla_to_properties(tb[NCR_ATTR_ALGORITHM]); + if (algo == NULL) { + err(); + ret = -EINVAL; + goto fail; + } + item->type = algo->key_type; + if (item->type == NCR_KEY_TYPE_SECRET) { + u32 key_bits; + + item->algorithm = algo; + + nla = tb[NCR_ATTR_SECRET_KEY_BITS]; + if (nla == NULL) { + err(); + ret = -EINVAL; + goto fail; + } + key_bits = nla_get_u32(nla); + size = key_bits / 8; + if (key_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: + audit_log_crypto_op(AUDIT_CRYPTO_OP_KEY_GEN, lst->id, -1, NULL, + ncr_algorithm_name(algo), item->desc, item->key_id, + item->key_id_size, -1, NULL, 0); + if (item->type == NCR_KEY_TYPE_PUBLIC + || item->type == NCR_KEY_TYPE_PRIVATE) + ncr_pk_audit_values(item); + + if (ret < 0) item->type = NCR_KEY_TYPE_INVALID; + _ncr_key_item_put(item); + return ret; +} + +/* Those values are derived from "ECRYPT II Yearly Report on Algorithms and + * Keysizes (2009-2010)". It maps the strength of public key algorithms to + * symmetric ones. Should be kept up to date. + */ +static const struct { + unsigned int bits; /* sec level */ + unsigned int rsa_bits; + unsigned int dlog_bits; +} ecrypt_vals[] = { + {64, 816, 816}, + {80, 1248, 1248}, + {112, 2432, 2432}, + {128, 3248, 3248}, + {160, 5312, 5312}, + {192, 7936, 7936}, + {256, 15424, 15424}, + {0,0,0} +}; + +static unsigned int rsa_to_bits(unsigned int rsa_bits) +{ +int i = 1; + + if (rsa_bits <= ecrypt_vals[0].rsa_bits) + return ecrypt_vals[0].rsa_bits; + + do { + if (rsa_bits <= ecrypt_vals[i].rsa_bits && + rsa_bits > ecrypt_vals[i-1].rsa_bits) { + + return ecrypt_vals[i].bits; + } + } while(ecrypt_vals[++i].bits != 0); + + /* return the highest found so far */ + return ecrypt_vals[i-1].bits; +} + +static unsigned int dlog_to_bits(unsigned int dlog_bits) +{ +int i = 1; + + if (dlog_bits <= ecrypt_vals[0].dlog_bits) + return ecrypt_vals[0].dlog_bits; + + do { + if (dlog_bits <= ecrypt_vals[i].dlog_bits && + dlog_bits > ecrypt_vals[i-1].dlog_bits) { + + return ecrypt_vals[i].bits; + } + } while(ecrypt_vals[++i].bits != 0); + + /* return the highest found so far */ + return ecrypt_vals[i-1].bits; +} + +/* returns the security level of the key in bits. Private/Public keys + * are mapped to symmetric key bits using the ECRYPT II 2010 recommendation. + */ +int _ncr_key_get_sec_level(struct key_item_st* item) +{ +int bits; + + /* FIXME: should we move everything here into algorithm properties? + */ + if (item->type == NCR_KEY_TYPE_SECRET) { + if (item->algorithm->algo == NCR_ALG_3DES_CBC || item->algorithm->algo == NCR_ALG_3DES_ECB) + return 112; + + return item->key.secret.size*8; + } else if (item->type == NCR_KEY_TYPE_PRIVATE) { + switch(item->algorithm->algo) { + case NCR_ALG_RSA: + bits = ncr_pk_get_rsa_size(&item->key.pk.rsa); + if (bits < 0) { + err(); + return bits; + } + + return rsa_to_bits(bits); + case NCR_ALG_DSA: + bits = ncr_pk_get_dsa_size(&item->key.pk.dsa); + if (bits < 0) { + err(); + return bits; + } + + return dlog_to_bits(bits); + case NCR_ALG_DH: + bits = ncr_pk_get_dh_size(&item->key.pk.dh); + if (bits < 0) { + err(); + return bits; + } + + return dlog_to_bits(bits); + default: + return -EINVAL; + } + } else { + return -EINVAL; + } +} + +int ncr_key_get_info(struct ncr_lists *lst, struct ncr_out *out, + const struct ncr_key_get_info *info, struct nlattr *tb[]) +{ +const struct nlattr *nla; +const u16 *attr, *attr_end; +struct key_item_st* item = NULL; +int ret; + + ret = ncr_key_item_get_read(&item, lst, info->key); + if (ret < 0) { + err(); + return ret; + } + + if (item->type == NCR_KEY_TYPE_INVALID) { + err(); + ret = -EINVAL; + goto fail; + } + + nla = tb[NCR_ATTR_WANTED_ATTRS]; + if (nla == NULL || nla_len(nla) % sizeof(u16) != 0) { + err(); + ret = -EINVAL; + goto fail; + } + attr = nla_data(nla); + attr_end = attr + nla_len(nla) / sizeof(u16); + while (attr < attr_end) { + switch (*attr) { + case NCR_ATTR_KEY_FLAGS: + ret = ncr_out_put_u32(out, *attr, item->flags); + break; + case NCR_ATTR_KEY_TYPE: + ret = ncr_out_put_u32(out, *attr, item->type); + break; + case NCR_ATTR_ALGORITHM: + ret = ncr_out_put_string(out, *attr, + item->algorithm->kstr); + break; + default: + break; /* Silently ignore */ + } + if (ret != 0) { + err(); + goto fail; + } + attr++; + } + + ret = ncr_out_finish(out); + if (ret != 0) { + err(); + goto fail; + } + +fail: + audit_log_crypto_op(AUDIT_CRYPTO_OP_KEY_GET_INFO, lst->id, -1, NULL, + ncr_algorithm_name(item->algorithm), item->desc, + item->key_id, item->key_id_size, -1, NULL, 0); + + _ncr_key_item_put( item); + + return ret; +} + +int ncr_key_generate_pair(struct ncr_lists *lst, + const struct ncr_key_generate_pair *gen, + struct nlattr *tb[]) +{ +struct key_item_st* private = NULL; +struct key_item_st* public = NULL; +int ret; + + ret = ncr_key_item_get_write(&private, lst, gen->private_key); + if (ret < 0) { + err(); + goto fail; + } + + ret = ncr_key_item_get_write(&public, lst, gen->public_key); + if (ret < 0) { + err(); + goto fail; + } + + ncr_key_clear(public); + ncr_key_clear(private); + + /* we generate only secret keys */ + private->algorithm = public->algorithm + = _ncr_nla_to_properties(tb[NCR_ATTR_ALGORITHM]); + if (private->algorithm == NULL) { + err(); + ret = -EINVAL; + goto fail; + } + public->type = public->algorithm->key_type; + private->type = NCR_KEY_TYPE_PRIVATE; + ret = ncr_key_update_flags(private, tb[NCR_ATTR_KEY_FLAGS]); + if (ret < 0) { + err(); + goto fail; + } + ret = ncr_key_update_flags(public, tb[NCR_ATTR_KEY_FLAGS]); + if (ret < 0) { + err(); + goto fail; + } + + public->flags |= (NCR_KEY_FLAG_EXPORTABLE|NCR_KEY_FLAG_WRAPPABLE); + + if (public->type == NCR_KEY_TYPE_PUBLIC) { + ret = ncr_pk_generate(public->algorithm, tb, private, public); + if (ret < 0) { + err(); + goto fail; + } + } else { + err(); + ret = -EINVAL; + goto fail; + } + + ret = 0; +fail: + audit_log_crypto_op(AUDIT_CRYPTO_OP_KEY_GEN, lst->id, -1, NULL, + ncr_algorithm_name(private != NULL + ? private->algorithm : NULL), + private != NULL ? private->desc : -1, + private != NULL ? private->key_id : NULL, + private != NULL ? private->key_id_size : 0, + public != NULL ? public->desc : -1, + public != NULL ? public->key_id : NULL, + public != NULL ? public->key_id_size : 0); + if (public != NULL && ret >= 0) + ncr_pk_audit_values(public); + + 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 ncr_lists *lst, const struct ncr_key_derive *data, + struct nlattr *tb[]) +{ +int ret; +struct key_item_st* key = NULL; +struct key_item_st* newkey = NULL; + + ret = ncr_key_item_get_read(&key, lst, data->input_key); + if (ret < 0) { + err(); + return ret; + } + + /* wrapping keys cannot be used for anything except wrapping. + */ + if (key->flags & NCR_KEY_FLAG_WRAPPING || key->flags & NCR_KEY_FLAG_UNWRAPPING) { + err(); + ret = -EINVAL; + goto fail; + } + + ret = ncr_key_item_get_write(&newkey, lst, data->new_key); + if (ret < 0) { + err(); + goto fail; + } + + ncr_key_clear(newkey); + + ret = ncr_key_update_flags(newkey, tb[NCR_ATTR_KEY_FLAGS]); + if (ret < 0) { + err(); + goto fail; + } + + switch (key->type) { + case NCR_KEY_TYPE_PUBLIC: + case NCR_KEY_TYPE_PRIVATE: + ret = ncr_pk_derive(newkey, key, tb); + if (ret < 0) { + err(); + goto fail; + } + break; + default: + err(); + ret = -EINVAL; + goto fail; + } + +fail: + audit_log_crypto_op(AUDIT_CRYPTO_OP_KEY_DERIVE, lst->id, -1, NULL, + ncr_algorithm_name(key->algorithm), key->desc, + key->key_id, key->key_id_size, data->input_key, + newkey != NULL ? newkey->key_id : NULL, + newkey != NULL ? newkey->key_id_size : 0); + + _ncr_key_item_put(key); + if (newkey) + _ncr_key_item_put(newkey); + return ret; + +} + |