/* $USAGI: calc_hmac.c,v 1.1 2001/12/23 16:01:12 mk Exp $ */

#define __NO_VERSION__
#include <linux/module.h>
#include <linux/version.h>
#ifdef MODVERSIONS
#include <linux/modversions.h>
#endif
#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/crypto.h>
#include <linux/calc_hmac.h>

#include <net/sadb.h>

/*
   Now it support algorithms MD5 and SHA1.
   It digest size is 64 byte. See RFC2401

   It should be a valiable value in ipsec_sa or digest context.


*/
#define DIGEST_SIZE 64


int calc_hmac(struct ipsec_sa *ipsec_sa, __u8 *in, int size, __u8 *hmac)
{
	int i = 0;
	int error = 0;
	int blocksize = 0;
	struct digest_implementation *di = NULL;
	int key_len = 0;
	__u8 *key = NULL;
	__u8 k_ipad[DIGEST_SIZE+1];    /* inner padding - key XORd with ipad */
	__u8 k_opad[DIGEST_SIZE+1];    /* outer padding - key XORd with opad */
	__u8 *tk = NULL;


	if(!(ipsec_sa&&in&&hmac)){
		printk(KERN_ERR "%s: some parameter is null\n",__FUNCTION__);
		error = -EINVAL;
		goto err;
	}

	if(!(di = ipsec_sa->auth_algo.cx.di)){
		printk(KERN_ERR "%s: digest_implementation is null\n",__FUNCTION__);
		error = -EINVAL;
		goto err;
	}

	blocksize = di->blocksize;
	key_len = ipsec_sa->auth_algo.key_len;
	key = ipsec_sa->auth_algo.key;

	/* if key is longer than 64 bytes reset it to key=H(key) */
	if (key_len > DIGEST_SIZE) {

		tk = kmalloc(blocksize, GFP_KERNEL);
		if(!tk){
			printk(KERN_ERR "%s: tk is not allocated\n", __FUNCTION__);
			error = -ENOMEM;
			goto err;
		}

		memset(tk, 0, blocksize);

		di->open(&(ipsec_sa->auth_algo.cx));
		di->update(&(ipsec_sa->auth_algo.cx), key, key_len);
		di->close(&(ipsec_sa->auth_algo.cx), tk);
		key = tk;
		key_len = blocksize;

		kfree(tk);
	}

	/*
	 * the HMAC_MD5 transform looks like:
	 *
	 * H(K XOR opad, H(K XOR ipad, text))
	 *
	 * where K is an n byte key
	 * ipad is the byte 0x36 repeated 64 times
	 * opad is the byte 0x5c repeated 64 times
	 * and text is the data being protected
	 */

	/* start out by storing key in pads */
	memset(k_ipad,0,sizeof(k_ipad));
	memset(k_opad,0,sizeof(k_opad));
	memcpy(k_ipad,key,key_len);
	memcpy(k_opad,key,key_len);

	/* XOR key with ipad and opad values */
	for (i=0; i<DIGEST_SIZE; i++) {
		k_ipad[i] ^= 0x36;
		k_opad[i] ^= 0x5c;
	}

	/*
	 * perform inner
	 */
	di->open(&(ipsec_sa->auth_algo.cx));
	di->update(&(ipsec_sa->auth_algo.cx), k_ipad, DIGEST_SIZE);
	di->update(&(ipsec_sa->auth_algo.cx), in, size);
	di->close(&(ipsec_sa->auth_algo.cx), hmac);

	/*
	 * perform outer
	 */
	di->open(&(ipsec_sa->auth_algo.cx));
	di->update(&(ipsec_sa->auth_algo.cx), k_opad, DIGEST_SIZE);
	di->update(&(ipsec_sa->auth_algo.cx), hmac, blocksize);
	di->close(&(ipsec_sa->auth_algo.cx), hmac);

err:
	return error;

}

