/**
 * TLSSecurityParameters
 * 
 * This class encapsulates all the security parameters that get negotiated
 * during the TLS handshake. It also holds all the key derivation methods.
 * Copyright (c) 2007 Henri Torgemane
 * 
 * See LICENSE.txt for full license information.
 */
package com.hurlant.crypto.tls {
	import com.hurlant.crypto.hash.MD5;
	import com.hurlant.crypto.hash.SHA1;
	import com.hurlant.util.Hex;
	
	import flash.utils.ByteArray;
	
	public class SSLSecurityParameters implements ISecurityParameters {
		
		// COMPRESSION
		public static const COMPRESSION_NULL:uint = 0;
		
		private var entity:uint; // SERVER | CLIENT
		private var bulkCipher:uint; // BULK_CIPHER_*
		private var cipherType:uint; // STREAM_CIPHER | BLOCK_CIPHER
		private var keySize:uint;
		private var keyMaterialLength:uint;
		private var keyBlock:ByteArray;
		private var IVSize:uint;
		private var MAC_length:uint;
		private var macAlgorithm:uint; // MAC_*
		private var hashSize:uint;
		private var compression:uint; // COMPRESSION_NULL
		private var masterSecret:ByteArray; // 48 bytes
		private var clientRandom:ByteArray; // 32 bytes
		private var serverRandom:ByteArray; // 32 bytes
		private var pad_1:ByteArray; // varies
		private var pad_2:ByteArray; // varies
		private var ignoreCNMismatch:Boolean = true;
		private var trustAllCerts:Boolean = false;
		private var trustSelfSigned:Boolean = false;
		public static const PROTOCOL_VERSION:uint = 0x0300;
		
		// not strictly speaking part of this, but yeah.
		public var keyExchange:uint;
		
		public function get version() : uint { 
			return PROTOCOL_VERSION;
		}
		public function SSLSecurityParameters(entity:uint, localCert:ByteArray = null, localKey:ByteArray = null) {
			this.entity = entity;
			reset();
		}
		
		public function reset():void {
			bulkCipher = BulkCiphers.NULL;
			cipherType = BulkCiphers.BLOCK_CIPHER;
			macAlgorithm = MACs.NULL;
			compression = COMPRESSION_NULL;
			masterSecret = null;
		}
		
		public function getBulkCipher():uint {
			return bulkCipher;
		}
		public function getCipherType():uint {
			return cipherType;
		}
		public function getMacAlgorithm():uint {
			return macAlgorithm;
		}
		
		public function setCipher(cipher:uint):void {
			bulkCipher = CipherSuites.getBulkCipher(cipher);
			cipherType = BulkCiphers.getType(bulkCipher);
			keySize = BulkCiphers.getExpandedKeyBytes(bulkCipher);   // 8
			keyMaterialLength = BulkCiphers.getKeyBytes(bulkCipher); // 5
			IVSize = BulkCiphers.getIVSize(bulkCipher);


			keyExchange = CipherSuites.getKeyExchange(cipher);
			
			macAlgorithm = CipherSuites.getMac(cipher);
			hashSize = MACs.getHashSize(macAlgorithm);
			pad_1 = new ByteArray();
			pad_2 = new ByteArray();
			for (var x:int = 0; x < 48; x++) {
				pad_1.writeByte(0x36);
				pad_2.writeByte(0x5c);
			}			
		}
		public function setCompression(algo:uint):void {
			compression = algo;
		}
		
		public function setPreMasterSecret(secret:ByteArray):void {
			/* Warning! Following code may cause madness
				 Tread not here, unless ye be men of valor.
			
			***** Official Prophylactic Comment ******
				(to protect the unwary...this code actually works, that's all you need to know)
			
			This does two things, computes the master secret, and generates the keyBlock
			
			
			To compute the master_secret, the following algorithm is used.
			 for SSL 3, this means
			 master = MD5( premaster + SHA1('A' + premaster + client_random + server_random ) ) +
						MD5( premaster + SHA1('BB' + premaster + client_random + server_random ) ) +
						MD5( premaster + SHA1('CCC' + premaster + client_random + server_random ) )
			*/		
			var tempHashA:ByteArray = new ByteArray(); // temporary hash, gets reused a lot
			var tempHashB:ByteArray = new ByteArray(); // temporary hash, gets reused a lot
			
			var shaHash:ByteArray;
			var mdHash:ByteArray;
			
			var i:int;
			var j:int;
			
			var sha:SHA1 = new SHA1();
			var md:MD5 = new MD5();
					
			var k:ByteArray = new ByteArray();
			
			k.writeBytes(secret);
			k.writeBytes(clientRandom);
			k.writeBytes(serverRandom);
			
			masterSecret = new ByteArray();
			var pad_char:uint = 0x41;
			
			for ( i = 0; i < 3; i++) {
				// SHA portion
				tempHashA.position = 0;
								
				for ( j = 0; j < i + 1; j++) {
					tempHashA.writeByte(pad_char);
				}
				pad_char++;
				
				tempHashA.writeBytes(k);
				shaHash = sha.hash(tempHashA);
				
				// MD5 portion
				tempHashB.position = 0;
				tempHashB.writeBytes(secret); 
				tempHashB.writeBytes(shaHash); 
				mdHash = md.hash(tempHashB);
				
				// copy into my key
				masterSecret.writeBytes(mdHash);
			}
			
			// *************** END MASTER SECRET **************
			
			// More prophylactic comments
			// *************** START KEY BLOCK ****************
			
			// So here, I'm setting up the keyBlock array that I will derive MACs, keys, and IVs from.
			// Rebuild k (hash seed)
			 
			k.position = 0; 
			k.writeBytes(masterSecret);
			k.writeBytes(serverRandom);
			k.writeBytes(clientRandom);
			
			keyBlock = new ByteArray(); 
			
			tempHashA = new ByteArray();
			tempHashB = new ByteArray();
			// now for 16 iterations to get 256 bytes (16 * 16), better to have more than not enough
			pad_char = 0x41;
			for ( i = 0; i < 16; i++) {
				tempHashA.position = 0; 
				
				for ( j = 0; j < i + 1; j++) {
					tempHashA.writeByte(pad_char);
				}
				pad_char++;
				tempHashA.writeBytes(k);
				shaHash = sha.hash(tempHashA);	
				
				tempHashB.position = 0; 
				tempHashB.writeBytes(masterSecret);
				tempHashB.writeBytes(shaHash, 0);
				mdHash = md.hash(tempHashB);
				
				keyBlock.writeBytes(mdHash); 
			}
		}
		
		public function setClientRandom(secret:ByteArray):void {
			clientRandom = secret;
		}
		public function setServerRandom(secret:ByteArray):void {
			serverRandom = secret;
		}
		
		public function get useRSA():Boolean {
			return KeyExchanges.useRSA(keyExchange);
		}
		
		// This is the Finished message
		// if you value your sanity, stay away...far away
		public function computeVerifyData(side:uint, handshakeMessages:ByteArray):ByteArray {
			// for SSL 3.0, this consists of
			// 	finished = md5( masterSecret + pad2 + md5( handshake + sender + masterSecret + pad1 ) ) +
			//			   sha1( masterSecret + pad2 + sha1( handshake + sender + masterSecret + pad1 ) )
			
			// trace("Handshake messages: " + Hex.fromArray(handshakeMessages));
			var sha:SHA1 = new SHA1();
			var md:MD5 = new MD5();
			var k:ByteArray = new ByteArray(); // handshake + sender + masterSecret + pad1
			var j:ByteArray = new ByteArray(); // masterSecret + pad2 + k
			
			var innerKey:ByteArray;
			var outerKey:ByteArray = new ByteArray();
			
			var hashSha:ByteArray;
			var hashMD:ByteArray;
			
			var sideBytes:ByteArray = new ByteArray();
			if (side == TLSEngine.CLIENT) {
			 	sideBytes.writeUnsignedInt(0x434C4E54);
			 } else {
				sideBytes.writeUnsignedInt(0x53525652);
			}
			
			// Do the SHA1 part of the routine first
			masterSecret.position = 0;
			k.writeBytes(handshakeMessages);
			k.writeBytes(sideBytes);
			k.writeBytes(masterSecret);
			k.writeBytes(pad_1, 0, 40); // limited to 40 chars for SHA1
				
			innerKey = sha.hash(k);
			// trace("Inner SHA Key: " + Hex.fromArray(innerKey));
			
			j.writeBytes(masterSecret);
			j.writeBytes(pad_2, 0, 40); // limited to 40 chars for SHA1
			j.writeBytes(innerKey);
			
			hashSha = sha.hash(j);
			// trace("Outer SHA Key: " + Hex.fromArray(hashSha));
			
			// Rebuild k for MD5
			k = new ByteArray();
			
			k.writeBytes(handshakeMessages);
			k.writeBytes(sideBytes);
			k.writeBytes(masterSecret);
			k.writeBytes(pad_1); // Take the whole length of pad_1 & pad_2 for MD5
			
			innerKey = md.hash(k);
			// trace("Inner MD5 Key: " + Hex.fromArray(innerKey));
			
			j = new ByteArray();
			j.writeBytes(masterSecret);
			j.writeBytes(pad_2); // see above re: 48 byte pad
			j.writeBytes(innerKey); 
			
			hashMD = md.hash(j);
			// trace("Outer MD5 Key: " + Hex.fromArray(hashMD));
			
			outerKey.writeBytes(hashMD, 0, hashMD.length);
			outerKey.writeBytes(hashSha, 0, hashSha.length);
			var out:String = Hex.fromArray(outerKey);
			// trace("Finished Message: " + out);
			outerKey.position = 0;
			
			return outerKey;
		
		}
		
		public function computeCertificateVerify( side:uint, handshakeMessages:ByteArray ):ByteArray {
			// TODO: Implement this, but I don't forsee it being necessary at this point in time, since for purposes
			// of the override, I'm only going to use TLS
			return null;  
		}
		
		public function getConnectionStates():Object {
			
			if (masterSecret != null) {
				// so now, I have to derive the actual keys from the keyblock that I generated in setPremasterSecret.
				// for MY purposes, I need RSA-AES 128/256 + SHA
				// so I'm gonna have keylen = 32, minlen = 32, mac_length = 20, iv_length = 16
				// but...I can get this data from the settings returned in the constructor when this object is 
				// It strikes me that TLS does this more elegantly...
				
				var mac_length:int = hashSize as Number;
				var key_length:int = keySize as Number;
				var iv_length:int = IVSize as Number;
				
				var client_write_MAC:ByteArray = new ByteArray();
				var server_write_MAC:ByteArray = new ByteArray();
				var client_write_key:ByteArray = new ByteArray();
				var server_write_key:ByteArray = new ByteArray();
				var client_write_IV:ByteArray = new ByteArray();
				var server_write_IV:ByteArray = new ByteArray();
		
				// Derive the keys from the keyblock
				// Get the MACs first
				keyBlock.position = 0;
				keyBlock.readBytes(client_write_MAC, 0, mac_length);
				keyBlock.readBytes(server_write_MAC, 0, mac_length);
				
				// keyBlock.position is now at MAC_length * 2
				// then get the keys
				keyBlock.readBytes(client_write_key, 0, key_length);
				keyBlock.readBytes(server_write_key, 0, key_length);
				
				// keyBlock.position is now at (MAC_length * 2) + (keySize * 2) 
				// and then the IVs
				keyBlock.readBytes(client_write_IV, 0, iv_length);
				keyBlock.readBytes(server_write_IV, 0, iv_length);
				
				// reset this in case it's needed, for some reason or another, but I doubt it
				keyBlock.position = 0;
				
				var client_write:SSLConnectionState = new SSLConnectionState(
						bulkCipher, cipherType, macAlgorithm,
						client_write_MAC, client_write_key, client_write_IV);
				var server_write:SSLConnectionState = new SSLConnectionState(
						bulkCipher, cipherType, macAlgorithm,
						server_write_MAC, server_write_key, server_write_IV);
				
				if (entity == TLSEngine.CLIENT) {
					return {read:server_write, write:client_write};
				} else {
					return {read:client_write, write:server_write};
				}


			} else {
				return {read:new SSLConnectionState, write:new SSLConnectionState};
			}
		}
		
	}
}