// SigLabel.java
// $Id: SigLabel.java,v 1.1 1997/04/15 20:39:13 yhchu Exp $
// (c) COPYRIGHT MIT and INRIA, 1997.
// Please first read the full copyright statement in file COPYRIGHT.html

// The source code is jointly developed by W3C and some of its members
// participating in the Digital Signature Initiative.  The authors are:
//    Mark Champine, HP, <champine@apollo.hp.com>
//    Yang-hua Chu, MIT/W3C, <yhchu@w3.org>
//    Vasanthan Dasan, Sun, <vasanthan.dasan@central.sun.com>
//    Peter Lipp, University of Technology, Graz <plipp@iaik.tu-graz.ac.at>
//    Andreas Sterbenz, U. of Technology, Graz <sterbenz@iaik.tu-graz.ac.at>

package w3c.www.pics;

import java.math.BigInteger;
import java.security.interfaces.*;
import java.io.* ;
import java.util.*;
import java.net.*;
import java.security.*;

import w3c.tools.codec.*;
import w3c.tools.sexpr.*;
import w3c.tools.sorter.*;
import w3c.www.dsig.*;

/**
 * This class implements DSig Signature Labels as defined by the W3C's Digital
 * Signature Initiative.
 * Please read
 * <a href="http://www.w3.org/TR/WD-DSIG-label.html">DSig 1.0 Label Spec</a>
 * for more information.
 * In short, a DSigLabel is a valid
 * <a href="http://www.w3.org/pub/WWW/PICS/labels.html">PICS 1.1 Label</a>
 * which includes two PICS 1.1 extensions containing digital signature
 * information.
 * We anticipate that in the near future this will integrate into regular
 * PICS Label.
 * @see w3c.www.pics
 *
 * @author Andreas Sterbenz
 * @author Yang-hua Chu
 * @version 1.0beta (last modified 19-December-1997)
 */
public class SigLabel extends PICSLabel implements DSigLabelInterface {

  private static final boolean DEBUG = false;
  private ResInfoExtension resinfo;
  private SigBlockExtension sigblock;

  /**
   * Construct a DSig 1.0 label out of a PICS 1.1 label.
   * In case of an error while parsing the DSig extensions a DSigException
   * is thrown.
   * @exception DSigException if the DSig extensions cannot be parsed
   */
  public SigLabel(PICSLabel label) throws DSigException
  {
    super(label.getVersion(), label.getService(), label.getOptions(), label.getRatings());
    resinfo = new ResInfoExtension();
    sigblock = new SigBlockExtension();
    try {
      // yc-m
      if (getOption(resinfo.EXTENSION_ID) != null) {
	UserOptionValue optionVal = 
	  (UserOptionValue)getOption(resinfo.EXTENSION_ID);
	Vector v = optionVal.getVectorData();
	resinfo.parse(v);
      }
      if (DEBUG) System.out.println("\nThe resinfo is " + resinfo.toString());
      if (getOption(sigblock.EXTENSION_ID) != null) {
	UserOptionValue optionVal=
	  (UserOptionValue)getOption(sigblock.EXTENSION_ID);
	Vector v = optionVal.getVectorData();
	sigblock.parse(v);
      }
      if (DEBUG) System.out.println("\nThe sigblock is "+sigblock.toString());
    } catch (Exception e) {
      if(DEBUG) {
        System.err.println("SigLabel Error while parsing the DSig extensions");
        e.printStackTrace();
      }
      throw new DSigException("SigLabel Error while parsing the DSig extensions");
    }
  }

  /**
   * Parse the data from the InputStream into a SigLabel.
   * @exception DSigException in case of a parser error.
   * @exception IOException if there was an error reading from the InputStream
   */
  public SigLabel(InputStream ins) throws DSigException, IOException
  {
    this(parseStream(ins));
  }

  /**
   * Parse the InputStream into a PICSLabel and return it.
   * This method was necessary because Java insists that a constructor
   * is the first thing invoked from a constructor and allows no try/ catch
   * or anything.
   * @exception DSigException in case of a parser error.
   * @exception IOException if there was an error reading from the InputStream
   */
  private static PICSLabel parseStream(InputStream ins) 
       throws DSigException, IOException
  {
    PICSParser p = new PICSParser();
    try {
      return (PICSLabel)p.parse(ins);
    } catch( SExprParserException e ) {
      if (DEBUG) {
	System.out.println("SigLabel: error parsing stream");
	e.printStackTrace();
      }
      throw new DSigException("parsing error");
    }
  }

  /**
   * Parse the data from the String into a SigLabel.
   * @exception DSigException in case of a parser error.
   */
  public SigLabel(String labelString) throws DSigException
  {
    this(parseString(labelString));
  }

  /**
   * Parse the InputStream into a PICSLabel and return it.
   * This method was necessary because Java insists that a constructor
   * is the first thing invoked from a constructor and allows no try/ catch
   * or anything and we do not want to declare a never happening IOException
   * for SigLabel(String).
   */
  private static PICSLabel parseString(String labelString) 
       throws DSigException
  {
    try {
      return parseStream(new ByteArrayInputStream(labelString.getBytes()));
    } catch( IOException e ) {
      throw new DSigException(); // cannot occur with ByteArrayInputStreams anyway
    }
  }

  /**
   * Get the DSig Resource Information (resinfo).
   */
  public ResInfoExtension getResInfo()
  {
    return this.resinfo;
  }

  /**
   * Set the DSig Resource Information (resinfo).
   */
  public void setResInfo(ResInfoExtension resinfo)
  {
    this.resinfo = resinfo;
  }

  /**
   * Get the DSig Signature Block (sigblock)
   */
  public SigBlockExtension getSigBlock()
  {
    return this.sigblock;
  }

  /**
   * Set the DSig Signature Block (sigblock)
   */
  public void setSigBlock(SigBlockExtension sigblock)
  {
    this.sigblock = sigblock;
  }

  /**
   * The changes made in ResInfo and SigBlock extension is not automatic;
   * calling this method updates the original PICS1.1 Label and reflect
   * the changes in resinfo and sigblock.
   */
  public void updateDSig()
  {
    if (resinfo.size() == 0)
      super.removeOption(resinfo.EXTENSION_ID);
    else {
      UserOptionValue val = new UserOptionValue();
      val.setVectorData(resinfo.toVector());
      val.setOptional();
      val.setURL(resinfo.EXTENSION_ID);
      setOption(resinfo.EXTENSION_ID, val);
    }
    if (sigblock.size() == 0)
      super.removeOption(sigblock.EXTENSION_ID);
    else {
      UserOptionValue val = new UserOptionValue();
      val.setVectorData(sigblock.toVector());
      val.setURL(sigblock.EXTENSION_ID);
      val.setOptional();
      setOption(sigblock.EXTENSION_ID, val);
    }
  }

  /**
   * Unparse the DSigLabel to a normalized PICS1.1/DSig 1.0 string
   */
  public String toString()
  {
    updateDSig();
    return super.toString();
  }

  /**
   * Sign the label using the appropriate private key class 
   * recognized by the signature suite. Signing automatically
   * sets the ByName property to "[anonymous]", that should be
   * set appropriately after signing. If this signature suite
   * object already contains a signature, it will be replaced by
   * the new one.
   */
  public void sign(SignatureSuite sigsuite, PrivateKey privkey)
  {
    sigsuite.sign(this, privkey);
    sigblock.addSigSuite(sigsuite);
  }    

  /**
   * Verify the complete Signature Label. This will verify all
   * message digests present in the resinfo and all signatures
   * present in the sigblock. Therefore, this is a computationally
   * rather expensive operation that is often not required. Still,
   * this function is provided to allow verification with just one
   * function call. For other methods to verify the label see
   * the documentation.
   */
  public Trivalue verifyAll()
  {
    Trivalue result = Trivalue.newTrue();
    result = result.and(resinfo.verifyAll(this));     // verify all hashes
    result = result.and(sigblock.verifyAll(this));    // verify all signatures
    return result;
  }

  /**
   * Verify the label using the user's default policy. Currently this
   * is defined to be the same as verifyAll(), but this will change.
   */
  public Trivalue verify()
  {
    return verifyAll();
  }

  /**
   * Return the URL specified in the 'for' option of this label
   * or null if none is present.
   */
  public String getFor()
  {
    Symbol forsym = Symbol.makeSymbol("for", null);
    Object forobj = getOption(forsym);
    if( forobj != null ) {
      return forobj.toString();
    } else {
      return null;
    }
  }

  /**
   * Returns a String containing the canonical PICS 1.1 representation of 
   * this Label, suitable for signing.
   * @return A String with a valid canonicalized PICS 1.1 label 
   * representation of this Label object.
   **/
  public String toDSigString()
  {
    return toString();
  }


  /**
   * Returns the canonicallized form of the DSig label with fields
   * selectively included or excluded according the sigdata.  Please refer
   * to the DSig Label specification for more detail.
   * @exception DSigException if the format of the sigdata is incorrect.
   */
  public String toExcludedDSigString(Hashtable suiteValues) 
       throws DSigException
  {
    this.updateDSig();
    PICSLabel tmp_label = (PICSLabel) super.clone();
    if (suiteValues.get("exclude") != null) {
      Vector exclude = (Vector) suiteValues.get("exclude");
      for (int i=0; i<exclude.size(); i++) {
	Vector list = (Vector) exclude.elementAt(i);
	String type = (String) list.elementAt(0);
	for (int j=1; j<list.size(); j++) {
	  if (type.equals("options")) {
	    tmp_label.removeOption(list.elementAt(j));
           } else if (type.equals("extensions")) {
             tmp_label.removeOption(list.elementAt(j));
           } else if (type.equals("ratings")) {
             tmp_label.removeRating(list.elementAt(j));
           } else {
             System.err.println("Error: unknown exclude option: " + type);
           }
         } 
       }
     }
    tmp_label.removeOption(SigBlockExtension.EXTENSION_ID);
    return tmp_label.toString();
  }

  private final static boolean printKeys = false;
  private final static boolean generateKeys = false;


  /**
   * Another main() method for testing.
   * @exception Throwable if just about anything goes wrong.
   */
  public static void main(String args[])
    throws Throwable
  {
    String signalg="DSA";
    String labelfname = null;

    switch( args.length )
    {
      case 0:
        labelfname = "k:\\Projects\\DSig\\w3c\\src\\w3c\\www\\dsig\\test2.lbl";
        labelfname = "k:\\Projects\\DSig\\w3c\\src\\w3c\\www\\dsig\\ms.lbl";
//        labelfname = "k:\\Projects\\DSig\\w3c\\src\\w3c\\www\\dsig\\signed.lbl";
//        labelfname = "k:\\Projects\\DSig\\w3c\\src\\w3c\\www\\dsig\\index.html.lbl";
        break;
      default:
        labelfname = args[0];
        break;
    }

    System.out.println("*** Parsing the label...");
    SigLabel label = new SigLabel(new FileInputStream(labelfname));
 System.out.println("the pretty label is:\n" + label.prettyPrint());
 waitKey();
 if( true ) System.exit(0);

// System.out.println("for: " + label.getFor());
// System.out.println(label.prettyPrint());
// SigBlockExtension sigblock = label.getSigBlock();
// Trivalue vres = sigblock.verifyAll(label);
// System.out.println("Verification result: " + vres);

// waitKey();
// if( true ) System.exit(0);

    SigSuiteRegistry.printAlgorithms();
    //setup keys
    DSAPublicKey pubKey;
    DSAPrivateKey privKey;
    if( generateKeys ) {
      System.out.println("\n*** Generating a DSA key pair...");
      KeyPairGenerator keyGen;
      try {
        keyGen = KeyPairGenerator.getInstance(signalg);
      }
      catch (NoSuchAlgorithmException e) {
        System.out.println("*** Failed to obtain Keypair for: " + signalg);
        return;
      }
      keyGen.initialize(1024, new SecureRandom());
      KeyPair pair = keyGen.generateKeyPair();
      pubKey = (DSAPublicKey) pair.getPublic();
      privKey = (DSAPrivateKey) pair.getPrivate();
    } else {
      System.out.println("\n*** Using predefined DSA keys.");
      BigInteger x = new BigInteger("427680156495144453885115690373649756045959408862");
      BigInteger y = new BigInteger("177339384197349739065818268761102971597420546697560629529304621486431387773923117551613374450730394130117771062930596087436245169541121954274843478181992055685752245183950336677657126495585057247936207232282564918317794169791277754707537125797798694653557421342631893997403826497768230679355141661766921531808");
      BigInteger p = new BigInteger("178011905478542266528237562450159990145232156369120674273274450314442865788737020770612695252123463079567156784778466449970650770920727857050009668388144034129745221171818506047231150039301079959358067395348717066319802262019714966524135060945913707594956514672855690606794135837542707371727429551343320695239");
      BigInteger q = new BigInteger("864205495604807476120572616017955259175325408501");
      BigInteger g = new BigInteger("174068207532402095185811980123523436538604490794561350978495831040599953488455823147851597408940950725307797094915759492368300574252438761037084473467180148876118103083043754985190983472601550494691329488083395492313850000361646482644608492304078721818959999056496097769368017749273708962006689187956744210730");
      pubKey = new DSAPublicKeyClass(y, p, q, g);
      privKey = new DSAPrivateKeyClass(x, p, q, g);
    }
    if( printKeys ) {
      DSAParams params = pubKey.getParams();
      System.out.println("x = " + privKey.getX());
      System.out.println("y = " + pubKey.getY());
      System.out.println("p = " + params.getP());
      System.out.println("q = " + params.getQ());
      System.out.println("g = " + params.getG());
      waitKey();
      return;
    }
    SignatureSuite dss = SigSuiteRegistry.getInstance(SigSuiteRegistry.DSSURL);
    SigLabel signedlabel = (SigLabel) dss.sign(label, privKey);
    System.out.println("*** Adding 'by' information...");
    dss.setBy("ByKey", pubKey);
//    dss.setBy("ByHash", pubKey);
//    dss.setBy(signedlabel, "Andreas Sterbenz");
//    dss.setByHash(signedlabel, pubKey);
//    System.out.println("*** The signed label is:\n" + signedlabel.prettyPrint());
    System.out.println("*** The signed label is:\n" + signedlabel);
//    System.out.println("*** Verifying...");
//    System.out.println("*** Signature verification result: " + dss.verify(label, pubKey));
//    System.out.println("*** Signature verification result: " + dss.verify(label, null));

    System.out.println("Parsing the signed label...");
    SigLabel verifylabel = new SigLabel(signedlabel.toString());
    System.out.println("*** Verifying...");
    System.out.println("*** Signature verification result: " + dss.verify(verifylabel, null));

    waitKey();
  }

  /**
   * Return a (currently not too pretty) pretty print of this
   * label.
   */
  public String prettyPrint()
  {
    return prettyPrint(toString());
  }

  static String prettyPrint(String sexpr)
  {
    String t1 = insertNewLine(sexpr);
    String t2 = removeSpaces(t1);
    String t3 = indent(t2);
    while( t3.startsWith("\n") ) t3 = t3.substring(1);
    return t3;
  }

  private static String insertNewLine(String sexpr)
  {
    StringBuffer sb = new StringBuffer();
    boolean inquotes = false;
    for( int i=0; i<sexpr.length(); i++ ) {
      char c = sexpr.charAt(i);
      if( c == '"' ) inquotes = !inquotes;
      if( !inquotes ) {
        if( c == '(' ) {
          sb.append("\n(\n");
        } else if( c == ')' ) {
          sb.append("\n)\n");
        } else {
          sb.append(c);
        }
      } else {
        sb.append(c);
      }
    }
    return sb.toString();
  }

  private static String removeSpaces(String sexpr)
  {
    boolean changed;
    boolean inquotes = false;
    String t = sexpr;
    StringBuffer sb;
    do {
      sb = new StringBuffer();
      changed = false;
      char c, prev = '\0';
      for( int i=0; i<t.length(); i++ ) {
        c = t.charAt(i);
        if( c == '"' ) inquotes = !inquotes;
        if( !inquotes ) {
          if( prev == '\n' ) {
            if( (c == '\n') || (c == ' ') ) {
              changed = true;
              continue;
            }
          }
        }
        sb.append(c);
        prev = c;
      }
      t = sb.toString();
    } while( changed == true );
    return t;
  }

  private static String indent(String sexpr)
  {
    int count = 0;
    StringBuffer sb = new StringBuffer();
    boolean newline = false;
    boolean inquotes = false;
    for( int i=0; i<sexpr.length(); i++ ) {
      char c = sexpr.charAt(i);
      if( c == '"' ) inquotes = !inquotes;
      if( !inquotes ) {
        if( c == ')' ) {
          count--;
        } else if( c == '\n' ) {
          newline = true;
          continue;
        }
      }
      if( newline ) {
        newline = false;
        sb.append('\n');
        for( int j=0; j<count; j++ ) {
          sb.append("  ");
        }
      }
      if( c == '(' ) {
        count++;
      }
      sb.append(c);
    }
    return sb.toString();
  }

  static void waitKey()
  {
    System.out.println("Hit the <RETURN> key to continue.");
    try {
      do {
        System.in.read();
      } while( System.in.available() > 0 );
    } catch( java.io.IOException e ) {
      ;
    }
  }
}
