From 12883105566c38cf0b0ccd2a258311435283a97b Mon Sep 17 00:00:00 2001 From: Nikos Mavrogiannopoulos Date: Fri, 19 Feb 2010 17:01:52 +0100 Subject: Added support for asynchronous block ciphers. --- Makefile | 2 + cryptodev.c | 730 ------------------------------------------------------- cryptodev_int.h | 24 ++ cryptodev_main.c | 689 +++++++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 715 insertions(+), 730 deletions(-) delete mode 100644 cryptodev.c create mode 100644 cryptodev_int.h create mode 100644 cryptodev_main.c diff --git a/Makefile b/Makefile index 7c4a33b..2093acc 100644 --- a/Makefile +++ b/Makefile @@ -1,5 +1,7 @@ KERNEL_DIR := /lib/modules/$(shell uname -r)/build +cryptodev-objs = cryptodev_main.o cryptodev_cipher.o + obj-m += cryptodev.o build: 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 , SuSE Labs - * Copyright (c) 2009,2010 Nikos Mavrogiannopoulos - * - * 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 . - */ - -/* - * 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 -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include "cryptodev.h" -#include -#include -#include - -MODULE_AUTHOR("Michal Ludvig "); -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); diff --git a/cryptodev_int.h b/cryptodev_int.h new file mode 100644 index 0000000..f91bea7 --- /dev/null +++ b/cryptodev_int.h @@ -0,0 +1,24 @@ +struct cipher_data +{ + int type; /* 1 synchronous, 2 async, 0 uninitialized */ + int blocksize; + int ivsize; + union { + struct { + struct crypto_blkcipher* s; + struct blkcipher_desc desc; + } blk; + struct { + struct crypto_ablkcipher* s; + struct cryptodev_result *async_result; + struct ablkcipher_request *async_request; + uint8_t iv[EALG_MAX_BLOCK_LEN]; + } ablk; + } u; +}; + +int cryptodev_cipher_init(struct cipher_data* out, const char* alg_name, __user uint8_t * key, size_t keylen); +void cryptodev_cipher_deinit(struct cipher_data* cdata); +ssize_t cryptodev_cipher_decrypt( struct cipher_data* cdata, struct scatterlist *sg1, struct scatterlist *sg2, size_t len); +ssize_t cryptodev_cipher_encrypt( struct cipher_data* cdata, struct scatterlist *sg1, struct scatterlist *sg2, size_t len); +void cryptodev_cipher_set_iv(struct cipher_data* cdata, void* iv, size_t iv_size); diff --git a/cryptodev_main.c b/cryptodev_main.c new file mode 100644 index 0000000..2685161 --- /dev/null +++ b/cryptodev_main.c @@ -0,0 +1,689 @@ +/* + * Driver for /dev/crypto device (aka CryptoDev) + * + * Copyright (c) 2004 Michal Ludvig , SuSE Labs + * Copyright (c) 2009,2010 Nikos Mavrogiannopoulos + * + * 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 . + */ + +/* + * 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "cryptodev.h" +#include +#include +#include +#include "cryptodev_int.h" + +MODULE_AUTHOR("Nikos Mavrogiannopoulos "); +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 cipher_data cdata; + 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_hash *hash_tfm=NULL; + int ret = 0; + const char *alg_name=NULL; + const char *hash_name=NULL; + 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; + } + + /* 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)); + + /* Set-up crypto transform. */ + if (alg_name) { + + ret = cryptodev_cipher_init(&ses_new->cdata, alg_name, sop->key, sop->keylen); + if (ret < 0) { + return ret; + } + } + + 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; + } + } + + } + + /* put the new session to the list */ + get_random_bytes(&ses_new->sid, sizeof(ses_new->sid)); + 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: + cryptodev_cipher_deinit( &ses_new->cdata); + 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 + cryptodev_cipher_deinit(&ses_ptr->cdata); + 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 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; + + 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 (ses_ptr->cdata.type != 0) { + int blocksize = ses_ptr->cdata.blocksize; + + 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 = ses_ptr->cdata.ivsize; + + if (cop->iv) { + copy_from_user(ivp, cop->iv, ivsize); + cryptodev_cipher_set_iv(&ses_ptr->cdata, 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 (ses_ptr->cdata.type != 0) { + ret = cryptodev_cipher_encrypt( &ses_ptr->cdata, &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 (ses_ptr->cdata.type != 0) { + ret = cryptodev_cipher_decrypt( &ses_ptr->cdata, &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); -- cgit