var RSAPublicKey = $.klass({
	initialize: function($modulus, $encryptionExponent) {
		this.modulus = new BigInteger(Hex.encode($modulus),16);
		this.encryptionExponent = new BigInteger(Hex.encode($encryptionExponent),16);
	},
	getModulus: function() {
		return this.modulus;
	},
	getEncryptionExponent: function() {
		return this.encryptionExponent;
	}
});


var UTF8 = new ($.klass({
        encode: function ($input) {
                $input = $input.replace(/\r\n/g,"\n");
                var $output = "";
                for (var $n = 0; $n < $input.length; $n++) {
                        var $c = $input.charCodeAt($n);
                        if ($c < 128)
                                $output += String.fromCharCode($c);
                        else if(($c > 127) && ($c < 2048)) {
                                $output += String.fromCharCode(($c >> 6) | 192);
                                $output += String.fromCharCode(($c & 63) | 128);
                        } else {
                                $output += String.fromCharCode(($c >> 12) | 224);
                                $output += String.fromCharCode((($c >> 6) & 63) | 128);
                                $output += String.fromCharCode(($c & 63) | 128);
                        }
                }
                return $output;
        },
        decode: function ($input) {
                var $output = "";
                var $i = 0;
                var $c = $c1 = $c2 = 0;
                while ( $i < $input.length ) {
                        $c = $input.charCodeAt($i);
                        if ($c < 128) {
                                $output += String.fromCharCode($c);
                                $i++;
                        } else if(($c > 191) && ($c < 224)) {
                                $c2 = $input.charCodeAt($i+1);
                                $output += String.fromCharCode((($c & 31) << 6) | ($c2 & 63));
                                $i += 2;
                        } else {
                                $c2 = $input.charCodeAt($i+1);
                                $c3 = $input.charCodeAt($i+2);
                                $output += String.fromCharCode((($c & 15) << 12) | (($c2 & 63) << 6) | ($c3 & 63));
                                $i += 3;
                        }
                }
                return $output;
        }
}))();


var Base64 = new ($.klass({
	initialize: function() {
		this.base64 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";
	},
	encode: function($input) {
		if(!$input) return false;
                //$input = UTF8.encode($input);
		var $output = "";
		var $chr1, $chr2, $chr3;
		var $enc1, $enc2, $enc3, $enc4;
		var $i = 0;
		do {
			$chr1 = $input.charCodeAt($i++);
			$chr2 = $input.charCodeAt($i++);
			$chr3 = $input.charCodeAt($i++);
			$enc1 = $chr1 >> 2;
			$enc2 = (($chr1 & 3) << 4) | ($chr2 >> 4);
			$enc3 = (($chr2 & 15) << 2) | ($chr3 >> 6);
			$enc4 = $chr3 & 63;
			if (isNaN($chr2)) $enc3 = $enc4 = 64;
			else if (isNaN($chr3)) $enc4 = 64;
			$output += this.base64.charAt($enc1) + this.base64.charAt($enc2) + this.base64.charAt($enc3) + this.base64.charAt($enc4);
		} while ($i < $input.length);
		return $output;
	},
	decode: function($input) {
		if(!$input) return false;
		$input = $input.replace(/[^A-Za-z0-9\+\/\=]/g, "");
		var $output = "";
		var $enc1, $enc2, $enc3, $enc4;
		var $i = 0;
		do {
			$enc1 = this.base64.indexOf($input.charAt($i++));
			$enc2 = this.base64.indexOf($input.charAt($i++));
			$enc3 = this.base64.indexOf($input.charAt($i++));
			$enc4 = this.base64.indexOf($input.charAt($i++));
			$output += String.fromCharCode(($enc1 << 2) | ($enc2 >> 4));
			if ($enc3 != 64) $output += String.fromCharCode((($enc2 & 15) << 4) | ($enc3 >> 2));
			if ($enc4 != 64) $output += String.fromCharCode((($enc3 & 3) << 6) | $enc4);
		} while ($i < $input.length);
		return $output; //UTF8.decode($output);
	}
}))();


var Hex = new ($.klass({
	initialize: function() {
		this.hex = "0123456789abcdef";
	},
	encode: function($input) {
		if(!$input) return false;
		var $output = "";
		var $k;
		var $i = 0;
		do {
			$k = $input.charCodeAt($i++);
			$output += this.hex.charAt(($k >> 4) &0xf) + this.hex.charAt($k & 0xf);
		} while ($i < $input.length);
		return $output;
	},
	decode: function($input) {
		if(!$input) return false;
		$input = $input.replace(/[^0-9abcdef]/g, "");
		var $output = "";
		var $i = 0;
		do {
			$output += String.fromCharCode(((this.hex.indexOf($input.charAt($i++)) << 4) & 0xf0) | (this.hex.indexOf($input.charAt($i++)) & 0xf));
		} while ($i < $input.length);
		return $output;
	}
}))();


var ASN1Data = $.klass({
	initialize: function($data) {
		this.error = false;
		this.data = this.parse($data);
	},
	hasError: function() {
		return this.error;
	},
	getData: function() {
		return this.data;
	},
	parse: function($data) {
		if (!$data) {
			this.error = true;
			return null;
		}
		var $result = [];
		while($data.length > 0) {
			// get the tag
			var $tag = $data.charCodeAt(0);
			$data = $data.substr(1);
			// get length
			var $length = 0;
			// ignore any null tag
			if (($tag & 31) == 0x5) $data = $data.substr(1);
			else {
				if ($data.charCodeAt(0) & 128) {
					var $lengthSize = $data.charCodeAt(0) & 127;
					$data = $data.substr(1);
					if($lengthSize > 0) $length = $data.charCodeAt(0);
					if($lengthSize > 1)	$length = (($length << 8) | $data.charCodeAt(1));
					if($lengthSize > 2) {
						this.error = true;
						return null;
					}
					$data = $data.substr($lengthSize);
				} else {
					$length = $data.charCodeAt(0);
					$data = $data.substr(1);
				}
			}
			// get value
			var $value = "";
			if($length) {
				if ($length > $data.length){
					this.error = true;
					return null;
				}
				$value = $data.substr(0, $length);
				$data = $data.substr($length);
			}
			if ($tag & 32)
				$result.push(this.parse($value)); // sequence
			else
				$result.push(this.value(($tag & 128) ? 4 : ($tag & 31), $value));
		}
		return $result;
	},
	value: function($tag, $data) {
		if ($tag == 1)
			return $data ? true : false;
		else if ($tag == 2) //integer
			return $data;
		else if ($tag == 3) //bit string
			return this.parse($data.substr(1));
		else if ($tag == 5) //null
			return null;
		else if ($tag == 6){ //ID
			var $res = [];
			var $d0 = $data.charCodeAt(0);
			$res.push(Math.floor($d0 / 40));
			$res.push($d0 - $res[0]*40);
			var $stack = [];
			var $powNum = 0;
			var $i;
			for($i=1;$i<$data.length;$i++){
				var $token = $data.charCodeAt($i);
				$stack.push($token & 127);
				if ( $token & 128 )
					$powNum++;
				else {
					var $j;
					var $sum = 0;
					for($j=0;$j<$stack.length;$j++)
						$sum += $stack[$j] * Math.pow(128, $powNum--);
					$res.push($sum);
					$powNum = 0;
					$stack = [];
				}
			}
			return $res.join(".");
		}
		return null;
	}
});

var RSA = new ($.klass({
	initialize: function() {
	},
	getPublicKey: function($pem) {
		if($pem.length<50) return false;
		if($pem.substr(0,26)!="-----BEGIN PUBLIC KEY-----") return false;
		$pem = $pem.substr(26);
		if($pem.substr($pem.length-24)!="-----END PUBLIC KEY-----") return false;
		$pem = $pem.substr(0,$pem.length-24);
		$pem = new ASN1Data(Base64.decode($pem));
		if($pem.hasError()) return false;
		$pem = $pem.getData();
		if($pem[0][0][0]=="1.2.840.113549.1.1.1")
			return new RSAPublicKey($pem[0][1][0][0], $pem[0][1][0][1]);
		return false;
	},
	encrypt: function($data, $pubkey) {
		if (!$pubkey) return false;
		$data = this.pkcs1pad2($data,($pubkey.getModulus().bitLength()+7)>>3);
		if(!$data) return false;
		$data = $data.modPowInt($pubkey.getEncryptionExponent(), $pubkey.getModulus());
		if(!$data) return false;
		$data = $data.toString(16);
		return Base64.encode(Hex.decode($data));
	},
	pkcs1pad2: function($data, $keysize) {
		if($keysize < $data.length + 11)
			return null;
		var $buffer = [];
		var $i = $data.length - 1;
		while($i >= 0 && $keysize > 0)
			$buffer[--$keysize] = $data.charCodeAt($i--);
		$buffer[--$keysize] = 0;
		while($keysize > 2)
			$buffer[--$keysize] = Math.floor(Math.random()*254) + 1;
		$buffer[--$keysize] = 2;
		$buffer[--$keysize] = 0;
		return new BigInteger($buffer);
	}
}))();
