/**
 * Crypto
 * 
 * An abstraction layer to instanciate our crypto algorithms
 * Copyright (c) 2007 Henri Torgemane
 * 
 * See LICENSE.txt for full license information.
 */
package com.hurlant.crypto
{
	import com.hurlant.crypto.hash.HMAC;
	import com.hurlant.crypto.hash.MAC;
	import com.hurlant.crypto.hash.IHash;
	import com.hurlant.crypto.hash.MD2;
	import com.hurlant.crypto.hash.MD5;
	import com.hurlant.crypto.hash.SHA1;
	import com.hurlant.crypto.hash.SHA224;
	import com.hurlant.crypto.hash.SHA256;
	import com.hurlant.crypto.prng.ARC4;
	import com.hurlant.crypto.rsa.RSAKey;
	import com.hurlant.crypto.symmetric.AESKey;
	import com.hurlant.crypto.symmetric.BlowFishKey;
	import com.hurlant.crypto.symmetric.CBCMode;
	import com.hurlant.crypto.symmetric.CFB8Mode;
	import com.hurlant.crypto.symmetric.CFBMode;
	import com.hurlant.crypto.symmetric.CTRMode;
	import com.hurlant.crypto.symmetric.DESKey;
	import com.hurlant.crypto.symmetric.ECBMode;
	import com.hurlant.crypto.symmetric.ICipher;
	import com.hurlant.crypto.symmetric.IMode;
	import com.hurlant.crypto.symmetric.IPad;
	import com.hurlant.crypto.symmetric.ISymmetricKey;
	import com.hurlant.crypto.symmetric.IVMode;
	import com.hurlant.crypto.symmetric.NullPad;
	import com.hurlant.crypto.symmetric.OFBMode;
	import com.hurlant.crypto.symmetric.PKCS5;
	import com.hurlant.crypto.symmetric.SimpleIVMode;
	import com.hurlant.crypto.symmetric.TripleDESKey;
	import com.hurlant.crypto.symmetric.XTeaKey;
	import com.hurlant.util.Base64;
	
	import flash.utils.ByteArray;
	
	/**
	 * A class to make it easy to use the rest of the framework.
	 * As a side-effect, using this class will cause most of the framework
	 * to be linked into your application, which is not always what you want.
	 * 
	 * If you want to optimize your download size, don't use this class.
	 * (But feel free to read it to get ideas on how to get the algorithm you want.)
	 */
	public class Crypto
	{
		private var b64:Base64; // we don't use it, but we want the swc to include it, so cheap trick.
		
		public function Crypto(){
		}
		
		/**
		 * Things that should work, among others:
		 *  "aes"
		 *  "aes-128-ecb"
		 *  "aes-128-cbc"
		 *  "aes-128-cfb"
		 *  "aes-128-cfb8"
		 *  "aes-128-ofb"
		 *  "aes-192-cfb"
		 *  "aes-256-ofb"
		 *  "blowfish-cbc"
		 *  "des-ecb"
		 *  "xtea"
		 *  "xtea-ecb"
		 *  "xtea-cbc"
		 *  "xtea-cfb"
		 *  "xtea-cfb8"
		 *  "xtea-ofb"
		 *  "rc4"
		 *  "simple-aes-cbc"
		 */
		public static function getCipher(name:String, key:ByteArray, pad:IPad=null):ICipher {
			// split name into an array.
			var keys:Array = name.split("-");
			switch (keys[0]) {
				/**
				 * "simple" is a special case. It means:
				 * "If using an IV mode, prepend the IV to the ciphertext"
				 */
				case "simple":
					keys.shift();
					name = keys.join("-");
					var cipher:ICipher = getCipher(name, key, pad);
					if (cipher is IVMode) {
						return new SimpleIVMode(cipher as IVMode);
					} else {
						return cipher;
					}
				/**
				 * we support both "aes-128" and "aes128"
				 * Technically, you could use "aes192-128", but you'd
				 * only be hurting yourself.
				 */
				case "aes":
				case "aes128":
				case "aes192":
				case "aes256":
					keys.shift();
					if (key.length*8==keys[0]) {
						// support for "aes-128-..." and such.
						keys.shift();
					}
					return getMode(keys[0], new AESKey(key), pad);
				break;
				case "bf":
				case "blowfish":
					keys.shift();
					return getMode(keys[0], new BlowFishKey(key), pad);
				/**
				 * des-ede and des-ede3 are both equivalent to des3.
				 * the choice between 2tdes and 3tdes is made based
				 * on the length of the key provided.
				 */
				case "des":
					keys.shift();
					if (keys[0]!="ede" && keys[0]!="ede3") {
						return getMode(keys[0], new DESKey(key), pad);
					}
					if (keys.length==1) {
						keys.push("ecb"); // default mode for 2tdes and 3tdes with openssl enc
					}
					// fall-through to triple des
				case "3des":
				case "des3":
					keys.shift();
					return getMode(keys[0], new TripleDESKey(key), pad);
				case "xtea":
					keys.shift();
					return getMode(keys[0], new XTeaKey(key), pad);
				break;
				/**
				 * Technically, you could say "rc4-128" or whatever,
				 * but really, the length of the key is what counts here.
				 */
				case "rc4":
					keys.shift();
					return new ARC4(key);
				break;
			}
			return null;
		}
		
		/**
		 * Returns the size of a key for a given cipher identifier.
		 */
		public static function getKeySize(name:String):uint {
			var keys:Array = name.split("-");
			switch (keys[0]) {
				case "simple":
					keys.shift();
					return getKeySize(keys.join("-"));
				case "aes128":
					return 16;
				case "aes192":
					return 24;
				case "aes256":
					return 32;
				case "aes":
					keys.shift();
					return parseInt(keys[0])/8;
				case "bf":
				case "blowfish":
					return 16;
				case "des":
					keys.shift();
					switch (keys[0]) {
						case "ede":
							return 16;
						case "ede3":
							return 24;
						default:
							return 8;
					}
				case "3des":
				case "des3":
					return 24;
				case "xtea":
					return 8;
				case "rc4":
					if (parseInt(keys[1])>0) {
						return parseInt(keys[1])/8;
					}
					return 16; // why not.
			}
			return 0; // unknown;
		}
		
		private static function getMode(name:String, alg:ISymmetricKey, padding:IPad=null):IMode {
			switch (name) {
				case "ecb":
					return new ECBMode(alg, padding);
				case "cfb":
					return new CFBMode(alg, padding);
				case "cfb8":
					return new CFB8Mode(alg, padding);
				case "ofb":
					return new OFBMode(alg, padding);
				case "ctr":
					return new CTRMode(alg, padding);
				case "cbc":
				default:
					return new CBCMode(alg, padding);
			}
		}
		
		/**
		 * Things that should work:
		 * "md5"
		 * "sha"
		 * "sha1"
		 * "sha224"
		 * "sha256"
		 */
		public static function getHash(name:String):IHash {
			switch(name) {
				case "md2":
					return new MD2;
				case "md5":
					return new MD5;
				case "sha": // let's hope you didn't mean sha-0
				case "sha1":
					return new SHA1;
				case "sha224":
					return new SHA224;
				case "sha256":
					return new SHA256;
			}
			return null;
		}
		
		/**
		 * Things that should work:
		 * "sha1"
		 * "md5-64"
		 * "hmac-md5-96"
		 * "hmac-sha1-128"
		 * "hmac-sha256-192"
		 * etc.
		 */
		public static function getHMAC(name:String):HMAC {
			var keys:Array = name.split("-");
			if (keys[0]=="hmac") keys.shift();
			var bits:uint = 0;
			if (keys.length>1) {
				bits = parseInt(keys[1]);
			}
			return new HMAC(getHash(keys[0]), bits);
		}
		

		public static function getMAC(name:String):MAC {
			
			var keys:Array = name.split("-");
			if (keys[0]=="mac") keys.shift();
			var bits:uint = 0;
			if (keys.length > 1) {
				bits = parseInt(keys[1]);
			}
			return new MAC(getHash(keys[0]), bits);
		}
				
		
		public static function getPad(name:String):IPad {
			switch(name) {
				case "null":
					return new NullPad;
				case "pkcs5":
				default:
					return new PKCS5;
			}
		}
		
		/** mostly useless.
		 */
		public static function getRSA(E:String, M:String):RSAKey {
			return RSAKey.parsePublicKey(M,E);
		}
	}
}