////////////////////////////
//
// Security functions
// (c)2006 Jeremy Wilkins All Rights Reserved
//
// Implements security for every application
// SHA implemntation is based on javascript SHA256
// written by Angel Marin (from code by Paul Johnston's
// SHA-1 implementation) for compatibility
//
// Encrypt class is based on code by A J Marston with
// many bug fixes and feature additions
//

var chrsz   = 8;			// bits per input character. 8 - ASCII; 16 - Unicode
var hexcase = 0;			// hex output format. 0 - lowercase; 1 - uppercase
var session_scramble2 = '';	// session scrambled base 64 translation text
var session_key = '';		// session encryption key

// Crypto class

// Crypto class constructor
function Crypto()
{
	this.errors = new Array();
	// Each of these two strings must contain the same characters, but in a different order.
	// Use only base 64 encoded characters from the ASCII table.
	// Each character can only appear once in each string.

	this.scramble1 = '=ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/';

	if (session_scramble2)
	{
		this.scramble2 = session_scramble2;
	}
	else
	{
		this.scramblekey();
	}

	if (this.scramble1.length != this.scramble2.length)
	{
		this.errors.push('** SCRAMBLE1 is not same length as SCRAMBLE2 **');
	}

	this.adj = 1.75;  // this value is added to the rolling fudgefactors
	this.mod = 3;     // if divisible by this the adjustment is made negative
}

Crypto.prototype.scramble1 = '';
Crypto.prototype.scramble2 = '';
Crypto.prototype.errors = false;
Crypto.prototype.adj = 0;
Crypto.prototype.mod = 0;

////////////////////////////
// create a new scrambled key
Crypto.prototype.scramblekey =
	function ()
	{
		var notscrambled = true;
		var temp;
		var pos1;
		var pos2;
		var strstart;
		var strmiddle;
		var strend;

		this.scramble2 = this.scramble1;

		// loop while not fully scrambled
		while (notscrambled)
		{
			// get first random position
			pos1 = Math.round((Math.random() % 1) * this.scramble1.length);
			pos2 = pos1;
			// get the second random position and make sure it
			// is different than the first
			while (pos2 == pos1)
			{
				pos2 = Math.round((Math.random() % 1) * this.scramble1.length);
			}
			// if the first position is greater than the second
			// swap them.  The first position MUST be smaller
			// for all remaining code to work
			if (pos1 > pos2)
			{
				temp = pos1;
				pos1 = pos2;
				pos2 = temp;
			}
			strstart = '';
			strmiddle = '';
			strend = '';
			// if the first position is not at the beginning of
			// the scramble string, prepend the existing string
			// up to the first position
			if (pos1 > 0)
			{
				strstart = this.scramble2.substr(0, pos1);
			}
			// if the first and second position are not side by side
			// in the scramble string, sandwich the existing string
			// between the first and second positions
			if (pos1 + 1 != pos2)
			{
				strmiddle = this.scramble2.substr(pos1 + 1, pos2 - pos1 - 1);
			}
			// if the second position is not at the end of
			// the scramble string, append the existing string
			// after the second position
			if (pos2 < (this.scramble2.length - 1))
			{
				strend = this.scramble2.substr(pos2 + 1, this.scramble2.length - pos2);
			}
			// swap the characters and reassemble the string
			this.scramble2 = strstart + this.scramble2.substr(pos2, 1) + strmiddle + this.scramble2.substr(pos1, 1) + strend;
			notscrambled = false;
			for (x = 0; x < this.scramble1.length; x++)
			{
				// if any of the characters are still in the original positions
				// we are not done yet
				if (this.scramble1.substr(x, 1) == this.scramble2.substr(x, 1))
				{
					notscrambled = true;
				}
			}
		}
		session_scramble2 = this.scramble2;
	}

////////////////////////////
// decrypt string into its original form
Crypto.prototype.decrypt =
	function (source)
	{
		var target;
		var key;
		var fudgefactor;
		var adj;
		var num1;
		var factor1;
		var factor2;

		key = session_key;
		// convert key into a sequence of numbers
		fudgefactor = this._convertKey(key);
		if (this.errors.length > 0)
		{
			return false;
		}
		if (!source)
		{
			this.errors.push('No value has been supplied for decryption');
			return false;
		}

		target = '';
		factor2 = 0;

		for (var i = 0; i < source.length; i++)
		{
			// extract a character from source
			char2 = source.substr(i, 1);

			// identify its position in scramble2
			num2 = this.scramble2.indexOf(char2);
			if (num2 < 0)
			{
				this.errors.push("Source string contains an invalid character (" + char2 + ")");
				return false;
			}

			// get an adjustment value using fudgefactor
			adj     = this._applyFudgeFactor(fudgefactor);

			factor1 = factor2 + adj;					// accumulate in factor1
			num1    = Math.round(factor1 * -1) + num2;	// generate offset for scramble1
			num1    = this._checkRange(num1);			// check range
			factor2 = factor1 + num2;					// accumulate in factor2

			// extract character from scramble1
			char1 = this.scramble1.substr(num1, 1);

			// append to target string
			target += char1;
		}
		return base64_decode(target);
	}

////////////////////////////
// encrypt string into a garbled form
Crypto.prototype.encrypt =
	function (source)
	{
		var target;
		var key;
		var fudgefactor;
		var adj;
		var num1;
		var factor1;
		var factor2;

		key = session_key;

		// convert key into a sequence of numbers
		fudgefactor = this._convertKey(key);
		if (this.errors.length > 0)
		{
			return false;
		}
		if (!source)
		{
			this.errors.push('No value has been supplied for encryption');
			return false;
		}

		// Base 64 encode data to ensure encryption success
		source = base64_encode(source);

		target = '';
		factor2 = 0;

		for (var i = 0; i < source.length; i++)
		{
			// extract a character from source
			char1 = source.substr(i, 1);

			// identify its position in scramble1
			num1 = this.scramble1.indexOf(char1);
			if (num1 < 0)
			{
				this.errors.push("Source string contains an invalid character (" + char1 + ")");
				return false;
			}

			// get an adjustment value using fudgefactor
			adj     = this._applyFudgeFactor(fudgefactor);
			factor1 = factor2 + adj;					// accumulate in factor1
			num2    = Math.round(factor1) + num1;		// generate offset for scramble2
			num2    = this._checkRange(num2);			// check range
			factor2 = factor1 + num2;					// accumulate in factor2

			// extract character from scramble2
			char2 = this.scramble2.substr(num2, 1);

			// append to target string
			target += char2;
		}
		return target;
	}

////////////////////////////
// return the adjustment value
Crypto.prototype.getAdjustment =
	function ()
	{
		return this.adj;
	}

////////////////////////////
// Return the modulus value
Crypto.prototype.getModulus =
	function ()
	{
		return this.mod;
	}

////////////////////////////
// Set the adjustment value
Crypto.prototype.setAdjustment =
	function (adj)
	{
		this.adj = adj;
	}

////////////////////////////
// Set the modulus value
Crypto.prototype.setModulus =
	function (mod)
	{
		this.mod = Math.abs(mod);		// must be a positive whole number
	}

////////////////////////////
// Private methods

////////////////////////////
// Return an adjustment value based on the contents of fudgefactor
// NOTE: fudgefactor is passed by reference so that it can be modified
Crypto.prototype._applyFudgeFactor =
	function (fudgefactor)
	{
		var fudge;

		fudge = fudgefactor.shift();		// extract 1st number from array
		fudge = fudge + this.adj;			// add in adjustment value
		fudgefactor.push(fudge);			// put it back at end of array

		// if modifier has been supplied
		if (this.mod)
		{
			// if it is divisible by modifier
			if (fudge % this.mod < 1)
			{
				fudge = fudge * -1;           // make it negative
			}
		}
		return fudge;
	}

////////////////////////////
// Check whether the given number is within scramble1's length
Crypto.prototype._checkRange =
	function (num)
	{
		num = Math.round(num);			// round up to nearest whole number

		limit = this.scramble1.length;

		while (num >= limit)
		{
			num -= limit;			// value too high, so reduce it
		}
		while (num < 0)
		{
			num += limit;			// value too low, so increase it
		}
		return num;
	}

////////////////////////////
// convert a key into an array of numbers
Crypto.prototype._convertKey =
	function (key)
	{
		var newarray = new Array();
		var tot;

		if (!key)
		{
			this.errors.push('No value has been supplied for the encryption key');
			return false;
		}

		newarray.push(key.length);		// first entry in array is length of key
		tot = 0;
		for (var i = 0; i < key.length; i++)
		{
			// extract a character from key
			char = key.substr(i, 1);

			// identify its position in scramble1
			num = this.scramble1.indexOf(char);
			if (num < 0)
			{
				this.errors.push("Key contains an invalid character (" + char + ")");
				return false;
			}
			newarray.push(num);		// store in output array
			tot = tot + num;		// accumulate total for later
		}
		newarray.push(tot);            // insert total as last entry in array
		return newarray;
	}
// End Crypto class

function safe_add (x, y)
{
	var lsw = (x & 0xFFFF) + (y & 0xFFFF);
	var msw = (x >> 16) + (y >> 16) + (lsw >> 16);
	return (msw << 16) | (lsw & 0xFFFF);
}

function S (X, n)
{
	return (X >>> n) | (X << (32 - n));
}

function R (X, n)
{
	return (X >>> n);
}

function Ch(x, y, z)
{
	return ((x & y) ^ ((~x) & z));
}

function Maj(x, y, z)
{
	return ((x & y) ^ (x & z) ^ (y & z));
}

function Sigma0256(x)
{
	return (S(x, 2) ^ S(x, 13) ^ S(x, 22));
}

function Sigma1256(x)
{
	return (S(x, 6) ^ S(x, 11) ^ S(x, 25));
}

function Gamma0256(x)
{
	return (S(x, 7) ^ S(x, 18) ^ R(x, 3));
}

function Gamma1256(x)
{
	return (S(x, 17) ^ S(x, 19) ^ R(x, 10));
}

function core_sha256 (m, l)
{
	var K = new Array(0x428A2F98,0x71374491,0xB5C0FBCF,0xE9B5DBA5,0x3956C25B,0x59F111F1,0x923F82A4,0xAB1C5ED5,0xD807AA98,0x12835B01,0x243185BE,0x550C7DC3,0x72BE5D74,0x80DEB1FE,0x9BDC06A7,0xC19BF174,0xE49B69C1,0xEFBE4786,0xFC19DC6,0x240CA1CC,0x2DE92C6F,0x4A7484AA,0x5CB0A9DC,0x76F988DA,0x983E5152,0xA831C66D,0xB00327C8,0xBF597FC7,0xC6E00BF3,0xD5A79147,0x6CA6351,0x14292967,0x27B70A85,0x2E1B2138,0x4D2C6DFC,0x53380D13,0x650A7354,0x766A0ABB,0x81C2C92E,0x92722C85,0xA2BFE8A1,0xA81A664B,0xC24B8B70,0xC76C51A3,0xD192E819,0xD6990624,0xF40E3585,0x106AA070,0x19A4C116,0x1E376C08,0x2748774C,0x34B0BCB5,0x391C0CB3,0x4ED8AA4A,0x5B9CCA4F,0x682E6FF3,0x748F82EE,0x78A5636F,0x84C87814,0x8CC70208,0x90BEFFFA,0xA4506CEB,0xBEF9A3F7,0xC67178F2);
	var HASH = new Array(0x6A09E667, 0xBB67AE85, 0x3C6EF372, 0xA54FF53A, 0x510E527F, 0x9B05688C, 0x1F83D9AB, 0x5BE0CD19);
	var W = new Array(64);
	var a, b, c, d, e, f, g, h, i, j;
	var T1, T2;

	/* append padding */
	m[(l >> 5) + 1] |= 0x80 << (24 - l % 32);
	m[((l + 64 >> 9) << 4) + 16] = l;

	for ( var i = 0; i < m.length - 1; i += 16 )
	{
		a = HASH[0];
		b = HASH[1];
		c = HASH[2];
		d = HASH[3];
		e = HASH[4];
		f = HASH[5];
		g = HASH[6];
		h = HASH[7];

		for (var j = 0; j<64; j++)
		{
			if (j < 16)
			{
				W[j] = m[j + i + 1];
			}
			else
			{
				W[j] = safe_add(safe_add(safe_add(Gamma1256(W[j - 2]), W[j - 7]), Gamma0256(W[j - 15])), W[j - 16]);
			}

			T1 = safe_add(safe_add(safe_add(safe_add(h, Sigma1256(e)), Ch(e, f, g)), K[j]), W[j]);
			T2 = safe_add(Sigma0256(a), Maj(a, b, c));

			h = g;
			g = f;
			f = e;
			e = safe_add(d, T1);
			d = c;
			c = b;
			b = a;
			a = safe_add(T1, T2);
		}

		HASH[0] = safe_add(a, HASH[0]);
		HASH[1] = safe_add(b, HASH[1]);
		HASH[2] = safe_add(c, HASH[2]);
		HASH[3] = safe_add(d, HASH[3]);
		HASH[4] = safe_add(e, HASH[4]);
		HASH[5] = safe_add(f, HASH[5]);
		HASH[6] = safe_add(g, HASH[6]);
		HASH[7] = safe_add(h, HASH[7]);
	}
	HASH.unshift(HASH.length * 4);
	return HASH;
}

function str2binb(str)
{
	var bin = Array();
	var mask = (1 << chrsz) - 1;
	bin[0] = str.length;
	for(var i = 0; i < str.length * chrsz; i += chrsz)
	{
		bin[(i >> 5) + 1] |= (str.charCodeAt(i / chrsz) & mask) << (24 - i % 32);
	}
	return bin;
}

function b642binb(str)
{
	var binarray = Array();
	var tab = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
	var b64pad = "=";
	var num;

	for(var i = 0; i < str.length; i += 3)
	{
		num1 = tab.indexOf(str.substr(i, 1));
		var triplet = (((binarray[(i >> 2) + 1] >> 8 * (3 - i % 4)) & 0xFF) << 16)
					| (((binarray[(i + 1 >> 2) + 1] >> 8 * (3 - (i + 1) % 4)) & 0xFF) << 8)
					| ((binarray[(i + 2 >> 2) + 1] >> 8 * (3 - (i + 2) % 4)) & 0xFF);
		for(var j = 0; j < 4; j++)
		{
			if(i * 8 + j * 6 > binarray[0])
			{
				str += b64pad;
			}
			else
			{
				str += tab.charAt((triplet >> 6 * (3 - j)) & 0x3F);
			}
		}
	}
	return str;
}

function binb2str(bin)
{
	var str = "";
	var mask = (1 << chrsz) - 1;
	for(var i = 0; i < bin[0] * chrsz; i += chrsz)
	{
		str += String.fromCharCode((bin[(i >> 5) + 1] >>> (24 - i % 32)) & mask);
	}
	return str;
}

function binb2hex(binarray)
{
	var hex_tab = hexcase ? "0123456789ABCDEF" : "0123456789abcdef";
	var str = "";
	for(var i = 0; i < binarray[0]; i++)
	{
		str += hex_tab.charAt((binarray[(i >> 2) + 1] >> ((3 - i % 4) * 8 + 4)) & 0xF) +
				hex_tab.charAt((binarray[(i >> 2) + 1] >> ((3 - i % 4) * 8)) & 0xF);
	}
	return str;
}

function binb2b64(binarray)
{
	var tab = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
	var str = "";
	var b64pad = "=";
	for(var i = 0; i < binarray[0]; i += 3)
	{
		var triplet = (((binarray[(i >> 2) + 1] >> 8 * (3 - i % 4)) & 0xFF) << 16)
					| (((binarray[(i + 1 >> 2) + 1] >> 8 * (3 - (i + 1) % 4)) & 0xFF) << 8)
					| ((binarray[(i + 2 >> 2) + 1] >> 8 * (3 - (i + 2) % 4)) & 0xFF);
		for(var j = 0; j < 4; j++)
		{
			if(i * chrsz + j * 6 > binarray[0] * chrsz)
			{
				str += b64pad;
			}
			else
			{
				str += tab.charAt((triplet >> 6 * (3 - j)) & 0x3F);
			}
		}
	}
	return str;
}

function hex_sha256(s)
{
	return binb2hex(core_sha256(str2binb(s), s.length * chrsz));
}

function base64_sha256(s)
{
	return binb2b64(core_sha256(str2binb(s), s.length * chrsz));
}

function str_sha256(s)
{
	return binb2str(core_sha256(str2binb(s), s.length * chrsz));
}

function base64_encode(s)
{
	return binb2b64(str2binb(s));
}

function base64_decode(s)
{
	return binb2str(b642binb(s));
}

// Tag to body tag onkeydown to prevent submit on enter key pressed
function enterToTab(e)
{
	var intKey = window.Event ? e.which : e.KeyCode;

	if (e.srcElement.id != 'btnSubmit')
	{
		if(intKey == 13)
		{
			e.returnValue = false;
		}
	}
}
