/*-
 *
 * VCL module (vmod) providing some hash / message digest functions
 *
 * Copyright (c) 2012 UPLEX, Nils Goroll
 * All rights reserved.
 *
 * initially based upon vmod_std.c by Poul-Henning Kamp <phk@phk.freebsd.dk>
 *
 * Author: Nils Goroll <slink@uplex.de>
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY AUTHOR AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL AUTHOR OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 *
 */
#include "config.h"

#include "varnishapi.h"
#include "vmod_digest.h"

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <inttypes.h>

#include "vrt.h"
#include "vsha256.h"
#include "vmd5.h"

#define DIGEST_WS(sp) (sp)->http->ws

/*
 * common structure to keep results from all digests
 *
 * result must get filled up from lowest to highest bits
 * thinking big-endian (rightmost)
 * 
 */

#define DIGEST_RESULT_SIZE 32
#define DIGEST_RESULT_SIZE_UINT32 (DIGEST_RESULT_SIZE *	\
	sizeof(unsigned char) / sizeof(uint32_t))

struct digest_data {
	unsigned		magic;
#define DIGEST_MAGIC		0x0d12a0b2
	enum digest_type	type;
	void 			*state;
	size_t			state_size;

	union {
		unsigned char	uchar[DIGEST_RESULT_SIZE];
		uint32_t	uint32[DIGEST_RESULT_SIZE_UINT32];
	} result;
	size_t			result_size;

	/* how to free state and myself */
	vmod_priv_free_f	*free;
};

/* set to the largest context of all hashes */

#if 0 /* (sizeof(MD5_CTX) > sizeof(struct SHA256Context)) */
#define DIGEST_DATA_SIZE				\
	(sizeof(struct digest_data) + sizeof(MD5_CTX))
#else
#define DIGEST_DATA_SIZE						\
	(sizeof(struct digest_data) + sizeof(struct SHA256Context))
#endif

/* set to the largest block size of all hashes */

#define DIGEST_BLOCK_SIZE	64

/*
 * space will be allocated from DIGEST_WS(sp), unless buf is passed,
 * which must be at least of size DIGEST_DATA_SIZE
 * 
 */
static struct digest_data *
digest_init(const enum digest_type type, struct sess *sp, void *bufp)
{
	struct digest_data	*data;
	size_t 			sz;
	char			*buf = bufp;

	if (buf) {
		data = (void *)buf;
		buf += sizeof(struct digest_data);
	} else {
		CHECK_OBJ_NOTNULL(sp, SESS_MAGIC);
		data = (void *)WS_Alloc(DIGEST_WS(sp), sizeof(struct digest_data));
	}

	AN(data);
	memset(data, 0, sizeof(struct digest_data));

	switch (type) {
	case DEFAULT:
	case CRC32:
	case PLAIN:
		data->result_size = sizeof(uint32_t);
		/* no data->state */
		goto done;
	case RS:
		sz = sizeof(uint32_t);		
		data->result_size = sz;
		goto alloc_state;
	case SHA256:
		sz = sizeof(struct SHA256Context);
		data->result_size = 32;
		goto alloc_state;
	case MD5:
		sz = sizeof(MD5_CTX);
		data->result_size = MD5_DIGEST_LENGTH;
		goto alloc_state;
	default:
		INCOMPL();
		return (0);
	}

  alloc_state:
	if (buf) {			
		data->state = (void *)buf;
		buf += sz;
	} else {
		data->state = WS_Alloc(DIGEST_WS(sp), sz);
	}
	AN(data->state);
	memset(data->state, 0, sz);
	data->state_size = sz;
	
  done:
	data->magic = DIGEST_MAGIC;
	return (data);
}


static void
digest_free(struct digest_data *d)
{
	AN(d);

	/* zero out the internal state, if any */
	if (d->state && d->state_size)
		memset(d->state, 0, d->state_size);

	if (!d->free)
		return;

	if (d->state)
		d->free(d->state);
	
	d->free(d);
}

#define VA_VSTRING_FOREACH(p, s, ap)					\
	for (p = s;							\
	     p != vrt_magic_string_end;					\
	     p = ap ? va_arg(ap, const char *) : vrt_magic_string_end)	\
		if (p)

/*
 * digest: run varargs trough digest alg and return digest_data
 *
 *
 * data must have been intialized with digest_init
 *
 * We store the uint32_t based digest in network byte order, to allow using
 * these checksums cross-platform (and big endian is "network byte order", so
 * this seems most natural).
 *
 * see also: http://www.boost.org/doc/libs/1_39_0/libs/crc/crc.html:
 *
 * Note that the CRC has to be stored with the more-significant bytes first
 * (big-endian).
 *
 * checked with http://www.lammertbies.nl/comm/info/crc-calculation.html
 *		http://www.pvlteam.com/doc/crc/testonline.aspx
 */

static struct digest_data *
digest(const enum digest_type type, struct digest_data *data,
       const char *s,  va_list ap)
{
	const char *p;
	uint32_t res;

	CHECK_OBJ_NOTNULL(data, DIGEST_MAGIC);

	switch (type) {
	case DEFAULT:
	case CRC32:
		res = ntohl(data->result.uint32[0] ^ ~0U);

		VA_VSTRING_FOREACH(p, s, ap)
		    res = crc32(res, p, strlen(p));

		data->result.uint32[0] = htonl(res ^ ~0U);
		break;
	case PLAIN:
		res = ntohl(data->result.uint32[0]);

		VA_VSTRING_FOREACH(p, s, ap)
		    while (*p)
			    res += *p++;

		data->result.uint32[0] = htonl(res);
		break;
	case RS:
	{
		/* hash function from Robert Sedgwicks 'Algorithms in C' book */
		const uint32_t b    = 378551;
		uint32_t a	    = 63689;

		res = ntohl(data->result.uint32[0]);

		VA_VSTRING_FOREACH(p, s, ap)
		    while (*p) {
			    res = res * a + (*p++);
			    a *= b;
		    }

		data->result.uint32[0] = htonl(res);
		break;
	}
	case SHA256:
	{
		struct SHA256Context *sha256 = data->state;

		SHA256_Init(sha256);
		VA_VSTRING_FOREACH(p, s, ap)
		    SHA256_Update(sha256, p, strlen(p));
		SHA256_Final(data->result.uchar, sha256);
		break;
	}
	case MD5:
	{
		MD5_CTX *md5 = data->state;

		MD5Init(md5);
		VA_VSTRING_FOREACH(p, s, ap)
		    MD5Update(md5, p, strlen(p));
		MD5Final(data->result.uchar, md5);
		break;
	}
	default:
		INCOMPL();
		return (0);
	}
	return (data);
}

static size_t
hmac_digest_block_size(const enum digest_type type)
{
	switch (type) {
	case DEFAULT:
	case CRC32:
	case PLAIN:
	case RS:
		/* invalid for hmac */
		return (0);
	case SHA256:
	case MD5:
		/* XXX add macros to the digest impl to use here */
		return (64);
	default:
		INCOMPL();
		return (0);
	}
}


/* 
 * Implementation based upon http://en.wikipedia.org/wiki/HMAC
 * 
 * if key_l = 0, key_l will be initialized to strlen(key)
 * (which implies that key can only be binary if key_l is given)
 */

static struct digest_data *
hmac(const enum digest_type type, struct digest_data *data,
     size_t key_l, const char *key, const char *s, va_list ap)
{
	/*
	 * we need one temporary hash internally, which we're going to
	 * reuse, to:
	 * - optionally hash the key if it is longer than the block size
	 * - calculate the inner hash
	 */

	char			buf[DIGEST_DATA_SIZE];
	struct digest_data 	*tmp;

	unsigned char		i_key_pad[DIGEST_BLOCK_SIZE+1];
	unsigned char		o_key_pad[DIGEST_BLOCK_SIZE+1];

	size_t			blocksize = hmac_digest_block_size(type);

	int			i;
	const char     		*p;

	CHECK_OBJ_NOTNULL(data, DIGEST_MAGIC);

	tmp = NULL;

	if (blocksize == 0) {
		INCOMPL();
		return (NULL);
	}

	if (key_l == 0) {
		key_l = strlen(key);
		if (key_l == 0)
			return NULL;
	}
	/*
	 *  if (length(key) > blocksize) then
	 *    key = hash(key) // keys longer than blocksize are shortened
	 */
	if (key_l > blocksize) {		
		tmp = digest_init(type, NULL, buf);
		/* calling the hashes directly as long as digest() handles strings only */
		switch (type) {
		case SHA256:
		{
			struct SHA256Context *sha256 = tmp->state;

			SHA256_Init(sha256);
			SHA256_Update(sha256, key, key_l);
			SHA256_Final(tmp->result.uchar, sha256);
			break;
		}
		case MD5:
		{
			MD5_CTX *md5 = tmp->state;

			MD5Init(md5);
			MD5Update(md5, key, key_l);
			MD5Final(tmp->result.uchar, md5);
			break;
		}
		default:
			INCOMPL();
			return NULL;
		}
		key = (const char *)tmp->result.uchar;
		key_l = tmp->result_size;
	}

	assert(key_l <= blocksize);

	/* implicit in the following code:
	 *  if (length(key) < blocksize) then
	 *    key = key || zeroes(blocksize - length(key))
	 *
	 * o_key_pad = [0x5c * blocksize] ⊕ key
	 * i_key_pad = [0x36 * blocksize] ⊕ key
	 */

	for (i = 0; i < key_l; i++) {
		o_key_pad[i] = 0x5c ^ key[i];
		i_key_pad[i] = 0x36 ^ key[i];
	}

	/* PAD... probably not worth calling memset */
	while (i < blocksize) {
		o_key_pad[i] = 0x5c;
		i_key_pad[i] = 0x36;
		i++;
	}

	/* 
	 * At this point, tmp is free for reuse as the inner hash
	 *
	 * For completeness, call free, but as we've alloc'ed from buf, this
	 * should be a no-op (except for nulling)
	 */
	if (tmp)
		digest_free(tmp);

	/* 
	 * return hash(o_key_pad || hash(i_key_pad || message))
	 *
	 * generate the inner hash and the final hash
	 */
	 
	tmp = digest_init(type, NULL, buf);

	switch (type) {
	case SHA256:
	{
		struct SHA256Context *inner = tmp->state;
		struct SHA256Context *outer = data->state;

		/* inner */
		SHA256_Init(inner);
		SHA256_Update(inner, i_key_pad, blocksize);
		VA_VSTRING_FOREACH(p, s, ap)
		    SHA256_Update(inner, p, strlen(p));
		SHA256_Final(tmp->result.uchar, inner);

		/* outer */
		SHA256_Init(outer);
		SHA256_Update(outer, o_key_pad, blocksize);
		SHA256_Update(outer, tmp->result.uchar, tmp->result_size);
		SHA256_Final(data->result.uchar, outer);

		break;
	}
	case MD5:
	{
		MD5_CTX *inner = tmp->state;
		MD5_CTX *outer = data->state;

		/* inner */
		MD5Init(inner);
		MD5Update(inner, i_key_pad, blocksize);
		VA_VSTRING_FOREACH(p, s, ap)
		    MD5Update(inner, p, strlen(p));
		MD5Final(tmp->result.uchar, inner);

		/* outer */
		MD5Init(outer);
		MD5Update(outer, o_key_pad, blocksize);
		MD5Update(outer, tmp->result.uchar, tmp->result_size);
		MD5Final(data->result.uchar, outer);

		break;
	}
	default:
		INCOMPL();
		return (0);
	}

	return (data);
}

static inline char
hexnibble(char c)
{
	return ((c > 9) ? ('A' + (c - 0xa)) : ('0' + c));
}

static inline char
hexnibblelc(char c)
{
	return ((c > 9) ? ('a' + (c - 0xa)) : ('0' + c));
}

/* get size for specified encoding */

static inline size_t
encode_l(const enum encoding_type enc, size_t src_size)
{
	switch (enc) {
	case BASE64:
		return (base64_encode_l(src_size));
	case HEX:
	case HEXLC:
		return (src_size * 2 + 1);
	default:
		INCOMPL();
		return (0);
	}
}

/* encode data with the specified encoding */

static char *
encode(const enum encoding_type enc, char *dst, size_t dst_size,
	char *src, size_t src_size)
{
	char	*p;
	int	i;

	switch (enc) {
	case BASE64:
		base64_encode(dst, dst_size, src, src_size);
		break;
	case HEX:
		for (i = 0, p = dst; i < src_size; i++, p += 2) {
			/* sprintf(p, "%02X", src[i]); */
			*p = hexnibble((src[i] & 0xf0) >> 4);
			*(p+1) = hexnibble(src[i] & 0x0f);
		}
		*p = '\0';
		break;
	case HEXLC:
		for (i = 0, p = dst; i < src_size; i++, p += 2) {
			/* sprintf(p, "%02x", src[i]); */
			*p = hexnibblelc((src[i] & 0xf0) >> 4);
			*(p+1) = hexnibblelc(src[i] & 0x0f);
		}
		*p = '\0';
		break;
	default:
		INCOMPL();
		return (NULL);
	}
	return (dst);
}


/*
 * digest_string, encode_string:
 *
 * Functions called from auto-generated vmod wrappers (see vmod_digest.py)
 */
 

char *
digest_string(const enum digest_type type, const enum encoding_type enc,
	      struct sess *sp, const char *s, va_list ap)
{
	/* alloc scratch for digest_data on stack */
	char			buf[DIGEST_DATA_SIZE];
	struct digest_data 	*data = digest_init(type, sp, buf);
	char			*res;
	size_t			l;

	AN(data);
	digest(type, data, s, ap);
	l = encode_l(enc, data->result_size);
	res = WS_Alloc(DIGEST_WS(sp), l);
	if (!res)
		return (NULL);
	if (!encode(enc, res, l, (char *)data->result.uchar, data->result_size))
		return (NULL);

	/*
	 * not strictly needed because alloc'd from stack, calling free for
	 * completeness 
	 */
	digest_free(data);

	return (res);
}

#define hexnibbleval(n, d, errjmp)			\
	if (((n) >= '0') && ((n) <= '9'))		\
		(d) = (n) - '0';			\
	else if (((n) >= 'a') && ((n) <= 'f'))		\
		(d) = (n) - 'a' + 0xa;			\
	else if (((n) >= 'A') && ((n) <= 'F'))		\
		(d) = (n) - 'A' + 0xa;			\
	else						\
		goto errjmp



char *
hmac_string(const enum hmac_type hmac_type, const enum encoding_type enc,
	struct sess *sp, const char *key, const char *s, va_list ap)
{
	/* alloc scratch for digest_data on stack */
	char			buf[DIGEST_DATA_SIZE];
	struct digest_data 	*data;
	char			*res;
	size_t			l;


	enum digest_type	digest_type;

	AN(key);

	switch(hmac_type) {
	case HMAC_SHA256BK:
	case HMAC_SHA256:
		digest_type = SHA256;
		break;
	case HMAC_MD5BK:
	case HMAC_MD5:
		digest_type = MD5;
		break;
	default:
		INCOMPL();
		return NULL;
	}

	data = digest_init(digest_type, sp, buf);
	AN(data);

	if ((hmac_type == HMAC_SHA256BK) || (hmac_type == HMAC_MD5BK)) {
		char			*binkey = NULL;
		size_t			binkey_l = 0;

		const char		*k = key;
		char			vh, vl;
		unsigned		ws_space;

		/* decode the hex-encoded key on WS scratch space */
		binkey = WS_Snapshot(DIGEST_WS(sp));
		ws_space = WS_Reserve(DIGEST_WS(sp), 0);
		
		while(k[0] && k[1]) {
			XXXAZ(binkey_l >= ws_space);
			/*
			  if (binkey_l >= ws_space)
				goto err_nospc;
			*/

			hexnibbleval(k[0], vh, err_nothex);
			hexnibbleval(k[1], vl, err_nothex);
			binkey[binkey_l] = (vh << 4) | vl;
			binkey_l++;
			k+=2;
		}

		/* null key len really does not make sense */
		if (binkey_l == 0)
			goto err_length0;

		hmac(digest_type, data, binkey_l, binkey, s, ap);

		WS_Release(DIGEST_WS(sp), 0);
	} else {
		hmac(digest_type, data, 0, key, s, ap);
	}
	l = encode_l(enc, data->result_size);

	res = WS_Alloc(DIGEST_WS(sp), l);
	if (!res)
		return (NULL);
	if (!encode(enc, res, l, (char *)data->result.uchar, data->result_size))
		return (NULL);

	/*
	 * not strictly needed because alloc'd from stack, calling free for
	 * completeness 
	 */
	digest_free(data);

	return (res);

  err_nospc:
  err_nothex:
  err_length0:
	WS_Release(DIGEST_WS(sp), 0);
	digest_free(data);
	return NULL;
}

char *
encode_string(const enum encoding_type enc,
	      struct sess *sp, const char *s, va_list ap)
{
	const char		*p;
	size_t			l;
	unsigned 		u;
	char			*b, *e;


	/* get memory. skeleton taken from vmoc_std.c:vmod_updown() */
	u = WS_Reserve(DIGEST_WS(sp), 0);
	e = b = DIGEST_WS(sp)->f;
	e += u;

	switch (enc) {
	case BASE64:
		l = base64_encode_string(b, u, vrt_magic_string_end, s, ap);
		if (l < 0)
			return (NULL);
		b += l;
		break;
	case HEX:
	case HEXLC:
		p = s;
		while (p != vrt_magic_string_end && b < e) {
			if (p != NULL) {
				for (; (b < e - 1) && *p != '\0'; p++) {
					*b++ = (enc == HEX) ?
					    hexnibble((*p & 0xf0) >> 4) :
					    hexnibblelc((*p & 0xf0) >> 4);
					*b++ = (enc == HEX) ?
					    hexnibble(*p & 0x0f) :
					    hexnibblelc(*p & 0x0f);
				}
			}
			p = va_arg(ap, const char *);
		}
		if (b < e)
			*b = '\0';
		b++;
		break;
	default:
		INCOMPL();
		return (NULL);
	}

	if (b > e) {
		WS_Release(DIGEST_WS(sp), 0);
		return (NULL);
	} else {
		e = b;
		b = DIGEST_WS(sp)->f;
		WS_Release(DIGEST_WS(sp), e - b);
		return (b);
	}
}

#ifdef HAVE_VMOD
int
init_function(struct vmod_priv *priv, const struct VCL_conf *cfg)
#else
void
VRT_Digest_Init(void)
#endif
{
	base64_init();
	MD5Test();
	
	/* for hexnibble */
	assert(('b' - 'a' == 1) && ('B' - 'A' == 1) && ('2' - '1' == 1));

	/* for digest (result) size */
	assert((DIGEST_RESULT_SIZE >= SHA256_LEN) &&
	    (DIGEST_RESULT_SIZE >= MD5_DIGEST_LENGTH) &&
	    (DIGEST_RESULT_SIZE >= sizeof(uint32_t)));
	
	/* for context size allocation */
	assert((DIGEST_DATA_SIZE >= (sizeof(struct digest_data) + sizeof(struct SHA256Context))) &&
	    (DIGEST_DATA_SIZE >= (sizeof(struct digest_data) + sizeof(struct MD5Context))) &&
	    (DIGEST_DATA_SIZE >= (sizeof(struct digest_data) + sizeof(uint32_t))));

#ifdef HAVE_VMOD
	return (0);
#endif
}
