// Depends on jsbn.js, UTF8, aesprng.js, entropy.js, aes.js
// 17-08-08 VM UTF8 encryption added

// convert a (hex) string to a bignum object
var prng;

function parseBigInt(str,r) {
  return new BigInteger(str,r);
}

function linebrk(s,n) {
  var ret = "";
  var i = 0;
  while(i + n < s.length) {
    ret += s.substring(i,i+n) + "\n";
    i += n;
  }
  return ret + s.substring(i,s.length);
}

function byte2Hex(b) {
  if(b < 0x10)
    return "0" + b.toString(16);
  else
    return b.toString(16);
}

// "empty" RSA key constructor
function RSAKey() {
  this.n = null;
  this.e = 0;
  this.d = null;
  this.p = null;
  this.q = null;
  this.dmp1 = null;
  this.dmq1 = null;
  this.coeff = null;
}

// Set the public key fields N and e from hex strings
function RSASetPublic(N,E) {
  if(N != null && E != null && N.length > 0 && E.length > 0) {
    this.n = parseBigInt(N,16);
    this.e = parseInt(E,16);
  }
  else
    alert("Invalid RSA public key");
}

// Perform raw public operation on "x": return x^e (mod n)
function RSADoPublic(x) {
  return x.modPowInt(this.e, this.n);
}

function BytesToString(a){
 var s = new String();
 for(var i = 0; i < a.length; i++) {s+=String.fromCharCode(a[i]);}
 return s;
}

function StringToBytes(s){
 var a = new Array();
 for(var i = 0; i < s.length; i++) a[i]=s.charCodeAt(i);
 return a;
}

function RSApadString(s) {
 var i = ((this.n.bitLength()+7) >> 3) - s.length;
 if (i > 0) return s + BytesToString(getRandomBytes(i));
 return null;
}

function complexRSAEncrypt(text) {
  var plaintext = UTF8encode(text); 
  md5_init();
  for (var i = 0; i < plaintext.length; i++) {
   md5_update(plaintext.charCodeAt(i));
  }
  md5_finish();
  var header  = String.fromCharCode(1);
  header += String.fromCharCode(plaintext.length >>> 24);
  header += String.fromCharCode(plaintext.length >>> 16);
  header += String.fromCharCode(plaintext.length >>> 8);
  header += String.fromCharCode(plaintext.length & 0xFF);
  header += BytesToString(digestBits);
  /*  The format of the actual message is:
	Bytes   Content
        0       Byte 1 (to prevent number padding)
        1-4     Length of plaintext, big-endian order
	5-20	MD5 signature of plaintext
	21-end  Plaintext
		    
	Note that this message will be padded with random bytes 
        either to this.n size for short messages or
	to an integral number of AES blocks (blockSizeInBits / 8).
	This does not include the initial vector for CBC
	encryption, which is added internally by rijndaelEncrypt.
	    
  */
  addEntropyTime(); prng = new AESprng(keyFromEntropy());

  var RSAtext = this.padString(header + plaintext);
  var Aes = (RSAtext == null);
  if (Aes) {var key = getRandomBytes(32); 
   RSAtext = this.padString(header + BytesToString(key));
  }
  RSAa = this.doPublic(new BigInteger(StringToBytes(RSAtext)));
  RSAh = RSAa.toString(16); if(RSAh.length & 1) RSAh = "0" + RSAh;
  if (Aes) {var ct = rijndaelEncrypt(header + plaintext, key,"CBC");
   return RSAh+'/'+byteArrayToHex(ct);
  }
  return RSAh;
}

// protected
RSAKey.prototype.doPublic = RSADoPublic;
RSAKey.prototype.padString = RSApadString;

// public
RSAKey.prototype.setPublic = RSASetPublic;
RSAKey.prototype.encrypt = complexRSAEncrypt;

function RSACPZsetPublic() {
this.setPublic("b842718e025d5d9b5fb68c14cb01e584f6187152376a53bd012b793f246a3284\nf998780b3649927e1f8a758cf2dc46c1d8e2c588f2e7b4c74cdb47aa3afef1d0\nbcb4ea4b8f360b88b7d7fb58b8125c2302a556aba8b331297c3602490fc652b1\nbc89f3addaf37848229f8d2a2d1ea43db8788e37be1b5e3f627955af04e293c1","10001");
}

RSAKey.prototype.CPZsetPublic = RSACPZsetPublic;
