diff options
Diffstat (limited to 'cryptodev.c')
-rw-r--r-- | cryptodev.c | 730 |
1 files changed, 0 insertions, 730 deletions
diff --git a/cryptodev.c b/cryptodev.c deleted file mode 100644 index 5d195f3..0000000 --- a/cryptodev.c +++ /dev/null @@ -1,730 +0,0 @@ -/* - * Driver for /dev/crypto device (aka CryptoDev) - * - * Copyright (c) 2004 Michal Ludvig <mludvig@logix.net.nz>, SuSE Labs - * Copyright (c) 2009,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/>. - */ - -/* - * Device /dev/crypto provides an interface for - * accessing kernel CryptoAPI algorithms (ciphers, - * hashes) from userspace programs. - * - * /dev/crypto interface was originally introduced in - * OpenBSD and this module attempts to keep the API, - * although a bit extended. - * - */ - -#include <linux/module.h> -#include <linux/moduleparam.h> -#include <linux/init.h> -#include <linux/sched.h> -#include <linux/fs.h> -#include <linux/file.h> -#include <linux/fdtable.h> -#include <linux/miscdevice.h> -#include <linux/crypto.h> -#include <linux/mm.h> -#include <linux/highmem.h> -#include <linux/random.h> -#include "cryptodev.h" -#include <asm/uaccess.h> -#include <asm/ioctl.h> -#include <linux/scatterlist.h> - -MODULE_AUTHOR("Michal Ludvig <mludvig@logix.net.nz>"); -MODULE_DESCRIPTION("CryptoDev driver"); -MODULE_LICENSE("GPL"); - -/* ====== Compile-time config ====== */ - -#define CRYPTODEV_STATS - -/* ====== Module parameters ====== */ - -static int verbosity = 0; -module_param(verbosity, int, 0644); -MODULE_PARM_DESC(verbosity, "0: normal, 1: verbose, 2: debug"); - -#ifdef CRYPTODEV_STATS -static int enable_stats = 0; -module_param(enable_stats, int, 0644); -MODULE_PARM_DESC(enable_stats, "collect statictics about cryptodev usage"); -#endif - -/* ====== Debug helpers ====== */ - -#define PFX "cryptodev: " -#define dprintk(level,severity,format,a...) \ - do { \ - if (level <= verbosity) \ - printk(severity PFX "%s[%u]: " format, \ - current->comm, current->pid, \ - ##a); \ - } while (0) - -/* ====== CryptoAPI ====== */ - -#define FILL_SG(sg,ptr,len) \ - do { \ - (sg)->page = virt_to_page(ptr); \ - (sg)->offset = offset_in_page(ptr); \ - (sg)->length = len; \ - (sg)->dma_address = 0; \ - } while (0) - -struct csession { - struct list_head entry; - struct semaphore sem; - struct crypto_blkcipher *tfm; - struct crypto_hash *hash_tfm; - uint32_t sid; -#ifdef CRYPTODEV_STATS -#if ! ((COP_ENCRYPT < 2) && (COP_DECRYPT < 2)) -#error Struct csession.stat uses COP_{ENCRYPT,DECRYPT} as indices. Do something! -#endif - unsigned long long stat[2]; - size_t stat_max_size, stat_count; -#endif -}; - -struct fcrypt { - struct list_head list; - struct semaphore sem; -}; - -/* Prepare session for future use. */ -static int -crypto_create_session(struct fcrypt *fcr, struct session_op *sop) -{ - struct csession *ses_new, *ses_ptr; - struct crypto_blkcipher *blk_tfm=NULL; - struct crypto_hash *hash_tfm=NULL; - int ret = 0; - const char *alg_name=NULL; - const char *hash_name=NULL; - struct blkcipher_alg* blkalg; - int hmac_mode = 1; - - /* Does the request make sense? */ - if (!sop->cipher && !sop->mac) { - dprintk(1,KERN_DEBUG,"Both 'cipher' and 'mac' unset.\n"); - return -EINVAL; - } - - switch (sop->cipher) { - case 0: - break; - case CRYPTO_DES_CBC: - alg_name = "cbc(des)"; - break; - case CRYPTO_3DES_CBC: - alg_name = "cbc(3des_ede)"; - break; - case CRYPTO_BLF_CBC: - alg_name = "cbc(blowfish)"; - break; - case CRYPTO_AES_CBC: - alg_name = "cbc(aes)"; - break; - case CRYPTO_CAMELLIA_CBC: - alg_name = "cbc(camelia)"; - break; - default: - dprintk(1,KERN_DEBUG,"%s: bad cipher: %d\n", __func__, sop->cipher); - return -EINVAL; - } - - switch (sop->mac) { - case 0: - break; - case CRYPTO_MD5_HMAC: - hash_name = "hmac(md5)"; - break; - case CRYPTO_RIPEMD160_HMAC: - hash_name = "hmac(rmd160)"; - break; - case CRYPTO_SHA1_HMAC: - hash_name = "hmac(sha1)"; - break; - case CRYPTO_SHA2_256_HMAC: - hash_name = "hmac(sha256)"; - break; - case CRYPTO_SHA2_384_HMAC: - hash_name = "hmac(sha384)"; - break; - case CRYPTO_SHA2_512_HMAC: - hash_name = "hmac(sha512)"; - break; - - /* non-hmac cases */ - case CRYPTO_MD5: - hash_name = "md5"; - hmac_mode = 0; - break; - case CRYPTO_RIPEMD160: - hash_name = "rmd160"; - hmac_mode = 0; - break; - case CRYPTO_SHA1: - hash_name = "sha1"; - hmac_mode = 0; - break; - case CRYPTO_SHA2_256: - hash_name = "sha256"; - hmac_mode = 0; - break; - case CRYPTO_SHA2_384: - hash_name = "sha384"; - hmac_mode = 0; - break; - case CRYPTO_SHA2_512: - hash_name = "sha512"; - hmac_mode = 0; - break; - - default: - dprintk(1,KERN_DEBUG,"%s: bad mac: %d\n", __func__, sop->mac); - return -EINVAL; - } - - /* Set-up crypto transform. */ - if (alg_name) { - uint8_t keyp[CRYPTO_CIPHER_MAX_KEY_LEN]; - - blk_tfm = crypto_alloc_blkcipher(alg_name, 0, CRYPTO_ALG_ASYNC); - if (IS_ERR(blk_tfm)) { - dprintk(1,KERN_DEBUG,"%s: Failed to load transform for %s\n", __func__, - alg_name); - return -EINVAL; - } - - blkalg = crypto_blkcipher_alg(blk_tfm); - if (blkalg != NULL) { - /* Was correct key length supplied? */ - if ((sop->keylen < blkalg->min_keysize) || - (sop->keylen > blkalg->max_keysize)) { - dprintk(0,KERN_DEBUG,"Wrong keylen '%zu' for algorithm '%s'. Use %u to %u.\n", - sop->keylen, alg_name, blkalg->min_keysize, - blkalg->max_keysize); - ret = -EINVAL; - goto error; - } - } - - if (sop->keylen > CRYPTO_CIPHER_MAX_KEY_LEN) { - dprintk(0,KERN_DEBUG,"Setting key failed for %s-%zu.\n", - alg_name, sop->keylen*8); - ret = -EINVAL; - goto error; - } - - /* Copy the key from user and set to TFM. */ - copy_from_user(keyp, sop->key, sop->keylen); - ret = crypto_blkcipher_setkey(blk_tfm, keyp, sop->keylen); - if (ret) { - dprintk(0,KERN_DEBUG,"Setting key failed for %s-%zu.\n", - alg_name, sop->keylen*8); - ret = -EINVAL; - goto error; - } - - } - - if (hash_name) { - hash_tfm = crypto_alloc_hash(hash_name, 0, CRYPTO_ALG_ASYNC); - if (IS_ERR(hash_tfm)) { - dprintk(1,KERN_DEBUG,"%s: Failed to load transform for %s\n", __func__, - hash_name); - return -EINVAL; - } - - /* Copy the key from user and set to TFM. */ - if (hmac_mode != 0) { - uint8_t hkeyp[CRYPTO_HMAC_MAX_KEY_LEN]; - - if (sop->mackeylen > CRYPTO_HMAC_MAX_KEY_LEN) { - dprintk(0,KERN_DEBUG,"Setting hmac key failed for %s-%zu.\n", - hash_name, sop->mackeylen*8); - ret = -EINVAL; - goto error; - } - - copy_from_user(hkeyp, sop->mackey, sop->mackeylen); - ret = crypto_hash_setkey(hash_tfm, hkeyp, sop->mackeylen); - if (ret) { - dprintk(0,KERN_DEBUG,"Setting hmac key failed for %s-%zu.\n", - hash_name, sop->mackeylen*8); - ret = -EINVAL; - goto error; - } - } - - } - - /* Create a session and put it to the list. */ - ses_new = kmalloc(sizeof(*ses_new), GFP_KERNEL); - if(!ses_new) { - ret = -ENOMEM; - goto error; - } - - memset(ses_new, 0, sizeof(*ses_new)); - get_random_bytes(&ses_new->sid, sizeof(ses_new->sid)); - ses_new->tfm = blk_tfm; - ses_new->hash_tfm = hash_tfm; - init_MUTEX(&ses_new->sem); - - down(&fcr->sem); -restart: - list_for_each_entry(ses_ptr, &fcr->list, entry) { - /* Check for duplicate SID */ - if (unlikely(ses_new->sid == ses_ptr->sid)) { - get_random_bytes(&ses_new->sid, sizeof(ses_new->sid)); - /* Unless we have a broken RNG this - shouldn't loop forever... ;-) */ - goto restart; - } - } - - list_add(&ses_new->entry, &fcr->list); - up(&fcr->sem); - - /* Fill in some values for the user. */ - sop->ses = ses_new->sid; - - return 0; - -error: - if (blk_tfm) - crypto_free_blkcipher(blk_tfm); - if (hash_tfm) - crypto_free_hash(hash_tfm); - - return ret; - -} - -/* Everything that needs to be done when remowing a session. */ -static inline void -crypto_destroy_session(struct csession *ses_ptr) -{ - if(down_trylock(&ses_ptr->sem)) { - dprintk(2, KERN_DEBUG, "Waiting for semaphore of sid=0x%08X\n", - ses_ptr->sid); - down(&ses_ptr->sem); - } - dprintk(2, KERN_DEBUG, "Removed session 0x%08X\n", ses_ptr->sid); -#if defined(CRYPTODEV_STATS) - if(enable_stats) - dprintk(2, KERN_DEBUG, - "Usage in Bytes: enc=%llu, dec=%llu, max=%zu, avg=%lu, cnt=%zu\n", - ses_ptr->stat[COP_ENCRYPT], ses_ptr->stat[COP_DECRYPT], - ses_ptr->stat_max_size, ses_ptr->stat_count > 0 - ? ((unsigned long)(ses_ptr->stat[COP_ENCRYPT]+ - ses_ptr->stat[COP_DECRYPT]) / - ses_ptr->stat_count) : 0, - ses_ptr->stat_count); -#endif - if (ses_ptr->tfm) { - crypto_free_blkcipher(ses_ptr->tfm); - ses_ptr->tfm = NULL; - } - if (ses_ptr->hash_tfm) { - crypto_free_hash(ses_ptr->hash_tfm); - ses_ptr->hash_tfm = NULL; - } - up(&ses_ptr->sem); - kfree(ses_ptr); -} - -/* Look up a session by ID and remove. */ -static int -crypto_finish_session(struct fcrypt *fcr, uint32_t sid) -{ - struct csession *tmp, *ses_ptr; - struct list_head *head; - int ret = 0; - - down(&fcr->sem); - head = &fcr->list; - list_for_each_entry_safe(ses_ptr, tmp, head, entry) { - if(ses_ptr->sid == sid) { - list_del(&ses_ptr->entry); - crypto_destroy_session(ses_ptr); - break; - } - } - - if (!ses_ptr) { - dprintk(1, KERN_ERR, "Session with sid=0x%08X not found!\n", sid); - ret = -ENOENT; - } - up(&fcr->sem); - - return ret; -} - -/* Remove all sessions when closing the file */ -static int -crypto_finish_all_sessions(struct fcrypt *fcr) -{ - struct csession *tmp, *ses_ptr; - struct list_head *head; - - down(&fcr->sem); - - head = &fcr->list; - list_for_each_entry_safe(ses_ptr, tmp, head, entry) { - list_del(&ses_ptr->entry); - crypto_destroy_session(ses_ptr); - } - up(&fcr->sem); - - return 0; -} - -/* Look up session by session ID. The returned session is locked. */ -static struct csession * -crypto_get_session_by_sid(struct fcrypt *fcr, uint32_t sid) -{ - struct csession *ses_ptr; - - down(&fcr->sem); - list_for_each_entry(ses_ptr, &fcr->list, entry) { - if(ses_ptr->sid == sid) { - down(&ses_ptr->sem); - break; - } - } - up(&fcr->sem); - - return ses_ptr; -} - -/* This is the main crypto function - feed it with plaintext - and get a ciphertext (or vice versa :-) */ -static int -crypto_run(struct fcrypt *fcr, struct crypt_op *cop) -{ - char *data, ivp[EALG_MAX_BLOCK_LEN]; - char __user *src, __user *dst; - struct scatterlist sg; - struct csession *ses_ptr; - unsigned int ivsize=0; - size_t nbytes, bufsize; - int ret = 0; - uint8_t hash_output[HASH_MAX_LEN]; - struct blkcipher_desc bdesc = { - .flags = CRYPTO_TFM_REQ_MAY_SLEEP, - }; - struct hash_desc hdesc = { - .flags = CRYPTO_TFM_REQ_MAY_SLEEP, - }; - - - if (unlikely(cop->op != COP_ENCRYPT && cop->op != COP_DECRYPT)) { - dprintk(1, KERN_DEBUG, "invalid operation op=%u\n", cop->op); - return -EINVAL; - } - - ses_ptr = crypto_get_session_by_sid(fcr, cop->ses); - if (!ses_ptr) { - dprintk(1, KERN_ERR, "invalid session ID=0x%08X\n", cop->ses); - return -EINVAL; - } - - nbytes = cop->len; - data = (char*)__get_free_page(GFP_KERNEL); - - if (unlikely(!data)) { - ret = -ENOMEM; - goto out_unlock; - } - bufsize = PAGE_SIZE < nbytes ? PAGE_SIZE : nbytes; - - nbytes = cop->len; - bdesc.tfm = ses_ptr->tfm; - hdesc.tfm = ses_ptr->hash_tfm; - if (hdesc.tfm) { - ret = crypto_hash_init(&hdesc); - if (unlikely(ret)) { - dprintk(1, KERN_ERR, - "error in crypto_hash_init()\n"); - goto out_unlock; - } - } - - if (bdesc.tfm) { - int blocksize = crypto_blkcipher_blocksize(bdesc.tfm); - - if (unlikely(nbytes % blocksize)) { - dprintk(1, KERN_ERR, - "data size (%zu) isn't a multiple of block size (%u)\n", - nbytes, blocksize); - ret = -EINVAL; - goto out_unlock; - } - - ivsize = crypto_blkcipher_ivsize(bdesc.tfm); - - if (cop->iv) { - copy_from_user(ivp, cop->iv, ivsize); - crypto_blkcipher_set_iv(bdesc.tfm, ivp, ivsize); - } - } - - src = cop->src; - dst = cop->dst; - - - while(nbytes > 0) { - size_t current_len = nbytes > bufsize ? bufsize : nbytes; - - copy_from_user(data, src, current_len); - - sg_set_buf(&sg, data, current_len); - - /* Always hash before encryption and after decryption. Maybe - * we should introduce a flag to switch... TBD later on. - */ - if (cop->op == COP_ENCRYPT) { - if (hdesc.tfm) { - ret = crypto_hash_update(&hdesc, &sg, current_len); - if (unlikely(ret)) { - dprintk(0, KERN_ERR, "CryptoAPI failure: %d\n",ret); - goto out; - } - } - if (bdesc.tfm) { - ret = crypto_blkcipher_encrypt(&bdesc, &sg, &sg, current_len); - - if (unlikely(ret)) { - dprintk(0, KERN_ERR, "CryptoAPI failure: %d\n",ret); - goto out; - } - copy_to_user(dst, data, current_len); - dst += current_len; - } - } else { - if (bdesc.tfm) { - ret = crypto_blkcipher_decrypt(&bdesc, &sg, &sg, current_len); - - if (unlikely(ret)) { - dprintk(0, KERN_ERR, "CryptoAPI failure: %d\n",ret); - goto out; - } - copy_to_user(dst, data, current_len); - dst += current_len; - - } - - if (hdesc.tfm) { - ret = crypto_hash_update(&hdesc, &sg, current_len); - if (unlikely(ret)) { - dprintk(0, KERN_ERR, "CryptoAPI failure: %d\n",ret); - goto out; - } - } - } - - nbytes -= current_len; - src += current_len; - } - - if (hdesc.tfm) { - ret = crypto_hash_final(&hdesc, hash_output); - if (unlikely(ret)) { - dprintk(0, KERN_ERR, "CryptoAPI failure: %d\n",ret); - goto out; - } - - copy_to_user(cop->mac, hash_output, crypto_hash_digestsize(ses_ptr->hash_tfm)); - } - -#if defined(CRYPTODEV_STATS) - if (enable_stats) { - /* this is safe - we check cop->op at the function entry */ - ses_ptr->stat[cop->op] += cop->len; - if (ses_ptr->stat_max_size < cop->len) - ses_ptr->stat_max_size = cop->len; - ses_ptr->stat_count++; - } -#endif - -out: - free_page((unsigned long)data); - -out_unlock: - up(&ses_ptr->sem); - - return ret; -} - -/* ====== /dev/crypto ====== */ - -static int -cryptodev_open(struct inode *inode, struct file *filp) -{ - struct fcrypt *fcr; - - fcr = kmalloc(sizeof(*fcr), GFP_KERNEL); - if(!fcr) - return -ENOMEM; - - memset(fcr, 0, sizeof(*fcr)); - init_MUTEX(&fcr->sem); - INIT_LIST_HEAD(&fcr->list); - filp->private_data = fcr; - - return 0; -} - -static int -cryptodev_release(struct inode *inode, struct file *filp) -{ - struct fcrypt *fcr = filp->private_data; - - if(fcr) { - crypto_finish_all_sessions(fcr); - kfree(fcr); - filp->private_data = NULL; - } - - return 0; -} - -static int -clonefd(struct file *filp) -{ - struct fdtable *fdt = files_fdtable(current->files); - int ret; - ret = get_unused_fd(); - if (ret >= 0) { - get_file(filp); - FD_SET(ret, fdt->open_fds); - fd_install(ret, filp); - } - - return ret; -} - -static int -cryptodev_ioctl(struct inode *inode, struct file *filp, - unsigned int cmd, unsigned long arg) -{ - int __user *p = (void __user *)arg; - struct session_op sop; - struct crypt_op cop; - struct fcrypt *fcr = filp->private_data; - uint32_t ses; - int ret, fd; - - if (!fcr) - BUG(); - - switch (cmd) { - case CIOCASYMFEAT: - put_user(0, p); - return 0; - case CRIOGET: - fd = clonefd(filp); - put_user(fd, p); - return 0; - - case CIOCGSESSION: - copy_from_user(&sop, (void*)arg, sizeof(sop)); - ret = crypto_create_session(fcr, &sop); - if (ret) - return ret; - copy_to_user((void*)arg, &sop, sizeof(sop)); - return 0; - - case CIOCFSESSION: - get_user(ses, (uint32_t*)arg); - ret = crypto_finish_session(fcr, ses); - return ret; - - case CIOCCRYPT: - copy_from_user(&cop, (void*)arg, sizeof(cop)); - ret = crypto_run(fcr, &cop); - copy_to_user((void*)arg, &cop, sizeof(cop)); - return ret; - - default: - return -EINVAL; - } -} - -struct file_operations cryptodev_fops = { - .owner = THIS_MODULE, - .open = cryptodev_open, - .release = cryptodev_release, - .ioctl = cryptodev_ioctl, -}; - -struct miscdevice cryptodev = { - .minor = CRYPTODEV_MINOR, - .name = "crypto", - .fops = &cryptodev_fops, -}; - -static int -cryptodev_register(void) -{ - int rc; - - rc = misc_register (&cryptodev); - if (rc) { - printk(KERN_ERR PFX "registeration of /dev/crypto failed\n"); - return rc; - } - - return 0; -} - -static void -cryptodev_deregister(void) -{ - misc_deregister(&cryptodev); -} - -/* ====== Module init/exit ====== */ - -int __init init_cryptodev(void) -{ - int rc; - - rc = cryptodev_register(); - if (rc) - return rc; - - printk(KERN_INFO PFX "driver loaded.\n"); - - return 0; -} - -void __exit exit_cryptodev(void) -{ - cryptodev_deregister(); - printk(KERN_INFO PFX "driver unloaded.\n"); -} - -module_init(init_cryptodev); -module_exit(exit_cryptodev); |