/*
 * ipsec_alg to linux cryptoapi GLUE
 *
 * Authors: CODE.ar TEAM
 * 	Harpo MAxx <harpo@linuxmendoza.org.ar>
 * 	JuanJo Ciarlante <jjo-ipsec@mendoza.gov.ar>
 * 	Luciano Ruete <docemeses@softhome.net>
 * 
 * ipsec_alg_cryptoapi.c,v 1.1.2.1 2003/11/21 18:12:23 jjo Exp
 * 
 * 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.  See <http://www.fsf.org/copyleft/gpl.txt>.
 * 
 * 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.
 *
 * Example usage:
 *   modinfo -p ipsec_cryptoapi   (quite useful info, including supported algos)
 *   modprobe ipsec_cryptoapi
 *   modprobe ipsec_cryptoapi test=1
 *   modprobe ipsec_cryptoapi excl=1                     (exclusive cipher/algo)
 *   modprobe ipsec_cryptoapi noauto=1  aes=1 twofish=1  (only these ciphers)
 *   modprobe ipsec_cryptoapi aes=128,128                (force these keylens)
 *   modprobe ipsec_cryptoapi des_ede3=0                 (everything but 3DES)
 */
#include <linux/config.h>
#include <linux/version.h>

/*	
 *	special case: ipsec core modular with this static algo inside:
 *	must avoid MODULE magic for this file
 */
#if CONFIG_KLIPS_MODULE && CONFIG_KLIPS_ENC_CRYPTOAPI
#undef MODULE
#endif

#include <linux/module.h>
#include <linux/init.h>

#include <linux/kernel.h> /* printk() */
#include <linux/errno.h>  /* error codes */
#include <linux/types.h>  /* size_t */
#include <linux/string.h>

/* Check if __exit is defined, if not null it */
#ifndef __exit
#define __exit
#endif

/* warn the innocent */
#if !defined (CONFIG_CRYPTO) && !defined (CONFIG_CRYPTO_MODULE)
#warning "No linux CryptoAPI found, install 2.4.22+ or 2.6.x"
#define NO_CRYPTOAPI_SUPPORT
#endif

#include "openswan.h"
#include "openswan/ipsec_alg.h"
#include "openswan/ipsec_policy.h"

#include <linux/crypto.h>
#ifdef CRYPTO_API_VERSION_CODE
#warning "Old CryptoAPI is not supported. Only linux-2.4.22+ or linux-2.6.x are supported"
#define NO_CRYPTOAPI_SUPPORT
#endif

#ifdef NO_CRYPTOAPI_SUPPORT
#warning "Building an unusable module :P"
/* Catch old CryptoAPI by not allowing module to load */
IPSEC_ALG_MODULE_INIT_STATIC( ipsec_cryptoapi_init )
{
	printk(KERN_WARNING "ipsec_cryptoapi.o was not built on stock Linux CryptoAPI (2.4.22+ or 2.6.x), not loading.\n");
	return -EINVAL;
}
#else
#include <asm/scatterlist.h>
#include <asm/pgtable.h>
#include <linux/mm.h>

#define CIPHERNAME_AES		"aes"
#define CIPHERNAME_1DES		"des"
#define CIPHERNAME_3DES		"des3_ede"
#define CIPHERNAME_BLOWFISH	"blowfish"
#define CIPHERNAME_CAST		"cast5"
#define CIPHERNAME_SERPENT	"serpent"
#define CIPHERNAME_TWOFISH	"twofish"

#define ESP_SERPENT		252	/* from ipsec drafts */
#define ESP_TWOFISH		253	/* from ipsec drafts */

#define DIGESTNAME_MD5		"md5"
#define DIGESTNAME_SHA1		"sha1"

MODULE_AUTHOR("Juanjo Ciarlante, Harpo MAxx, Luciano Ruete");
static int debug_crypto=0;
MODULE_PARM(debug_crypto, "i");
static int test_crypto=0;
MODULE_PARM(test_crypto, "i");
static int excl_crypto=0;
MODULE_PARM(excl_crypto, "i");

static int noauto = 0;
MODULE_PARM(noauto,"i");
MODULE_PARM_DESC(noauto, "Dont try all known algos, just setup enabled ones");

#ifdef CONFIG_KLIPS_ENC_1DES
static int des_ede1[] = {-1, -1};
#endif
static int des_ede3[] = {-1, -1};
static int aes[] = {-1, -1};
static int blowfish[] = {-1, -1};
static int cast[] = {-1, -1};
static int serpent[] = {-1, -1};
static int twofish[] = {-1, -1};

#ifdef CONFIG_KLIPS_ENC_1DES
MODULE_PARM(des_ede1,"1-2i");
#endif
MODULE_PARM(des_ede3,"1-2i");
MODULE_PARM(aes,"1-2i");
MODULE_PARM(blowfish,"1-2i");
MODULE_PARM(cast,"1-2i");
MODULE_PARM(serpent,"1-2i");
MODULE_PARM(twofish,"1-2i");
MODULE_PARM_DESC(des_ede1, "0: disable | 1: force_enable | min,max: dontuse");
MODULE_PARM_DESC(des_ede3, "0: disable | 1: force_enable | min,max: dontuse");
MODULE_PARM_DESC(aes, "0: disable | 1: force_enable | min,max: keybitlens");
MODULE_PARM_DESC(blowfish, "0: disable | 1: force_enable | min,max: keybitlens");
MODULE_PARM_DESC(cast, "0: disable | 1: force_enable | min,max: keybitlens");
MODULE_PARM_DESC(serpent, "0: disable | 1: force_enable | min,max: keybitlens");
MODULE_PARM_DESC(twofish, "0: disable | 1: force_enable | min,max: keybitlens");

struct ipsec_alg_capi_cipher {
	const char *ciphername;	/* cryptoapi's ciphername */
	unsigned blocksize;
	unsigned short minbits;
	unsigned short maxbits;
	int *parm;		/* lkm param for this cipher */
	struct ipsec_alg_enc alg;	/* note it's not a pointer */
};

static struct ipsec_alg_capi_cipher alg_capi_carray[] = {
  { CIPHERNAME_AES,     16, 128, 256, aes,      { ixt_common:{ ixt_support:{ ias_id: ESP_AES}}}},
  { CIPHERNAME_TWOFISH, 16, 128, 256, twofish,  { ixt_common:{ ixt_support:{ ias_id: ESP_TWOFISH,}}}},
  { CIPHERNAME_SERPENT, 16, 128, 256, serpent,  { ixt_common:{ ixt_support:{ ias_id: ESP_SERPENT,}}}},
  { CIPHERNAME_CAST,     8, 128, 128, cast   ,  { ixt_common:{ ixt_support:{ ias_id: ESP_CAST,}}}},
  { CIPHERNAME_BLOWFISH, 8,  96, 448, blowfish, { ixt_common:{ ixt_support:{ ias_id: ESP_BLOWFISH,}}}},
  { CIPHERNAME_3DES,     8, 192, 192, des_ede3, { ixt_common:{ ixt_support:{ ias_id: ESP_3DES,}}}},
#ifdef CONFIG_KLIPS_ENC_1DES
  { CIPHERNAME_1DES,     8,  64,  64, des_ede1, { ixt_common:{ ixt_support:{ ias_id: ESP_DES,}}}},
#endif
  { NULL, 0, 0, 0, NULL, {} }
};

#ifdef NOT_YET
struct ipsec_alg_capi_digest {
	const char *digestname;	/* cryptoapi's digestname */
	struct digest_implementation *di;
	struct ipsec_alg_auth alg;	/* note it's not a pointer */
};
static struct ipsec_alg_capi_cipher alg_capi_darray[] = {
	{ DIGESTNAME_MD5,     NULL, { ixt_alg_id: AH_MD5, }},
	{ DIGESTNAME_SHA1,    NULL, { ixt_alg_id: AH_SHA, }},
	{ NULL, NULL, {} }
};
#endif
/*
 * 	"generic" linux cryptoapi setup_cipher() function
 */
int setup_cipher(const char *ciphername)
{
	return crypto_alg_available(ciphername, 0);
}

/*
 * 	setups ipsec_alg_capi_cipher "hyper" struct components, calling
 * 	register_ipsec_alg for cointaned ipsec_alg object
 */
static void _capi_destroy_key (struct ipsec_alg_enc *alg, __u8 *key_e);
static __u8 * _capi_new_key (struct ipsec_alg_enc *alg, const __u8 *key, size_t keylen);
static int _capi_cbc_encrypt(struct ipsec_alg_enc *alg, __u8 * key_e, __u8 * in, int ilen, const __u8 * iv, int encrypt);

static int
setup_ipsec_alg_capi_cipher(struct ipsec_alg_capi_cipher *cptr)
{
	int ret;
	cptr->alg.ixt_common.ixt_version = IPSEC_ALG_VERSION;
	cptr->alg.ixt_common.ixt_module  = THIS_MODULE;
	atomic_set (& cptr->alg.ixt_common.ixt_refcnt, 0);
	strncpy (cptr->alg.ixt_common.ixt_name , cptr->ciphername, sizeof (cptr->alg.ixt_common.ixt_name));

	cptr->alg.ixt_common.ixt_blocksize=cptr->blocksize;
	cptr->alg.ixt_common.ixt_support.ias_keyminbits=cptr->minbits;
	cptr->alg.ixt_common.ixt_support.ias_keymaxbits=cptr->maxbits;
	cptr->alg.ixt_common.ixt_state = 0;
	if (excl_crypto) cptr->alg.ixt_common.ixt_state |= IPSEC_ALG_ST_EXCL;
	cptr->alg.ixt_e_keylen=cptr->alg.ixt_common.ixt_support.ias_keymaxbits/8;
	cptr->alg.ixt_e_ctx_size = 0;
	cptr->alg.ixt_common.ixt_support.ias_exttype = IPSEC_ALG_TYPE_ENCRYPT;
	cptr->alg.ixt_e_new_key = _capi_new_key;
	cptr->alg.ixt_e_destroy_key = _capi_destroy_key;
	cptr->alg.ixt_e_cbc_encrypt = _capi_cbc_encrypt;
	cptr->alg.ixt_common.ixt_data = cptr;

	ret=register_ipsec_alg_enc(&cptr->alg);
	printk(KERN_INFO "KLIPS cryptoapi interface: " 
			"alg_type=%d alg_id=%d name=%s "
			"keyminbits=%d keymaxbits=%d, %s(%d)\n", 
				cptr->alg.ixt_common.ixt_support.ias_exttype, 
				cptr->alg.ixt_common.ixt_support.ias_id, 
				cptr->alg.ixt_common.ixt_name, 
				cptr->alg.ixt_common.ixt_support.ias_keyminbits,
				cptr->alg.ixt_common.ixt_support.ias_keymaxbits,
	       ret ? "not found" : "found", ret);
	return ret;
}
/*
 * 	called in ipsec_sa_wipe() time, will destroy key contexts
 * 	and do 1 unbind()
 */
static void 
_capi_destroy_key (struct ipsec_alg_enc *alg, __u8 *key_e)
{
	struct crypto_tfm *tfm=(struct crypto_tfm*)key_e;
	
	if (debug_crypto > 0)
		printk(KERN_DEBUG "klips_debug: _capi_destroy_key:"
				"name=%s key_e=%p \n",
				alg->ixt_common.ixt_name, key_e);
	if (!key_e) {
		printk(KERN_ERR "klips_debug: _capi_destroy_key:"
				"name=%s NULL key_e!\n",
				alg->ixt_common.ixt_name);
		return;
	}
	crypto_free_tfm(tfm);
}
	
/*
 * 	create new key context, need alg->ixt_data to know which
 * 	(of many) cipher inside this module is the target
 */
static __u8 *
_capi_new_key (struct ipsec_alg_enc *alg, const __u8 *key, size_t keylen)
{
	struct ipsec_alg_capi_cipher *cptr;
	struct crypto_tfm *tfm=NULL;

	cptr = alg->ixt_common.ixt_data;
	if (!cptr) {
		printk(KERN_ERR "_capi_new_key(): "
				"NULL ixt_data (?!) for \"%s\" algo\n" 
				, alg->ixt_common.ixt_name);
		goto err;
	}
	if (debug_crypto > 0)
		printk(KERN_DEBUG "klips_debug:_capi_new_key:"
				"name=%s cptr=%p key=%p keysize=%d\n",
				alg->ixt_common.ixt_name, cptr, key, keylen);
	
	/*	
	 *	alloc tfm
	 */
	tfm = crypto_alloc_tfm(cptr->ciphername, CRYPTO_TFM_MODE_CBC);
	if (!tfm) {
		printk(KERN_ERR "_capi_new_key(): "
				"NULL tfm for \"%s\" cryptoapi (\"%s\") algo\n" 
			, alg->ixt_common.ixt_name, cptr->ciphername);
		goto err;
	}
	if (crypto_cipher_setkey(tfm, key, keylen) < 0) {
		printk(KERN_ERR "_capi_new_key(): "
				"failed new_key() for \"%s\" cryptoapi algo (keylen=%d)\n" 
			, alg->ixt_common.ixt_name, keylen);
		crypto_free_tfm(tfm);
		tfm=NULL;
	}
err:
	if (debug_crypto > 0)
		printk(KERN_DEBUG "klips_debug:_capi_new_key:"
				"name=%s key=%p keylen=%d tfm=%p\n",
				alg->ixt_common.ixt_name, key, keylen, tfm);
	return (__u8 *) tfm;
}
/*
 * 	core encryption function: will use cx->ci to call actual cipher's
 * 	cbc function
 */
static int 
_capi_cbc_encrypt(struct ipsec_alg_enc *alg, __u8 * key_e, __u8 * in, int ilen, const __u8 * iv, int encrypt) {
	int error =0;
	struct crypto_tfm *tfm=(struct crypto_tfm *)key_e;
	struct scatterlist sg = { 
		.page = virt_to_page(in),
		.offset = (unsigned long)(in) % PAGE_SIZE,
		.length=ilen,
	};
	if (debug_crypto > 1)
		printk(KERN_DEBUG "klips_debug:_capi_cbc_encrypt:"
				"key_e=%p "
				"in=%p out=%p ilen=%d iv=%p encrypt=%d\n"
				, key_e
				, in, in, ilen, iv, encrypt);
	crypto_cipher_set_iv(tfm, iv, crypto_tfm_alg_ivsize(tfm));
	if (encrypt)
		error = crypto_cipher_encrypt (tfm, &sg, &sg, ilen);
	else
		error = crypto_cipher_decrypt (tfm, &sg, &sg, ilen);
	if (debug_crypto > 1)
		printk(KERN_DEBUG "klips_debug:_capi_cbc_encrypt:"
				"error=%d\n"
				, error);
	return (error<0)? error : ilen;
}
/*
 * 	main initialization loop: for each cipher in list, do
 * 	1) setup cryptoapi cipher else continue
 * 	2) register ipsec_alg object
 */
static int
setup_cipher_list (struct ipsec_alg_capi_cipher* clist) 
{
	struct ipsec_alg_capi_cipher *cptr;
	/* foreach cipher in list ... */
	for (cptr=clist;cptr->ciphername;cptr++) {
		/* 
		 * see if cipher has been disabled (0) or
		 * if noauto set and not enabled (1)
		 */
		if (cptr->parm[0] == 0 || (noauto && cptr->parm[0] < 0)) {
			if (debug_crypto>0)
				printk(KERN_INFO "setup_cipher_list(): "
					"ciphername=%s skipped at user request: "
					"noauto=%d parm[0]=%d parm[1]=%d\n"
					, cptr->ciphername
					, noauto
					, cptr->parm[0]
					, cptr->parm[1]);
			continue;
		}
		/* 
		 * 	use a local ci to avoid touching cptr->ci,
		 * 	if register ipsec_alg success then bind cipher
		 */
		if(cptr->alg.ixt_common.ixt_support.ias_name == NULL) {
		  cptr->alg.ixt_common.ixt_support.ias_name = cptr->ciphername;
		}

		if( setup_cipher(cptr->ciphername) ) {
			if (debug_crypto > 0)
				printk(KERN_DEBUG "klips_debug:"
						"setup_cipher_list():"
						"ciphername=%s found\n"
				, cptr->ciphername);

			if (setup_ipsec_alg_capi_cipher(cptr) != 0) {
				printk(KERN_ERR "klips_debug:"
				       "setup_cipher_list():"
				       "ciphername=%s failed ipsec_alg_register\n"
				       , cptr->ciphername);
			}
		} else {
			printk(KERN_INFO "KLIPS: lookup for ciphername=%s: not found \n",
			       cptr->ciphername);
		}
	}
	return 0;
}
/*
 * 	deregister ipsec_alg objects and unbind ciphers
 */
static int
unsetup_cipher_list (struct ipsec_alg_capi_cipher* clist) 
{
	struct ipsec_alg_capi_cipher *cptr;
	/* foreach cipher in list ... */
	for (cptr=clist;cptr->ciphername;cptr++) {
		if (cptr->alg.ixt_common.ixt_state & IPSEC_ALG_ST_REGISTERED) {
			unregister_ipsec_alg_enc(&cptr->alg);
		}
	}
	return 0;
}
/*
 * 	test loop for registered algos
 */
static int
test_cipher_list (struct ipsec_alg_capi_cipher* clist) 
{
	int test_ret;
	struct ipsec_alg_capi_cipher *cptr;
	/* foreach cipher in list ... */
	for (cptr=clist;cptr->ciphername;cptr++) {
		if (cptr->alg.ixt_common.ixt_state & IPSEC_ALG_ST_REGISTERED) {
			test_ret=ipsec_alg_test(
					cptr->alg.ixt_common.ixt_support.ias_exttype,
					cptr->alg.ixt_common.ixt_support.ias_id, 
					test_crypto);
			printk("test_cipher_list(alg_type=%d alg_id=%d): test_ret=%d\n", 
			       cptr->alg.ixt_common.ixt_support.ias_exttype, 
			       cptr->alg.ixt_common.ixt_support.ias_id,
			       test_ret);
		}
	}
	return 0;
}

IPSEC_ALG_MODULE_INIT_STATIC( ipsec_cryptoapi_init )
{
	int ret, test_ret;
	if ((ret=setup_cipher_list(alg_capi_carray)) < 0)
		return  -EPROTONOSUPPORT;
	if (ret==0 && test_crypto) {
		test_ret=test_cipher_list(alg_capi_carray);
	}
	return ret;
}
IPSEC_ALG_MODULE_EXIT_STATIC( ipsec_cryptoapi_fini )
{
	unsetup_cipher_list(alg_capi_carray);
	return;
}
#ifdef MODULE_LICENSE
MODULE_LICENSE("GPL");
#endif

#endif /* NO_CRYPTOAPI_SUPPORT */
