/*
* 2007+ Copyright (c) Evgeniy Polyakov <zbr@ioremap.net>
* All rights reserved.
*
* 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.
*/
#include <linux/crypto.h>
#include <linux/highmem.h>
#include <linux/kthread.h>
#include <linux/pagemap.h>
#include <linux/slab.h>
#include "netfs.h"
static struct crypto_hash *pohmelfs_init_hash(struct pohmelfs_sb *psb)
{
int err;
struct crypto_hash *hash;
hash = crypto_alloc_hash(psb->hash_string, 0, CRYPTO_ALG_ASYNC);
if (IS_ERR(hash)) {
err = PTR_ERR(hash);
dprintk("%s: idx: %u: failed to allocate hash '%s', err: %d.\n",
__func__, psb->idx, psb->hash_string, err);
goto err_out_exit;
}
psb->crypto_attached_size = crypto_hash_digestsize(hash);
if (!psb->hash_keysize)
return hash;
err = crypto_hash_setkey(hash, psb->hash_key, psb->hash_keysize);
if (err) {
dprintk("%s: idx: %u: failed to set key for hash '%s', err: %d.\n",
__func__, psb->idx, psb->hash_string, err);
goto err_out_free;
}
return hash;
err_out_free:
crypto_free_hash(hash);
err_out_exit:
return ERR_PTR(err);
}
static struct crypto_ablkcipher *pohmelfs_init_cipher(struct pohmelfs_sb *psb)
{
int err = -EINVAL;
struct crypto_ablkcipher *cipher;
if (!psb->cipher_keysize)
goto err_out_exit;
cipher = crypto_alloc_ablkcipher(psb->cipher_string, 0, 0);
if (IS_ERR(cipher)) {
err = PTR_ERR(cipher);
dprintk("%s: idx: %u: failed to allocate cipher '%s', err: %d.\n",
__func__, psb->idx, psb->cipher_string, err);
goto err_out_exit;
}
crypto_ablkcipher_clear_flags(cipher, ~0);
err = crypto_ablkcipher_setkey(cipher, psb->cipher_key, psb->cipher_keysize);
if (err) {
dprintk("%s: idx: %u: failed to set key for cipher '%s', err: %d.\n",
__func__, psb->idx, psb->cipher_string, err);
goto err_out_free;
}
return cipher;
err_out_free:
crypto_free_ablkcipher(cipher);
err_out_exit:
return ERR_PTR(err);
}
int pohmelfs_crypto_engine_init(struct pohmelfs_crypto_engine *e, struct pohmelfs_sb *psb)
{
int err;
e->page_num = 0;
e->size = PAGE_SIZE;
e->data = kmalloc(e->size, GFP_KERNEL);
if (!e->data) {
err = -ENOMEM;
goto err_out_exit;
}
if (psb->hash_string) {
e->hash = pohmelfs_init_hash(psb);
if (IS_ERR(e->hash)) {
err = PTR_ERR(e->hash);
e->hash = NULL;
goto err_out_free;
}
}
if (psb->cipher_string) {
e->cipher = pohmelfs_init_cipher(psb);
if (IS_ERR(e->cipher)) {
err = PTR_ERR(e->cipher);
e->cipher = NULL;
goto err_out_free_hash;
}
}
return 0;
err_out_free_hash:
crypto_free_hash(e->hash);
err_out_free:
kfree(e->data);
err_out_exit:
return err;
}
void pohmelfs_crypto_engine_exit(struct pohmelfs_crypto_engine *e)
{
crypto_free_hash(e->hash);
crypto_free_ablkcipher(e->cipher);
kfree(e->data);
}
static void pohmelfs_crypto_complete(struct crypto_async_request *req, int err)
{
struct pohmelfs_crypto_completion *c = req->data;
if (err == -EINPROGRESS)
return;
dprintk("%s: req: %p, err: %d.\n", __func__, req, err);
c->error = err;
complete(&c->complete);
}
static int pohmelfs_crypto_process(struct ablkcipher_request *req,
struct scatterlist *sg_dst, struct scatterlist *sg_src,
void *iv, int enc, unsigned long timeout)
{
struct pohmelfs_crypto_completion complete;
int err;
init_completion(&complete.complete);
complete.error = -EINPROGRESS;
ablkcipher_request_set_callback(req, CRYPTO_TFM_REQ_MAY_BACKLOG,
pohmelfs_crypto_complete, &complete);
ablkcipher_request_set_crypt(req, sg_src, sg_dst, sg_src->length, iv);
if (enc)
err = crypto_ablkcipher_encrypt(req);
else
err = crypto_ablkcipher_decrypt(req);
switch (err) {
case -EINPROGRESS:
case -EBUSY:
err = wait_for_completion_interruptible_timeout(&complete.complete,
timeout);
if (!err)
err = -ETIMEDOUT;
else if (err > 0)
err = complete.error;
break;
default:
break;
}