blob: fad5ba6cb54306c25aa5b9f82e1cc89e1a9ebdb1 [file] [log] [blame]
/*
* Driver for /dev/crypto device (aka CryptoDev)
*
* Copyright (c) 2010,2011 Nikos Mavrogiannopoulos <nmav@gnutls.org>
* Portions Copyright (c) 2010 Michael Weiser
* Portions Copyright (c) 2010 Phil Sutter
*
* 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/crypto.h>
#include <linux/mm.h>
#include <linux/highmem.h>
#include <linux/ioctl.h>
#include <linux/random.h>
#include <linux/scatterlist.h>
#include <linux/uaccess.h>
#include <crypto/algapi.h>
#include <crypto/hash.h>
#include <crypto/cryptodev.h>
#include <crypto/aead.h>
#include "cryptodev_int.h"
struct cryptodev_result {
struct completion completion;
int err;
};
static void cryptodev_complete(struct crypto_async_request *req, int err)
{
struct cryptodev_result *res = req->data;
if (err == -EINPROGRESS)
return;
res->err = err;
complete(&res->completion);
}
int cryptodev_cipher_init(struct cipher_data *out, const char *alg_name,
uint8_t *keyp, size_t keylen, int stream, int aead)
{
int ret;
memset(out, 0, sizeof(*out));
if (aead == 0) {
struct ablkcipher_alg *alg;
out->async.s = crypto_alloc_ablkcipher(alg_name, 0, 0);
if (unlikely(IS_ERR(out->async.s))) {
dprintk(1, KERN_DEBUG, "Failed to load cipher %s\n", alg_name);
return -EINVAL;
}
alg = crypto_ablkcipher_alg(out->async.s);
if (alg != NULL) {
/* Was correct key length supplied? */
if (alg->max_keysize > 0 &&
unlikely((keylen < alg->min_keysize) ||
(keylen > alg->max_keysize))) {
dprintk(1, KERN_DEBUG,
"Wrong keylen '%zu' for algorithm '%s'. \
Use %u to %u.\n",
keylen, alg_name, alg->min_keysize,
alg->max_keysize);
ret = -EINVAL;
goto error;
}
}
out->blocksize = crypto_ablkcipher_blocksize(out->async.s);
out->ivsize = crypto_ablkcipher_ivsize(out->async.s);
out->alignmask = crypto_ablkcipher_alignmask(out->async.s);
ret = crypto_ablkcipher_setkey(out->async.s, keyp, keylen);
} else {
out->async.as = crypto_alloc_aead(alg_name, 0, 0);
if (unlikely(IS_ERR(out->async.as))) {
dprintk(1, KERN_DEBUG, "Failed to load cipher %s\n", alg_name);
return -EINVAL;
}
out->blocksize = crypto_aead_blocksize(out->async.as);
out->ivsize = crypto_aead_ivsize(out->async.as);
out->alignmask = crypto_aead_alignmask(out->async.as);
ret = crypto_aead_setkey(out->async.as, keyp, keylen);
}
if (unlikely(ret)) {
dprintk(1, KERN_DEBUG, "Setting key failed for %s-%zu.\n",
alg_name, keylen*8);
ret = -EINVAL;
goto error;
}
out->stream = stream;
out->aead = aead;
out->async.result = kmalloc(sizeof(*out->async.result), GFP_KERNEL);
if (unlikely(!out->async.result)) {
ret = -ENOMEM;
goto error;
}
memset(out->async.result, 0, sizeof(*out->async.result));
init_completion(&out->async.result->completion);
if (aead == 0) {
out->async.request = ablkcipher_request_alloc(out->async.s, GFP_KERNEL);
if (unlikely(!out->async.request)) {
dprintk(1, KERN_ERR, "error allocating async crypto request\n");
ret = -ENOMEM;
goto error;
}
ablkcipher_request_set_callback(out->async.request,
CRYPTO_TFM_REQ_MAY_BACKLOG,
cryptodev_complete, out->async.result);
} else {
out->async.arequest = aead_request_alloc(out->async.as, GFP_KERNEL);
if (unlikely(!out->async.arequest)) {
dprintk(1, KERN_ERR, "error allocating async crypto request\n");
ret = -ENOMEM;
goto error;
}
aead_request_set_callback(out->async.arequest,
CRYPTO_TFM_REQ_MAY_BACKLOG,
cryptodev_complete, out->async.result);
}
out->init = 1;
return 0;
error:
if (aead == 0) {
if (out->async.request)
ablkcipher_request_free(out->async.request);
if (out->async.s)
crypto_free_ablkcipher(out->async.s);
} else {
if (out->async.arequest)
aead_request_free(out->async.arequest);
if (out->async.s)
crypto_free_aead(out->async.as);
}
kfree(out->async.result);
return ret;
}
void cryptodev_cipher_deinit(struct cipher_data *cdata)
{
if (cdata->init) {
if (cdata->aead == 0) {
if (cdata->async.request)
ablkcipher_request_free(cdata->async.request);
if (cdata->async.s)
crypto_free_ablkcipher(cdata->async.s);
} else {
if (cdata->async.arequest)
aead_request_free(cdata->async.arequest);
if (cdata->async.as)
crypto_free_aead(cdata->async.as);
}
kfree(cdata->async.result);
cdata->init = 0;
}
}
static inline int waitfor(struct cryptodev_result *cr, ssize_t ret)
{
switch (ret) {
case 0:
break;
case -EINPROGRESS:
case -EBUSY:
wait_for_completion(&cr->completion);
/* At this point we known for sure the request has finished,
* because wait_for_completion above was not interruptible.
* This is important because otherwise hardware or driver
* might try to access memory which will be freed or reused for
* another request. */
if (unlikely(cr->err)) {
dprintk(0, KERN_ERR, "error from async request: %d\n",
cr->err);
return cr->err;
}
break;
default:
return ret;
}
return 0;
}
ssize_t cryptodev_cipher_encrypt(struct cipher_data *cdata,
const struct scatterlist *src, struct scatterlist *dst,
size_t len)
{
int ret;
INIT_COMPLETION(cdata->async.result->completion);
if (cdata->aead == 0) {
ablkcipher_request_set_crypt(cdata->async.request,
(struct scatterlist *)src, dst,
len, cdata->async.iv);
ret = crypto_ablkcipher_encrypt(cdata->async.request);
} else {
aead_request_set_crypt(cdata->async.arequest,
(struct scatterlist *)src, dst,
len, cdata->async.iv);
ret = crypto_aead_encrypt(cdata->async.arequest);
}
return waitfor(cdata->async.result, ret);
}
ssize_t cryptodev_cipher_decrypt(struct cipher_data *cdata,
const struct scatterlist *src, struct scatterlist *dst,
size_t len)
{
int ret;
INIT_COMPLETION(cdata->async.result->completion);
if (cdata->aead == 0) {
ablkcipher_request_set_crypt(cdata->async.request,
(struct scatterlist *)src, dst,
len, cdata->async.iv);
ret = crypto_ablkcipher_decrypt(cdata->async.request);
} else {
aead_request_set_crypt(cdata->async.arequest,
(struct scatterlist *)src, dst,
len, cdata->async.iv);
ret = crypto_aead_decrypt(cdata->async.arequest);
}
return waitfor(cdata->async.result, ret);
}
/* Hash functions */
int cryptodev_hash_init(struct hash_data *hdata, const char *alg_name,
int hmac_mode, void *mackey, size_t mackeylen)
{
int ret;
hdata->async.s = crypto_alloc_ahash(alg_name, 0, 0);
if (unlikely(IS_ERR(hdata->async.s))) {
dprintk(1, KERN_DEBUG, "Failed to load transform for %s\n", alg_name);
return -EINVAL;
}
/* Copy the key from user and set to TFM. */
if (hmac_mode != 0) {
ret = crypto_ahash_setkey(hdata->async.s, mackey, mackeylen);
if (unlikely(ret)) {
dprintk(1, KERN_DEBUG,
"Setting hmac key failed for %s-%zu.\n",
alg_name, mackeylen*8);
ret = -EINVAL;
goto error;
}
}
hdata->digestsize = crypto_ahash_digestsize(hdata->async.s);
hdata->alignmask = crypto_ahash_alignmask(hdata->async.s);
hdata->async.result = kmalloc(sizeof(*hdata->async.result), GFP_KERNEL);
if (unlikely(!hdata->async.result)) {
ret = -ENOMEM;
goto error;
}
memset(hdata->async.result, 0, sizeof(*hdata->async.result));
init_completion(&hdata->async.result->completion);
hdata->async.request = ahash_request_alloc(hdata->async.s, GFP_KERNEL);
if (unlikely(!hdata->async.request)) {
dprintk(0, KERN_ERR, "error allocating async crypto request\n");
ret = -ENOMEM;
goto error;
}
ahash_request_set_callback(hdata->async.request,
CRYPTO_TFM_REQ_MAY_BACKLOG,
cryptodev_complete, hdata->async.result);
ret = crypto_ahash_init(hdata->async.request);
if (unlikely(ret)) {
dprintk(0, KERN_ERR, "error in crypto_hash_init()\n");
goto error_request;
}
hdata->init = 1;
return 0;
error_request:
ahash_request_free(hdata->async.request);
error:
kfree(hdata->async.result);
crypto_free_ahash(hdata->async.s);
return ret;
}
void cryptodev_hash_deinit(struct hash_data *hdata)
{
if (hdata->init) {
if (hdata->async.request)
ahash_request_free(hdata->async.request);
kfree(hdata->async.result);
if (hdata->async.s)
crypto_free_ahash(hdata->async.s);
hdata->init = 0;
}
}
int cryptodev_hash_reset(struct hash_data *hdata)
{
int ret;
ret = crypto_ahash_init(hdata->async.request);
if (unlikely(ret)) {
dprintk(0, KERN_ERR, "error in crypto_hash_init()\n");
return ret;
}
return 0;
}
ssize_t cryptodev_hash_update(struct hash_data *hdata,
struct scatterlist *sg, size_t len)
{
int ret;
INIT_COMPLETION(hdata->async.result->completion);
ahash_request_set_crypt(hdata->async.request, sg, NULL, len);
ret = crypto_ahash_update(hdata->async.request);
return waitfor(hdata->async.result, ret);
}
int cryptodev_hash_final(struct hash_data *hdata, void* output)
{
int ret;
INIT_COMPLETION(hdata->async.result->completion);
ahash_request_set_crypt(hdata->async.request, NULL, output, 0);
ret = crypto_ahash_final(hdata->async.request);
return waitfor(hdata->async.result, ret);
}