/*(c) 2006 - 2008 AllenPort Co. All Rights Reserved.
All versions of this code, including the source and executable versions, 
constitute the intellectual property of AllenPort Co., which expressly reserves 
any and all U.S. and foreign rights and benefits to the code under copyright, 
trade secret and any other intellectual property law or international treaty 
whatsoever. Use of the code is subject to the terms and conditions of a separate 
written license agreement, and the code shall not be reproduced, modified, distributed 
or otherwise used in any form or manner whatsoever without obtaining the prior written 
permission of AllenPort Co. Any unauthorized reproduction or distribution of the code, 
or any portion of it, may result in civil and criminal penalties and be prosecuted 
to the fullest extent of the law.*/
// Depends on rsa.js and jsbn2.js

// Undo PKCS#1 (type 2, random) padding and, if valid, return the plaintext
function pkcs1unpad2(d,n) {
  var b = d.toByteArray();
  var i = 0;
  while(i < b.length && b[i] == 0) ++i;
  if(b.length-i != n-1 || b[i] != 2)
    return null;
  ++i;
  while(b[i] != 0)
    if(++i >= b.length) return null;
  var ret = "";
  while(++i < b.length)
    ret += String.fromCharCode(b[i]);
  return ret;
}

// Set the private key fields N, e, and d from hex strings
function RSASetPrivate(N,E,D) {
  if(N != null && E != null && N.length > 0 && E.length > 0) {
    this.n = parseBigInt(N,16);
    this.e = parseInt(E,16);
    this.d = parseBigInt(D,16);
  }
  else
  {
    //alert("Invalid RSA private key");
  }
}

// Set the private key fields N, e, d and CRT params from hex strings
function RSASetPrivateEx(N,E,D,P,Q,DP,DQ,C) {
  if(N != null && E != null && N.length > 0 && E.length > 0) {
    this.n = parseBigInt(N,16);
    this.e = parseInt(E,16);
    this.d = parseBigInt(D,16);
    this.p = parseBigInt(P,16);
    this.q = parseBigInt(Q,16);
    this.dmp1 = parseBigInt(DP,16);
    this.dmq1 = parseBigInt(DQ,16);
    this.coeff = parseBigInt(C,16);
  }
  else
  {
    //alert("Invalid RSA private key");
  }
}
var init_state  = 0;
var gen_p_state = 1;
var gen_q_state = 2;
var check_state = 3;
var end_state   = 4;
    
//var local_context = {state:null, rng:null, qs:null, ee:null, e:null, p:null, q:null, isGenerated:null};
//var B_arg;
//var E_arg;

function RSAGenerateExInternal()
{
    //var startTime = new Date().getTime();
    switch(this.local_context.state)
    {
        case init_state:
            this.local_context.rng = new SecureRandom();
            this.local_context.qs = this.B_arg >> 1;
            this.local_context.e = parseInt(this.E_arg, 16);
            this.local_context.ee = new BigInteger(this.E_arg, 16);
            this.local_context.state = gen_p_state;
            break;
        case gen_p_state:
              this.local_context.p = new BigInteger(this.B_arg - this.local_context.qs, 1, this.local_context.rng);
              if(this.local_context.p.subtract(BigInteger.ONE).gcd(this.local_context.ee).compareTo(BigInteger.ONE) == 0 
                                          && this.local_context.p.isProbablePrime(10)) {
                  this.local_context.state = gen_q_state;
              }
            break;
        case gen_q_state:
              this.local_context.q = new BigInteger(this.local_context.qs, 1, this.local_context.rng);
              if(this.local_context.q.subtract(BigInteger.ONE).gcd(this.local_context.ee).compareTo(BigInteger.ONE) == 0 
                                          && this.local_context.q.isProbablePrime(10)) {
                  this.local_context.state = check_state;
              }
            break;
        case check_state:
            if(this.local_context.p.compareTo(this.local_context.q) <= 0) {
              var t = this.local_context.p;
              this.local_context.p = this.local_context.q;
              this.local_context.q = t;
            }
            var p1 = this.local_context.p.subtract(BigInteger.ONE);
            var q1 = this.local_context.q.subtract(BigInteger.ONE);
            
            var phi = p1.multiply(q1);
            if(phi.gcd(this.local_context.ee).compareTo(BigInteger.ONE) == 0) {
              this.e = this.local_context.e;
              this.p = this.local_context.p;
              this.q = this.local_context.q;
              this.n = this.p.multiply(this.q);
              this.d = this.local_context.ee.modInverse(phi);
              this.dmp1 = this.d.mod(p1);
              this.dmq1 = this.d.mod(q1);
              this.coeff = this.q.modInverse(this.p);
              this.local_context.state = end_state;  
            }
            else {
                this.local_context.state = gen_p_state;
            }
            break;     
        case end_state:
            break;     
    }
    if(this.local_context.state != end_state)
    {
        var that = this;
        /*var now = new Date().getTime();
        var ms = now - startTime;
        setTimeout(function () {that.generateExInternal();} , ms * (Math.SQRT2 - 1));*/
        setTimeout(function () {that.generateExInternal();} , 0);
    }
    else
    {
        this.local_context.isGenerated = 1;
    }
}

function RSAGenerateEx(B,E)
{
    this.B_arg = B;
    this.E_arg = E;
    this.local_context.state = init_state;
    this.local_context.isGenerated = 0;
    this.generateExInternal();   
    /*while (local_context.state != end_state)
    {
        var req = newXMLHttpRequest();
	    req.open("GET", "http://localhost/delay.php?delay=3000", false);
	    req.send();   
    } */
}

// Generate a new random private key B bits long, using public expt E
function RSAGenerate(B,E) {
  var rng = new SecureRandom();
  var qs = B>>1;
  this.e = parseInt(E,16);
  var ee = new BigInteger(E,16);
  for(;;) {
    for(;;) {
      this.p = new BigInteger(B-qs,1,rng);
      if(this.p.subtract(BigInteger.ONE).gcd(ee).compareTo(BigInteger.ONE) == 0 && this.p.isProbablePrime(10)) 
        break;
    }
    
    for(;;) {
      this.q = new BigInteger(qs,1,rng);
      if(this.q.subtract(BigInteger.ONE).gcd(ee).compareTo(BigInteger.ONE) == 0 && this.q.isProbablePrime(10)) 
        break;
    }
    if(this.p.compareTo(this.q) <= 0) {
      var t = this.p;
      this.p = this.q;
      this.q = t;
    }
    var p1 = this.p.subtract(BigInteger.ONE);
    var q1 = this.q.subtract(BigInteger.ONE);
    
    var phi = p1.multiply(q1);
    if(phi.gcd(ee).compareTo(BigInteger.ONE) == 0) {
      this.n = this.p.multiply(this.q);
      this.d = ee.modInverse(phi);
      this.dmp1 = this.d.mod(p1);
      this.dmq1 = this.d.mod(q1);
      this.coeff = this.q.modInverse(this.p);
      break;
    }
  }
}

// Perform raw private operation on "x": return x^d (mod n)
function RSADoPrivate(x) {
  if(this.p == null || this.q == null)
    return x.modPow(this.d, this.n);

  // TODO: re-calculate any missing CRT params
  var xp = x.mod(this.p).modPow(this.dmp1, this.p);
  var xq = x.mod(this.q).modPow(this.dmq1, this.q);

  while(xp.compareTo(xq) < 0)
    xp = xp.add(this.p);
  return xp.subtract(xq).multiply(this.coeff).mod(this.p).multiply(this.q).add(xq);
}

// Return the PKCS#1 RSA decryption of "ctext".
// "ctext" is an even-length hex string and the output is a plain string.
function RSADecrypt(ctext) {
  var c = parseBigInt(ctext, 16);
  var m = this.doPrivate(c);
  if(m == null) return null;
  return pkcs1unpad2(m, (this.n.bitLength()+7)>>3);
}

// Return the PKCS#1 RSA decryption of "ctext".
// "ctext" is a Base64-encoded string and the output is a plain string.
//function RSAB64Decrypt(ctext) {
//  var h = b64tohex(ctext);
//  if(h) return this.decrypt(h); else return null;
//}

// protected
RSAKey.prototype.doPrivate = RSADoPrivate;

// public
RSAKey.prototype.setPrivate = RSASetPrivate;
RSAKey.prototype.setPrivateEx = RSASetPrivateEx;
RSAKey.prototype.generate = RSAGenerate;
RSAKey.prototype.decrypt = RSADecrypt;
RSAKey.prototype.generateExInternal = RSAGenerateExInternal;
RSAKey.prototype.generateEx = RSAGenerateEx;
//RSAKey.prototype.b64_decrypt = RSAB64Decrypt;
