import java.security.*;
import java.math.BigInteger;
import cryptix.provider.rsa.*;

class cookie extends ActiveCacheInterface {
private static BigInteger mval;
private static BigInteger eval;

// the main function of the applet
public static int FromCache(String User_HTTP_Request,
                            String Client_IP_Address,
                            String Client_Name,
                            int Cache_File,
                            int New_File)
  {
    int status, result;
    // is the public key of the server in ActiveCache or not
    boolean keyInCache = is_in_cache("pubkey");
    if (!keyInCache) {
      // not in ActiveCache, get it from server, and save it in cache
      result = getPubKeyObj();
      // get public key failed, let proxy contact server
      if (result == -1) return -1;
    }
    // read the public key from object in cache
    if (readPubKey() == -1) return -1;
    // check the client request for cookie header and verify signature
    status = checkCookie(User_HTTP_Request);
    if (status == 1) {
      // client request has cookie header and passed verification
      // access to the cached document granted
      return 0;
    }
    else {
      // no cookie header or signature not correct, access not granted
      return -1;
    }
  }

// get the public key from server and save to an object in ActiveCache
private static int getPubKeyObj()
  {
    // send request to server to get public key
    int fd = sendGetReq();
    if (fd == -1) return -1;
    // save the public key into an object in ActiveCache
    int result = saveGetResult(fd);
    return result;
  }

// send GET request to server to get the public key file
// return the file descriptor that save the response from server
private static int sendGetReq()
  {
    StringBuffer reqStr = new StringBuffer();
    String curDoc, docName;
    int fd, pos;
    // construct the url of public key, which is the url of
    // the cached document appended by ".pubkey"
    curDoc = curdocurl();
    pos = curDoc.indexOf("http://");
    if (pos == -1) return -1;
    pos = curDoc.indexOf('/', pos+8);
    if (pos == -1) return -1;
    docName = curDoc.substring(pos);
    // reqStr is the HTTP request to be sent to server
    // should be like GET cached_doc.html.pubkey HTTP/1.0
    reqStr.append("GET ").append(docName)
      .append(".pubkey").append(" HTTP/1.0\n\n");
    // send request to server getting the object,
    // response is saved in a file, whose file descriptor is fd
    fd = send_request_to_server(new String(reqStr));
    return fd;
  }

// parse the response from server when proxy get
// the public key file from server, then save the
// public key contained in the response into an
// object in ActiveCache. fd is the file
// which contains the response from server
private static int saveGetResult(int fd)
  {
    int num, pos1, pos2, start;
    int newfd;
    byte buf[];
    String line;
    // check the response status, the first line
    // of the response should be HTTP/1.x 200 OK
    {
      // we assume the response header from server
      // is no longer than 2048 bytes, read the whole
      // response header into the buffer at once
      buf = new byte[2048];
      num = read(fd, buf, 2048);
      line = new String(buf, 0);
      pos1 = line.indexOf(' ');
      if (pos1 == -1) {
        close(fd);
        return -1;
      } else {
        String status = line.substring(pos1+1, pos1+4);
        if (!status.equals("200"))
          return -1;
      }
    }
    // filter the header of the response, the header
    // ends with \n\n or \r\n\r\n
    {
      pos2 = line.indexOf("\n\n", pos1);
      if (pos2 != -1) start = pos2 + 2;
      else {
        pos2 = line.indexOf("\n\r\n", pos1);
        if (pos2 != -1) start = pos2 + 3;
        else start = -1;
      }
    }
    // if the end of header not found, there are
    // some error in the response from server, quit
    if (start == -1) {
      close(fd);
      return -1;
    }
    // now going to copy the content of the response,
    // which is the public key into an object "pubkey"
    newfd = create("pubkey", 00700);
    if (newfd == -1) {
      close(fd);
      return -1;
    }
    byte[] writebuf = new byte[num-start];
    // we need to copy the bytes into another buffer
    // because write can only write the whole buffer
    // into an object at this moment
    byteCopy(buf, start, num-start, writebuf);
    // write the part in the buffer after header into object
    write(newfd, writebuf, num-start);
    // copy other parts in the response into object
    while ((num = read(fd, buf, 2048)) > 0) {
      write(newfd, buf, num);
    }
    close(fd);
    close(newfd);
    return 0;
  }

// read the public key from the object in ActiveCache.
// the public key is RSA key. here we uses two biginteger
// to represent the key. format of the object is like:
// byte#_of_int1 int1 byte#_of_int2 int2
private static int readPubKey()
  {
    int num, len1, len2;
    int fd = open("pubkey", 0);
    if (fd == -1) return -1;
    // we currently assume the size of object is less than 1024
    byte[] lbuf = new byte[1024];
    num = read(fd, lbuf, 1024);
    // the first two bytes contains the value of length of mval
    // now len1 is the length of mval
    len1 = (((lbuf[0]+256)%256) << 8) + ((lbuf[1]+256)%256);
    // read mval # of bytes and create a biginteger from it
    byte[] mbuf = new byte[len1];
    if (byteCopy(lbuf, 2, len1, mbuf) != len1) {
      close(fd);
      return -1;
    }
    // now mval contains the value of the first biginteger
    mval = new BigInteger(mbuf);
    // read the length of eval
    len2 = (((lbuf[len1+2]+256)%256) << 8) + ((lbuf[len1+3]+256)%256);
    // read the value of eval
    byte[] ebuf = new byte[len2];
    if (byteCopy(lbuf, len1+4, len2, ebuf) != len2) {
      close(fd);
      return -1;
    }
    // now eval contains the value of the second biginteger
    eval = new BigInteger(ebuf);
    return 0;
  }

// parse the cookie header from the client request, get
// the ID and SIGNATURE, then verify the signature
public static int checkCookie(String User_HTTP_Request)
  {
    int pos1, pos2, pos3;
    String cookieStr;
    String content, signature;
    // test whether cookie string is in the request header
    // and extract the string if exists
    pos1 = User_HTTP_Request.indexOf("Cookie:");
    if (pos1 == -1) return -1;
    // find the end of line of this cookie header
    pos2 = User_HTTP_Request.indexOf("\n", pos1);
    pos3 = User_HTTP_Request.indexOf("\r", pos1);
    if ((pos3!=-1) && (pos3<pos2)) pos2 = pos3;
    if (pos2 == -1) return -1;
    // copy this line of cookie header into cookieStr
    cookieStr = User_HTTP_Request.substring(pos1, pos2);
    // check whether the cookie string contains "ID"
    // and extract the content if exists
    pos1 = cookieStr.indexOf("ID=");
    if (pos1 == -1) return -1;
    // should end with a "; "
    pos2 = cookieStr.indexOf("; ", pos1);
    if (pos2 == -1) content = cookieStr.substring(pos1+3);
    else content = cookieStr.substring(pos1+3, pos2);
    // check whether the cookie string contains "signature"
    // and extract the signature if exists
    pos1 = cookieStr.indexOf("SIGNATURE=");
    if (pos1 == -1) return -1;
    // should end with a "; "
    pos2 = cookieStr.indexOf("; ", pos1);
    if (pos2 == -1) signature = cookieStr.substring(pos1+10);
    else signature = cookieStr.substring(pos1+10, pos2);
    // copy the value of id to byte array cont
    byte[] cont = Convert(content);
    // copy the value of signature to byte array codedsign
    byte[] codedsign = Convert(signature);
    // since the original signature may contain bytes > 128
    // we encoded the original signature with uuencode and then
    // send it as a cookie, so the signature we have in cookie
    // header is encoded in uuencode, we now decode it with
    // uudecode to the original signature
    byte[] sign = uudecode(codedsign);
    // verify the signature
    return verifySign(cont, sign);
  }

// verify the signature, return 1 if correct
private static int verifySign(byte[] content, byte[] signature)
  {
    try {
      SHA1_RSA_PKCS1Signature rsa = new SHA1_RSA_PKCS1Signature();
      // now construct a RSA public key with the public key
      // we get from the server
      RawRSAPublicKey rawkey = new RawRSAPublicKey(mval, eval);
      rsa.initVerify(rawkey);
      // verify whether signature is the signature of content
      // with the public key
      rsa.update(content);
      boolean verifies = rsa.verify(signature);
      if (verifies) {
        // yes, passed verification
        return 1;
      }
      else {
        System.out.println("signature verified false");
        return -1;
      }
    } catch (InvalidKeyException ike) {
      System.out.println("Invalid key exception");
    } catch (InvalidKeyException ike) {
      System.out.println("Invalid key exception");
      return -1;
    } catch (SignatureException se) {
      System.out.println("signature exception");
      return -1;
    }
  }

// uudecode to decode the encoded signature, check the
// algorithm of uudecode if you are more interested in this
private static byte[] uudecode(byte[] d)
  {
    int i, j;
    int len = (d.length/4)*3;
    if (d.length%4 == 1) {
      System.out.println("error format of encoded byte array");
      return null;
    }
    if (d.length%4 != 0)
      len += (d.length%4 - 1);
    byte[] src = new byte[len];
      len += (d.length%4 - 1);
    byte[] src = new byte[len];
    for (i=0, j=0; i+4<=d.length; i+=4, j+=3) {
      src[j] = (byte)((DEC(d[i])<<2) | (DEC(d[i+1])>>4));
      src[j+1] = (byte)((DEC(d[i+1])<<4) | (DEC(d[i+2])>>2));
      src[j+2] = (byte) ((DEC(d[i+2])<<6) | DEC(d[i+3]));
    }
    if (d.length-i == 2) {
      src[j] = (byte)((DEC(d[i])<<2) | (DEC(d[i+1])>>4));
    } else if (d.length-i == 3) {
      src[j] = (byte)((DEC(d[i])<<2) | (DEC(d[i+1])>>4));
      src[j+1] = (byte)((DEC(d[i+1])<<4) | (DEC(d[i+2])>>2));
    }
    return src;
  }
 
private static byte DEC(byte c)
  {
    return (byte)(((c) - ' ') & 077);
  }

// copy bytes from b1 starting from off, length len to a new byte arrary b2
private static int byteCopy(byte[] b1, int off, int len, byte[] b2)
  {
    int i, realLen;
    if ((off < 0) || (len <= 0)) return -1;
    if (b1.length < off) return -1;
    if (b1.length < off+len)
      realLen = b1.length - off;
    else
      realLen = len;
    for (i = 0; i < realLen; i++) {
      b2[i] = b1[off+i];
    }
    return realLen;
  }

// convert a String to a byte array
private static byte[] Convert(String str)
  {
    int i;
    byte buf[] = new byte[str.length()];
    // str.getBytes(0, str.length(), buf, 0);
    for (i=0; i<str.length(); i++) {
      buf[i] = (byte)(str.charAt(i));
    }
    return buf;
  }
}