blob: fe6d3909c1424293cf2e4ef3c575e735a41d521f [file] [log] [blame]
/*
* 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.
*
* 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.
*/
/*
* 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.
*
*/
#include <crypto/hash.h>
#include <linux/crypto.h>
#include <linux/mm.h>
#include <linux/highmem.h>
#include <linux/ioctl.h>
#include <linux/random.h>
#include <linux/syscalls.h>
#include <linux/pagemap.h>
#include <linux/poll.h>
#include <linux/uaccess.h>
#include <crypto/cryptodev.h>
#include <crypto/scatterwalk.h>
#include <linux/scatterlist.h>
#include "cryptodev_int.h"
#include "zc.h"
#include "cryptlib.h"
#include "version.h"
/* This file contains the traditional operations of encryption
* and hashing of /dev/crypto.
*/
static int
hash_n_crypt(struct csession *ses_ptr, struct crypt_op *cop,
struct scatterlist *src_sg, struct scatterlist *dst_sg,
uint32_t len)
{
int ret;
/* Always hash before encryption and after decryption. Maybe
* we should introduce a flag to switch... TBD later on.
*/
if (cop->op == COP_ENCRYPT) {
if (ses_ptr->hdata.init != 0) {
ret = cryptodev_hash_update(&ses_ptr->hdata,
src_sg, len);
if (unlikely(ret))
goto out_err;
}
if (ses_ptr->cdata.init != 0) {
ret = cryptodev_cipher_encrypt(&ses_ptr->cdata,
src_sg, dst_sg, len);
if (unlikely(ret))
goto out_err;
}
} else {
if (ses_ptr->cdata.init != 0) {
ret = cryptodev_cipher_decrypt(&ses_ptr->cdata,
src_sg, dst_sg, len);
if (unlikely(ret))
goto out_err;
}
if (ses_ptr->hdata.init != 0) {
ret = cryptodev_hash_update(&ses_ptr->hdata,
dst_sg, len);
if (unlikely(ret))
goto out_err;
}
}
return 0;
out_err:
dprintk(0, KERN_ERR, "CryptoAPI failure: %d\n", ret);
return ret;
}
/* This is the main crypto function - feed it with plaintext
and get a ciphertext (or vice versa :-) */
static int
__crypto_run_std(struct csession *ses_ptr, struct crypt_op *cop)
{
char *data;
char __user *src, *dst;
struct scatterlist sg;
size_t nbytes, bufsize;
int ret = 0;
nbytes = cop->len;
data = (char *)__get_free_page(GFP_KERNEL);
if (unlikely(!data)) {
dprintk(1, KERN_ERR, "Error getting free page.\n");
return -ENOMEM;
}
bufsize = PAGE_SIZE < nbytes ? PAGE_SIZE : nbytes;
src = cop->src;
dst = cop->dst;
while (nbytes > 0) {
size_t current_len = nbytes > bufsize ? bufsize : nbytes;
if (unlikely(copy_from_user(data, src, current_len))) {
dprintk(1, KERN_ERR, "Error copying %d bytes from user address %p.\n", (int)current_len, src);
ret = -EFAULT;
break;
}
sg_init_one(&sg, data, current_len);
ret = hash_n_crypt(ses_ptr, cop, &sg, &sg, current_len);
if (unlikely(ret)) {
dprintk(1, KERN_ERR, "hash_n_crypt failed.\n");
break;
}
if (ses_ptr->cdata.init != 0) {
if (unlikely(copy_to_user(dst, data, current_len))) {
dprintk(1, KERN_ERR, "could not copy to user.\n");
ret = -EFAULT;
break;
}
}
dst += current_len;
nbytes -= current_len;
src += current_len;
}
free_page((unsigned long)data);
return ret;
}
/* This is the main crypto function - zero-copy edition */
static int
__crypto_run_zc(struct csession *ses_ptr, struct kernel_crypt_op *kcop)
{
struct scatterlist *src_sg, *dst_sg;
struct crypt_op *cop = &kcop->cop;
int ret = 0;
ret = get_userbuf(ses_ptr, cop->src, cop->len, cop->dst, cop->len,
kcop->task, kcop->mm, &src_sg, &dst_sg);
if (unlikely(ret)) {
dprintk(1, KERN_ERR, "Error getting user pages. "
"Falling back to non zero copy.\n");
return __crypto_run_std(ses_ptr, cop);
}
ret = hash_n_crypt(ses_ptr, cop, src_sg, dst_sg, cop->len);
release_user_pages(ses_ptr);
return ret;
}
int crypto_run(struct fcrypt *fcr, struct kernel_crypt_op *kcop)
{
struct csession *ses_ptr;
struct crypt_op *cop = &kcop->cop;
int ret;
if (unlikely(cop->op != COP_ENCRYPT && cop->op != COP_DECRYPT)) {
dprintk(1, KERN_DEBUG, "invalid operation op=%u\n", cop->op);
return -EINVAL;
}
/* this also enters ses_ptr->sem */
ses_ptr = crypto_get_session_by_sid(fcr, cop->ses);
if (unlikely(!ses_ptr)) {
dprintk(1, KERN_ERR, "invalid session ID=0x%08X\n", cop->ses);
return -EINVAL;
}
if (ses_ptr->hdata.init != 0 && (cop->flags == 0 || cop->flags & COP_FLAG_RESET)) {
ret = cryptodev_hash_reset(&ses_ptr->hdata);
if (unlikely(ret)) {
dprintk(1, KERN_ERR,
"error in cryptodev_hash_reset()\n");
goto out_unlock;
}
}
if (ses_ptr->cdata.init != 0) {
int blocksize = ses_ptr->cdata.blocksize;
if (unlikely(cop->len % blocksize)) {
dprintk(1, KERN_ERR,
"data size (%u) isn't a multiple "
"of block size (%u)\n",
cop->len, blocksize);
ret = -EINVAL;
goto out_unlock;
}
cryptodev_cipher_set_iv(&ses_ptr->cdata, kcop->iv,
min(ses_ptr->cdata.ivsize, kcop->ivlen));
}
if (likely(cop->len)) {
if (cop->flags & COP_FLAG_NO_ZC)
ret = __crypto_run_std(ses_ptr, &kcop->cop);
else
ret = __crypto_run_zc(ses_ptr, kcop);
if (unlikely(ret))
goto out_unlock;
}
if (ses_ptr->cdata.init != 0) {
cryptodev_cipher_get_iv(&ses_ptr->cdata, kcop->iv,
min(ses_ptr->cdata.ivsize, kcop->ivlen));
}
if (ses_ptr->hdata.init != 0 &&
((cop->flags & COP_FLAG_FINAL) ||
(!(cop->flags & COP_FLAG_UPDATE) || cop->len == 0))) {
ret = cryptodev_hash_final(&ses_ptr->hdata, kcop->hash_output);
if (unlikely(ret)) {
dprintk(0, KERN_ERR, "CryptoAPI failure: %d\n", ret);
goto out_unlock;
}
kcop->digestsize = ses_ptr->hdata.digestsize;
}
out_unlock:
crypto_put_session(ses_ptr);
return ret;
}