summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorNikos Mavrogiannopoulos <nmav@gnutls.org>2009-11-28 15:47:11 +0200
committerNikos Mavrogiannopoulos <nmav@gnutls.org>2009-11-28 15:47:11 +0200
commit3900f010e89c004b23fc648ed16c661aae4da785 (patch)
tree5f4f088fd39277ff4907599e19dcc6ed8b79a0f5
downloadcryptodev-linux-3900f010e89c004b23fc648ed16c661aae4da785.tar.gz
cryptodev-linux-3900f010e89c004b23fc648ed16c661aae4da785.tar.xz
cryptodev-linux-3900f010e89c004b23fc648ed16c661aae4da785.zip
Added files.
-rw-r--r--Makefile14
-rw-r--r--cryptodev.c711
-rw-r--r--cryptodev.h156
-rw-r--r--cryptodev.mod.c57
-rw-r--r--example1.c133
-rw-r--r--example2.c182
6 files changed, 1253 insertions, 0 deletions
diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000..12bb5e2
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,14 @@
+KERNEL_DIR := /lib/modules/$(shell uname -r)/build
+
+obj-m += cryptodev.o
+
+build:
+ make -C $(KERNEL_DIR) SUBDIRS=`pwd` modules
+
+install:
+ make -C $(KERNEL_DIR) SUBDIRS=`pwd` modules_install
+ @echo "Installing cryptodev.h in /usr/include/crypto ..."
+ @install -D cryptodev.h /usr/include/crypto
+
+clean:
+ make -C $(KERNEL_DIR) SUBDIRS=`pwd` clean
diff --git a/cryptodev.c b/cryptodev.c
new file mode 100644
index 0000000..6007648
--- /dev/null
+++ b/cryptodev.c
@@ -0,0 +1,711 @@
+/*
+ * Driver for /dev/crypto device (aka CryptoDev)
+ *
+ * Copyright (c) 2004 Michal Ludvig <mludvig@logix.net.nz>, SuSE Labs
+ * Copyright (c) 2009 Nikos Mavrogiannopoulos <nmav@gennetsa.com>, Gennet S.A.
+ *
+ * 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("Dual BSD/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 (ses_ptr->tfm) {
+ if (nbytes % crypto_blkcipher_blocksize(ses_ptr->tfm)) {
+ dprintk(1, KERN_ERR,
+ "data size (%zu) isn't a multiple of block size (%u)\n",
+ nbytes, crypto_blkcipher_blocksize(ses_ptr->tfm));
+ ret = -EINVAL;
+ goto out_unlock;
+ }
+
+ ivsize = crypto_blkcipher_ivsize(ses_ptr->tfm);
+
+ if (cop->iv) {
+ copy_from_user(ivp, cop->iv, ivsize);
+ crypto_blkcipher_set_iv(ses_ptr->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.h b/cryptodev.h
new file mode 100644
index 0000000..895ad1c
--- /dev/null
+++ b/cryptodev.h
@@ -0,0 +1,156 @@
+/*
+ * Driver for /dev/crypto device (aka CryptoDev)
+ *
+ * Copyright (c) 2004 Michal Ludvig <mludvig@logix.net.nz>, SuSE Labs
+ *
+ * Structures and ioctl command names were taken from
+ * OpenBSD to preserve compatibility with their API.
+ *
+ */
+
+#ifndef _CRYPTODEV_H
+#define _CRYPTODEV_H
+
+#ifndef __KERNEL__
+#include <inttypes.h>
+#endif
+
+#define CRYPTODEV_MINOR MISC_DYNAMIC_MINOR
+
+
+#define CRYPTO_FLAG_HMAC 0x0010
+#define CRYPTO_FLAG_MASK 0x00FF
+
+enum {
+ CRYPTO_DES_CBC=1,
+ CRYPTO_3DES_CBC,
+ CRYPTO_BLF_CBC,
+ CRYPTO_AES_CBC,
+ CRYPTO_RIJNDAEL128_CBC=CRYPTO_AES_CBC,
+ CRYPTO_CAMELLIA_CBC,
+ /* unsupported from here */
+ CRYPTO_CAST_CBC,
+ CRYPTO_SKIPJACK_CBC,
+
+ CRYPTO_MD5_KPDK=200,
+ CRYPTO_SHA1_KPDK,
+ CRYPTO_MD5,
+ CRYPTO_RIPEMD160,
+ CRYPTO_SHA1,
+ CRYPTO_SHA2_256,
+ CRYPTO_SHA2_384,
+ CRYPTO_SHA2_512,
+ CRYPTO_MD5_HMAC,
+ CRYPTO_RIPEMD160_HMAC,
+ CRYPTO_SHA1_HMAC,
+ CRYPTO_SHA2_256_HMAC,
+ CRYPTO_SHA2_384_HMAC,
+ CRYPTO_SHA2_512_HMAC,
+ CRYPTO_ALGORITHM_MAX
+};
+
+#define CRYPTO_CIPHER_MAX_KEY_LEN 512
+#define CRYPTO_HMAC_MAX_KEY_LEN 512
+
+#define HASH_MAX_LEN 64
+
+struct crparam;
+struct crypt_kop;
+
+/* ioctl parameter to create a session */
+struct session_op {
+ uint16_t cipher; /* e.g. CRYPTO_DES_CBC */
+ uint16_t mac; /* e.g. CRYPTO_MD5_HMAC */
+ uint8_t *key;
+ size_t keylen; /* cipher key */
+ size_t mackeylen; /* mac key */
+ uint8_t *mackey;
+
+ /* Return values */
+ uint32_t ses; /* session ID */
+};
+
+/* ioctl parameter to request a crypt/decrypt operation against a session */
+struct crypt_op {
+ uint32_t ses; /* from session_op->ses */
+ #define COP_DECRYPT 0
+ #define COP_ENCRYPT 1
+ uint32_t op; /* ie. COP_ENCRYPT */
+ uint32_t flags; /* unused */
+
+ size_t len;
+ void *src, *dst;
+ void *mac;
+ void *iv;
+};
+
+/* clone original filedescriptor */
+#define CRIOGET _IOWR('c', 101, uint32_t)
+
+/* create crypto session */
+#define CIOCGSESSION _IOWR('c', 102, struct session_op)
+
+/* finish crypto session */
+#define CIOCFSESSION _IOW('c', 103, uint32_t)
+
+/* request encryption/decryptions of a given buffer */
+#define CIOCCRYPT _IOWR('c', 104, struct crypt_op)
+
+/* ioctl()s for asym-crypto. Not yet supported. */
+#define CIOCKEY _IOWR('c', 105, void *)
+#define CIOCASYMFEAT _IOR('c', 106, uint32_t)
+
+#endif /* _CRYPTODEV_H */
+
+/* unused structures */
+struct crparam {
+ caddr_t crp_p;
+ uint32_t crp_nbits;
+};
+
+#define CRK_MAXPARAM 8
+
+struct crypt_kop {
+ uint32_t crk_op; /* ie. CRK_MOD_EXP or other */
+ uint32_t crk_status; /* return status */
+ uint16_t crk_iparams; /* # of input parameters */
+ uint16_t crk_oparams; /* # of output parameters */
+ uint32_t crk_crid; /* NB: only used by CIOCKEY2 (rw) */
+ struct crparam crk_param[CRK_MAXPARAM];
+};
+
+/* Definitions from openbsd's cryptodev */
+
+#define DES_BLOCK_LEN 8
+#define DES3_BLOCK_LEN 8
+#define BLOWFISH_BLOCK_LEN 8
+#define SKIPJACK_BLOCK_LEN 8
+#define CAST128_BLOCK_LEN 8
+#define RIJNDAEL128_BLOCK_LEN 16
+#define AES_BLOCK_LEN RIJNDAEL128_BLOCK_LEN
+#define EALG_MAX_BLOCK_LEN AES_BLOCK_LEN /* Keep this updated */
+
+#define NULL_HASH_LEN 16
+#define MD5_HASH_LEN 16
+#define SHA1_HASH_LEN 20
+#define RIPEMD160_HASH_LEN 20
+#define SHA2_256_HASH_LEN 32
+#define SHA2_384_HASH_LEN 48
+#define SHA2_512_HASH_LEN 64
+#define MD5_KPDK_HASH_LEN 16
+#define SHA1_KPDK_HASH_LEN 20
+
+#define CRK_ALGORITM_MIN 0
+#define CRK_MOD_EXP 0
+#define CRK_MOD_EXP_CRT 1
+#define CRK_DSA_SIGN 2
+#define CRK_DSA_VERIFY 3
+#define CRK_DH_COMPUTE_KEY 4
+#define CRK_ALGORITHM_MAX 4 /* Keep updated - see below */
+
+#define CRF_MOD_EXP (1 << CRK_MOD_EXP)
+#define CRF_MOD_EXP_CRT (1 << CRK_MOD_EXP_CRT)
+#define CRF_DSA_SIGN (1 << CRK_DSA_SIGN)
+#define CRF_DSA_VERIFY (1 << CRK_DSA_VERIFY)
+#define CRF_DH_COMPUTE_KEY (1 << CRK_DH_COMPUTE_KEY)
+
diff --git a/cryptodev.mod.c b/cryptodev.mod.c
new file mode 100644
index 0000000..983f1f4
--- /dev/null
+++ b/cryptodev.mod.c
@@ -0,0 +1,57 @@
+#include <linux/module.h>
+#include <linux/vermagic.h>
+#include <linux/compiler.h>
+
+MODULE_INFO(vermagic, VERMAGIC_STRING);
+
+struct module __this_module
+__attribute__((section(".gnu.linkonce.this_module"))) = {
+ .name = KBUILD_MODNAME,
+ .init = init_module,
+#ifdef CONFIG_MODULE_UNLOAD
+ .exit = cleanup_module,
+#endif
+ .arch = MODULE_ARCH_INIT,
+};
+
+static const struct modversion_info ____versions[]
+__used
+__attribute__((section("__versions"))) = {
+ { 0x903a5ec4, "module_layout" },
+ { 0x6980fe91, "param_get_int" },
+ { 0xff964b25, "param_set_int" },
+ { 0x79aa04a2, "get_random_bytes" },
+ { 0x4661e311, "__tracepoint_kmalloc" },
+ { 0x893412fe, "kmem_cache_alloc" },
+ { 0xadee93f3, "kmalloc_caches" },
+ { 0x4e1e7401, "crypto_alloc_base" },
+ { 0x4302d0eb, "free_pages" },
+ { 0xe52947e7, "__phys_addr" },
+ { 0x236c8c64, "memcpy" },
+ { 0xbe499d81, "copy_to_user" },
+ { 0x93fca811, "__get_free_pages" },
+ { 0x37a0cba, "kfree" },
+ { 0x3f1899f1, "up" },
+ { 0x9bf377f5, "crypto_destroy_tfm" },
+ { 0x9ced38aa, "down_trylock" },
+ { 0x945bc6a7, "copy_from_user" },
+ { 0x748caf40, "down" },
+ { 0x6729d3df, "__get_user_4" },
+ { 0xf0fdf6cb, "__stack_chk_fail" },
+ { 0xb2fd5ceb, "__put_user_4" },
+ { 0xa1c76e0a, "_cond_resched" },
+ { 0xa62a61be, "fd_install" },
+ { 0x99bfbe39, "get_unused_fd" },
+ { 0xd8778690, "per_cpu__current_task" },
+ { 0x54b8041c, "misc_register" },
+ { 0xea147363, "printk" },
+ { 0x11cef3ea, "misc_deregister" },
+};
+
+static const char __module_depends[]
+__used
+__attribute__((section(".modinfo"))) =
+"depends=";
+
+
+MODULE_INFO(srcversion, "A0688FBD1488A8A38E2DF83");
diff --git a/example1.c b/example1.c
new file mode 100644
index 0000000..3bd2610
--- /dev/null
+++ b/example1.c
@@ -0,0 +1,133 @@
+/*
+ * Demo on how to use OpenBSD /dev/crypto device.
+ *
+ * Author: Michal Ludvig <michal@logix.cz>
+ * http://www.logix.cz/michal
+ *
+ * Note: by default OpenBSD doesn't allow using
+ * /dev/crypto if there is no hardware accelerator
+ * for a given algorithm. To change this you'll have to
+ * set cryptodevallowsoft=1 in
+ * /usr/src/sys/crypto/cryptodev.c and rebuild your kernel.
+ */
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+#include <fcntl.h>
+
+#include <sys/ioctl.h>
+//#include <crypto/cryptodev.h>
+#include "cryptodev.h"
+
+#define DATA_SIZE 4096
+#define BLOCK_SIZE 16
+#define KEY_SIZE 16
+
+static int
+test_crypto(int cfd)
+{
+ struct {
+ char in[DATA_SIZE],
+ encrypted[DATA_SIZE],
+ decrypted[DATA_SIZE],
+ iv[BLOCK_SIZE],
+ key[KEY_SIZE];
+ } data;
+ struct session_op sess;
+ struct crypt_op cryp;
+
+ memset(&sess, 0, sizeof(sess));
+ memset(&cryp, 0, sizeof(cryp));
+
+ /* Use the garbage that is on the stack :-) */
+ /* memset(&data, 0, sizeof(data)); */
+
+ /* Get crypto session for AES128 */
+ sess.cipher = CRYPTO_AES_CBC;
+ sess.keylen = KEY_SIZE;
+ sess.key = data.key;
+ if (ioctl(cfd, CIOCGSESSION, &sess)) {
+ perror("ioctl(CIOCGSESSION)");
+ return 1;
+ }
+
+ /* Encrypt data.in to data.encrypted */
+ cryp.ses = sess.ses;
+ cryp.len = sizeof(data.in);
+ cryp.src = data.in;
+ cryp.dst = data.encrypted;
+ cryp.iv = data.iv;
+ cryp.op = COP_ENCRYPT;
+ if (ioctl(cfd, CIOCCRYPT, &cryp)) {
+ perror("ioctl(CIOCCRYPT)");
+ return 1;
+ }
+
+ /* Decrypt data.encrypted to data.decrypted */
+ cryp.src = data.encrypted;
+ cryp.dst = data.decrypted;
+ cryp.op = COP_DECRYPT;
+ if (ioctl(cfd, CIOCCRYPT, &cryp)) {
+ perror("ioctl(CIOCCRYPT)");
+ return 1;
+ }
+
+ /* Verify the result */
+ if (memcmp(data.in, data.decrypted, sizeof(data.in)) != 0) {
+ fprintf(stderr,
+ "FAIL: Decrypted data are different from the input data.\n");
+ return 1;
+ } else
+ printf("Test passed\n");
+
+ /* Finish crypto session */
+ if (ioctl(cfd, CIOCFSESSION, &sess.ses)) {
+ perror("ioctl(CIOCFSESSION)");
+ return 1;
+ }
+
+ return 0;
+}
+
+int
+main()
+{
+ int fd = -1, cfd = -1;
+
+ /* Open the crypto device */
+ fd = open("/dev/crypto", O_RDWR, 0);
+ if (fd < 0) {
+ perror("open(/dev/crypto)");
+ return 1;
+ }
+
+ /* Clone file descriptor */
+ if (ioctl(fd, CRIOGET, &cfd)) {
+ perror("ioctl(CRIOGET)");
+ return 1;
+ }
+
+ /* Set close-on-exec (not really neede here) */
+ if (fcntl(cfd, F_SETFD, 1) == -1) {
+ perror("fcntl(F_SETFD)");
+ return 1;
+ }
+
+ /* Run the test itself */
+ if (test_crypto(cfd))
+ return 1;
+
+ /* Close cloned descriptor */
+ if (close(cfd)) {
+ perror("close(cfd)");
+ return 1;
+ }
+
+ /* Close the original descriptor */
+ if (close(fd)) {
+ perror("close(fd)");
+ return 1;
+ }
+
+ return 0;
+}
diff --git a/example2.c b/example2.c
new file mode 100644
index 0000000..d4c49f0
--- /dev/null
+++ b/example2.c
@@ -0,0 +1,182 @@
+/*
+ * Demo on how to use OpenBSD /dev/crypto device.
+ *
+ * Author: Michal Ludvig <michal@logix.cz>
+ * http://www.logix.cz/michal
+ *
+ * Note: by default OpenBSD doesn't allow using
+ * /dev/crypto if there is no hardware accelerator
+ * for a given algorithm. To change this you'll have to
+ * set cryptodevallowsoft=1 in
+ * /usr/src/sys/crypto/cryptodev.c and rebuild your kernel.
+ */
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+#include <fcntl.h>
+
+#include <sys/ioctl.h>
+//#include <crypto/cryptodev.h>
+#include "cryptodev.h"
+
+#define DATA_SIZE 4096
+#define BLOCK_SIZE 16
+#define KEY_SIZE 16
+
+static int
+test_crypto(int cfd)
+{
+ struct {
+ uint8_t in[DATA_SIZE],
+ encrypted[DATA_SIZE],
+ decrypted[DATA_SIZE],
+ iv[BLOCK_SIZE],
+ key[KEY_SIZE];
+ } data;
+ struct session_op sess;
+ struct crypt_op cryp;
+ uint8_t mac[HASH_MAX_LEN];
+ uint8_t oldmac[HASH_MAX_LEN];
+ uint8_t def_out[] = "\x75\x0c\x78\x3e\x6a\xb0\xb5\x03\xea\xa8\x6e\x31\x0a\x5d\xb7\x38";
+ int i;
+
+ memset(&sess, 0, sizeof(sess));
+ memset(&cryp, 0, sizeof(cryp));
+
+ /* Use the garbage that is on the stack :-) */
+ /* memset(&data, 0, sizeof(data)); */
+
+ /* Get crypto session for AES128 */
+ sess.cipher = 0;
+ sess.mac = CRYPTO_SHA1_HMAC;
+ sess.mackeylen = 4;
+ sess.mackey = (uint8_t*)"Jefe";
+ if (ioctl(cfd, CIOCGSESSION, &sess)) {
+ perror("ioctl(CIOCGSESSION)");
+ return 1;
+ }
+
+ memset(mac, 0, sizeof(mac));
+
+ cryp.ses = sess.ses;
+ cryp.len = sizeof("what do ya want for nothing?")-1;
+ cryp.src = "what do ya want for nothing?";
+ cryp.mac = mac;
+ cryp.op = COP_ENCRYPT;
+ if (ioctl(cfd, CIOCCRYPT, &cryp)) {
+ perror("ioctl(CIOCCRYPT)");
+ return 1;
+ }
+
+ if (memcmp(mac, def_out, sizeof(mac))!=0) {
+ printf("mac: ");
+ for (i=0;i<SHA1_HASH_LEN;i++) {
+ printf("%.2x", (uint8_t)mac[i]);
+ }
+ puts("\n");
+ fprintf(stderr, "HMAC test 1: failed\n");
+ } else {
+ fprintf(stderr, "HMAC test 1: passed\n");
+ }
+return 0;
+ sess.cipher = CRYPTO_AES_CBC;
+ sess.mac = CRYPTO_SHA1_HMAC;
+ sess.keylen = KEY_SIZE;
+ sess.key = data.key;
+ sess.mackeylen = 16;
+ sess.mackey = (uint8_t*)"\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b";
+ if (ioctl(cfd, CIOCGSESSION, &sess)) {
+ perror("ioctl(CIOCGSESSION)");
+ return 1;
+ }
+
+ /* Encrypt data.in to data.encrypted */
+ cryp.ses = sess.ses;
+ cryp.len = sizeof(data.in);
+ cryp.src = data.in;
+ cryp.dst = data.encrypted;
+ cryp.iv = data.iv;
+ cryp.mac = mac;
+ cryp.op = COP_ENCRYPT;
+ if (ioctl(cfd, CIOCCRYPT, &cryp)) {
+ perror("ioctl(CIOCCRYPT)");
+ return 1;
+ }
+
+ memcpy(oldmac, mac, sizeof(mac));
+
+ /* Decrypt data.encrypted to data.decrypted */
+ cryp.src = data.encrypted;
+ cryp.dst = data.decrypted;
+ cryp.op = COP_DECRYPT;
+ if (ioctl(cfd, CIOCCRYPT, &cryp)) {
+ perror("ioctl(CIOCCRYPT)");
+ return 1;
+ }
+
+ /* Verify the result */
+ if (memcmp(data.in, data.decrypted, sizeof(data.in)) != 0) {
+ fprintf(stderr,
+ "FAIL: Decrypted data are different from the input data.\n");
+ return 1;
+ } else
+ printf("Crypt Test: passed\n");
+
+ if (memcmp(mac, oldmac, sizeof(mac)) != 0) {
+ fprintf(stderr,
+ "FAIL: Hash in decrypted data different than in encrypted.\n");
+ return 1;
+ } else
+ printf("HMAC Test 2: passed\n");
+
+ /* Finish crypto session */
+ if (ioctl(cfd, CIOCFSESSION, &sess.ses)) {
+ perror("ioctl(CIOCFSESSION)");
+ return 1;
+ }
+
+ return 0;
+}
+
+int
+main()
+{
+ int fd = -1, cfd = -1;
+
+ /* Open the crypto device */
+ fd = open("/dev/crypto", O_RDWR, 0);
+ if (fd < 0) {
+ perror("open(/dev/crypto)");
+ return 1;
+ }
+
+ /* Clone file descriptor */
+ if (ioctl(fd, CRIOGET, &cfd)) {
+ perror("ioctl(CRIOGET)");
+ return 1;
+ }
+
+ /* Set close-on-exec (not really neede here) */
+ if (fcntl(cfd, F_SETFD, 1) == -1) {
+ perror("fcntl(F_SETFD)");
+ return 1;
+ }
+
+ /* Run the test itself */
+ if (test_crypto(cfd))
+ return 1;
+
+ /* Close cloned descriptor */
+ if (close(cfd)) {
+ perror("close(cfd)");
+ return 1;
+ }
+
+ /* Close the original descriptor */
+ if (close(fd)) {
+ perror("close(fd)");
+ return 1;
+ }
+
+ return 0;
+}