/* Prosody IM
-- Copyright (C) 2009-2010 Matthew Wild
-- Copyright (C) 2009-2010 Waqas Hussain
--
-- This project is MIT/X11 licensed. Please see the
-- COPYING file in the source package for more information.
--
*/

/*
* hashes.c
* Lua library for sha1, sha256 and md5 hashes
*/

#include <string.h>
#include <stdlib.h>

#ifdef _MSC_VER
typedef unsigned __int32 uint32_t;
#else
#include <inttypes.h>
#endif

#include "lua.h"
#include "lauxlib.h"
#include <openssl/crypto.h>
#include <openssl/sha.h>
#include <openssl/md5.h>
#include <openssl/hmac.h>

#if (LUA_VERSION_NUM == 501)
#define luaL_setfuncs(L, R, N) luaL_register(L, NULL, R)
#endif

#define HMAC_IPAD 0x36363636
#define HMAC_OPAD 0x5c5c5c5c

const char *hex_tab = "0123456789abcdef";
void toHex(const unsigned char *in, int length, unsigned char *out) {
	int i;

	for(i = 0; i < length; i++) {
		out[i * 2] = hex_tab[(in[i] >> 4) & 0xF];
		out[i * 2 + 1] = hex_tab[(in[i]) & 0xF];
	}
}

#define MAKE_HASH_FUNCTION(myFunc, func, size) \
static int myFunc(lua_State *L) { \
	size_t len; \
	const char *s = luaL_checklstring(L, 1, &len); \
	int hex_out = lua_toboolean(L, 2); \
	unsigned char hash[size], result[size*2]; \
	func((const unsigned char*)s, len, hash);  \
	if (hex_out) { \
		toHex(hash, size, result); \
		lua_pushlstring(L, (char*)result, size*2); \
	} else { \
		lua_pushlstring(L, (char*)hash, size);\
	} \
	return 1; \
}

MAKE_HASH_FUNCTION(Lsha1, SHA1, SHA_DIGEST_LENGTH)
MAKE_HASH_FUNCTION(Lsha224, SHA224, SHA224_DIGEST_LENGTH)
MAKE_HASH_FUNCTION(Lsha256, SHA256, SHA256_DIGEST_LENGTH)
MAKE_HASH_FUNCTION(Lsha384, SHA384, SHA384_DIGEST_LENGTH)
MAKE_HASH_FUNCTION(Lsha512, SHA512, SHA512_DIGEST_LENGTH)
MAKE_HASH_FUNCTION(Lmd5, MD5, MD5_DIGEST_LENGTH)

struct hash_desc {
	int (*Init)(void *);
	int (*Update)(void *, const void *, size_t);
	int (*Final)(unsigned char *, void *);
	size_t digestLength;
	void *ctx, *ctxo;
};

static void hmac(struct hash_desc *desc, const char *key, size_t key_len,
                 const char *msg, size_t msg_len, unsigned char *result) {
	union xory {
		unsigned char bytes[64];
		uint32_t quadbytes[16];
	};

	int i;
	unsigned char hashedKey[64]; /* Maximum used digest length */
	union xory k_ipad, k_opad;

	if(key_len > 64) {
		desc->Init(desc->ctx);
		desc->Update(desc->ctx, key, key_len);
		desc->Final(hashedKey, desc->ctx);
		key = (const char *)hashedKey;
		key_len = desc->digestLength;
	}

	memcpy(k_ipad.bytes, key, key_len);
	memset(k_ipad.bytes + key_len, 0, 64 - key_len);
	memcpy(k_opad.bytes, k_ipad.bytes, 64);

	for(i = 0; i < 16; i++) {
		k_ipad.quadbytes[i] ^= HMAC_IPAD;
		k_opad.quadbytes[i] ^= HMAC_OPAD;
	}

	desc->Init(desc->ctx);
	desc->Update(desc->ctx, k_ipad.bytes, 64);
	desc->Init(desc->ctxo);
	desc->Update(desc->ctxo, k_opad.bytes, 64);
	desc->Update(desc->ctx, msg, msg_len);
	desc->Final(result, desc->ctx);
	desc->Update(desc->ctxo, result, desc->digestLength);
	desc->Final(result, desc->ctxo);
}

#define MAKE_HMAC_FUNCTION(myFunc, evp, size, type) \
static int myFunc(lua_State *L) { \
	unsigned char hash[size], result[2*size]; \
	size_t key_len, msg_len; \
	unsigned int out_len; \
	const char *key = luaL_checklstring(L, 1, &key_len); \
	const char *msg = luaL_checklstring(L, 2, &msg_len); \
	const int hex_out = lua_toboolean(L, 3); \
	HMAC(evp(), key, key_len, (const unsigned char*)msg, msg_len, (unsigned char*)hash, &out_len); \
	if (hex_out) { \
		toHex(hash, out_len, result); \
		lua_pushlstring(L, (char*)result, out_len*2); \
	} else { \
		lua_pushlstring(L, (char*)hash, out_len); \
	} \
	return 1; \
}

MAKE_HMAC_FUNCTION(Lhmac_sha1, EVP_sha1, SHA_DIGEST_LENGTH, SHA_CTX)
MAKE_HMAC_FUNCTION(Lhmac_sha256, EVP_sha256, SHA256_DIGEST_LENGTH, SHA256_CTX)
MAKE_HMAC_FUNCTION(Lhmac_sha512, EVP_sha512, SHA512_DIGEST_LENGTH, SHA512_CTX)
MAKE_HMAC_FUNCTION(Lhmac_md5, EVP_md5, MD5_DIGEST_LENGTH, MD5_CTX)

static int LscramHi(lua_State *L) {
	union xory {
		unsigned char bytes[SHA_DIGEST_LENGTH];
		uint32_t quadbytes[SHA_DIGEST_LENGTH / 4];
	};
	int i;
	SHA_CTX ctx, ctxo;
	unsigned char Ust[SHA_DIGEST_LENGTH];
	union xory Und;
	union xory res;
	size_t str_len, salt_len;
	struct hash_desc desc;
	const char *str = luaL_checklstring(L, 1, &str_len);
	const char *salt = luaL_checklstring(L, 2, &salt_len);
	char *salt2;
	const int iter = luaL_checkinteger(L, 3);

	desc.Init = (int (*)(void *))SHA1_Init;
	desc.Update = (int (*)(void *, const void *, size_t))SHA1_Update;
	desc.Final = (int (*)(unsigned char *, void *))SHA1_Final;
	desc.digestLength = SHA_DIGEST_LENGTH;
	desc.ctx = &ctx;
	desc.ctxo = &ctxo;

	salt2 = malloc(salt_len + 4);

	if(salt2 == NULL) {
		return luaL_error(L, "Out of memory in scramHi");
	}

	memcpy(salt2, salt, salt_len);
	memcpy(salt2 + salt_len, "\0\0\0\1", 4);
	hmac(&desc, str, str_len, salt2, salt_len + 4, Ust);
	free(salt2);

	memcpy(res.bytes, Ust, sizeof(res));

	for(i = 1; i < iter; i++) {
		int j;
		hmac(&desc, str, str_len, (char *)Ust, sizeof(Ust), Und.bytes);

		for(j = 0; j < SHA_DIGEST_LENGTH / 4; j++) {
			res.quadbytes[j] ^= Und.quadbytes[j];
		}

		memcpy(Ust, Und.bytes, sizeof(Ust));
	}

	lua_pushlstring(L, (char *)res.bytes, SHA_DIGEST_LENGTH);

	return 1;
}

static int Lhash_equals(lua_State *L) {
	size_t len1, len2;
	const char *s1 = luaL_checklstring(L, 1, &len1);
	const char *s2 = luaL_checklstring(L, 2, &len2);
	if(len1 == len2) {
		lua_pushboolean(L, CRYPTO_memcmp(s1, s2, len1) == 0);
	} else {
		lua_pushboolean(L, 0);
	}
	return 1;
}

static const luaL_Reg Reg[] = {
	{ "sha1",		Lsha1		},
	{ "sha224",		Lsha224		},
	{ "sha256",		Lsha256		},
	{ "sha384",		Lsha384		},
	{ "sha512",		Lsha512		},
	{ "md5",		Lmd5		},
	{ "hmac_sha1",		Lhmac_sha1	},
	{ "hmac_sha256",	Lhmac_sha256	},
	{ "hmac_sha512",	Lhmac_sha512	},
	{ "hmac_md5",		Lhmac_md5	},
	{ "scram_Hi_sha1",	LscramHi	},
	{ "equals",             Lhash_equals    },
	{ NULL,			NULL		}
};

LUALIB_API int luaopen_util_hashes(lua_State *L) {
#if (LUA_VERSION_NUM > 501)
	luaL_checkversion(L);
#endif
	lua_newtable(L);
	luaL_setfuncs(L, Reg, 0);;
	lua_pushliteral(L, "-3.14");
	lua_setfield(L, -2, "version");
	return 1;
}
