// SignatureSuite.java
// (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.dsig;

import java.io.*;
import java.util.*;
import java.security.*;
import java.math.BigInteger;
import w3c.www.dsig.*;
import w3c.tools.sexpr.*;
import w3c.tools.codec.*;

/**
 * The class <CODE>w3c.www.dsig.SignatureSuite</CODE> implements a generic
 * DSig Signature Suite as specified in the DSig Label specification.
 * Implementations of specific Signature Suites need to subclass
 * this class, it therefore also defines the basic API for all signature
 * suites.
 * <P>
 * Applications will never need to create instances of this class
 * using the constructors. Instances will either be created when
 * using <CODE>w3c.www.dsig.SigSuiteRegistry.getInstance(urlString)</CODE>
 * or automatically by the parser (which uses the same mechanism
 * insternally). For a more detailed description of the API for
 * application programmers please consult the
 * <EM>Using the W3C's DSig Reference Implementation</EM> document.
 * <P>
 * Programmers who want to write their on signature suite for the
 * DSig implementation will need to do the following:
 * <OL>
 * <LI>Create a subclass of <CODE>w3c.www.dsig.SignatureSuite</CODE>
 * <LI>Override the following dummy methods of the baseclass:
 *   <UL>
 *   <LI><CODE>sign()</CODE>
 *   <LI><CODE>verify()</CODE>
 *   <LI>the <CODE>setBy()</CODE> and <CODE>getBy()</CODE> methods
 *   <LI>the <CODE>getKeyLength()</CODE> method (not required but recommended)
 *   <LI>the class also needs to have a public constructor without
 * parameters that calls <CODE>super(url, shortName)</CODE> where url is the string
 * identifying the algorithm it implements and shortName is the string that
 * would identify the algorithm according to the Java Security naming conventions.
 * ShortName may also be null.
 *   </UL>
 * <LI>Register the signature suite using
 * <CODE>w3c.www.dsig.SigSuiteRegistry.addAlgorithm(MyUrl,&nbsp;MySigSuiteClass.getClass());</CODE>
 * </OL>
 * For a sample implementation of a signature suite 
 * see <CODE>w3c.www.dsig.SigSuiteDSS</CODE>.
 *
 * @see w3c.www.dsig.SigSuiteDSS
 * @author Andreas Sterbenz
 * @author Yang-hua Chu
 * @version 1.0beta (last modified 25-November-1997)
 */
public class SignatureSuite {

  /**
   * Internal flag indicating debug mode.
   */
  private static final boolean DEBUG = false;

  /**
   * The URL identifying this signature suite.
   */
  private String suiteURL;

  /**
   * The short name for the algorithm implemented by this signature suite.
   */
  private String shortName;

  /**
   * A hashtable with the (name, value) pairs of this signature
   * suite.
   */
  protected Hashtable suiteValues;

  /**
   * The string identifying a signature suite in a label,
   * <TT>Signature</TT>.
   */
  public static final String SIGSUITE_ID = "Signature";

  /**
   * Constructor without parameters, for use by the parser.
   * Applications will never need to create instances of a signature
   * suite via a constructor, see the comments at the top of this class.
   */
  SignatureSuite()
  {
    this("<uninitialized>", null);
  }

  /**
   * Constructor with the identifying URL as parameter, for use
   * by subclasses.
   * Applications will never need to create instances of a signature
   * suite via a constructor, see the comments at the top of this class.
   */
  protected SignatureSuite(String suiteURL, String shortName)
  {
    this.suiteURL = suiteURL;
    this.shortName = shortName;
    this.suiteValues = new Hashtable();
  }

  /**
   * Return the SigSuite key-value pair
   */
  public final Hashtable getSuiteValues()
  {
    return suiteValues;
  }

  /**
   * Return the URL identifying this signature suite object.
   */
  public final String getURL()
  {
    return suiteURL;
  }

  /**
   * Return the short name of the cryptographic algorithm implemented
   * by this signature suite. This should be the name used to identify
   * the signature algorithm in the Java Security API, e.g. MD5/RSA.
   * If no such equivalent exists this method returns null.
   * <P>
   * Note that this name is purely informational, in particular
   * there is absolutely no guarantee that there is only one
   * signature suite for each short name or that this signature suite
   * performs exactly like the algorithm in the Java Security API, nor
   * does it imply that this algorithm is installed in the Java Security
   * API at all. However, it still <EM>might</EM> be usefull to determine
   * which types of keys the signature suite accepts.
   */
  public final String getShortName()
  {
    return shortName;
  }

  /**
   * Add the pair (key, val) as an S-Expression under entryname,
   * entryname and key should be Strings. The result of this call will be:
   * <PRE>
   *   (Signature
   *      ...
   *      (entryname
   *         (key val)
   *      )
   *   )
   * </PRE>
   */
  protected void addValuePair(Object entryname, Object key, Object val)
  {
    Object entryval = suiteValues.get(entryname);
    if (entryval == null) {
      Hashtable tbl = new Hashtable();
      suiteValues.put(entryname, tbl);
      tbl.put(key, val);
      return;
    }
    if (entryval instanceof Dictionary) {
      ((Dictionary)entryval).put(key, val);
      return;
    }
    // This is bad, but it should never happen.
    suiteValues.remove(entryname);      // this loses the existing entryval!
    Hashtable tbl = new Hashtable();
    suiteValues.put(entryname, tbl);
    tbl.put(key, val);
    System.err.println("Error in SignatureSuite.addValuePair: you seem to be running a buggy application!");
  }

  /**
   * Verify if this signature suite object signs the given
   * label using the given public key. If the pubKey is null,
   * the public key specified in the given label will be used
   * if possible (usually with byKey or byName).
   * <P>
   * The SignatureSuite base class only provides a dummy function
   * the subclasses need to override.
   */
  public Trivalue verify(DSigLabelInterface label, PublicKey pubkey)
  {
    if(DEBUG) System.out.println("SignatureSuite: top level verify method called?!");
    return Trivalue.newUnknown();
  }

  /**
   * Sign the PICSLabel using the given private key and update
   * the label with the signature. If successful, return the
   * label, if signing could not be performed for some reason,
   * return null.
   * <P>
   * The SignatureSuite base class only provides a dummy function
   * the subclasses need to override.
   */
  public DSigLabelInterface sign(DSigLabelInterface label, PrivateKey privkey)
  {
    if(DEBUG) System.out.println("SignatureSuite: top level sign method called?!");
    return null;
  }

  /**
   * Return the length of the key used to create this signature in
   * bits, or -1 if it is unknown.
   * Note that this size is algorithm dependent, e.g. 512
   * bits for RSA are drastically different from 512 for some
   * elliptic curve algorithm. Also note, that for some signature
   * suites it might not be possible to deduce the keysize from a
   * signature or it might be available only after
   * signature verification was performed (and the keylength could
   * be deduced from the public key given there).
   * <P>
   * The SignatureSuite base class only provides a dummy function
   * the subclasses need to override.
   */
  public int getKeyLength()
  {
    return -1;
  }

  /**
   * Set the <CODE>By</CODE> information in the label. Set the <CODE>ByX</CODE>
   * type to <CODE>type</CODE> with the value <CODE>signer</CODE>. If
   * a signature suite does not support the given type, the by information
   * remains unchanged and this method returns null.
   * <P>
   * How <CODE>signer</CODE> is interpreted and what type it needs to have
   * is signature suite dependent. However, when using <CODE>ByKey</CODE> or
   * <CODE>ByHash</CODE> signer always has to be an instance of <CODE>PublicKey</CODE>,
   * for <CODE>ByName</CODE> it has to be a string.
   * <P>
   * This method has to be called <STRONG>after</STRONG> after
   * signing to have any effect.
   * <P>
   * The SignatureSuite base class only provides a dummy function
   * the subclasses need to override.
   */
  public SignatureSuite setBy(String type, Object signer)
  {
    return null;
  }

  /**
   * Return the type of the 'By' information used in this signature
   * suite object. It can be 'ByName', 'ByKey', 'ByHash', or a
   * signature suite dependent string. If the 'By' information is not
   * available, this methods returns null.
   * <P>
   * The SignatureSuite base class only provides a dummy function
   * the subclasses need to override.
   */
  public String getByType()
  {
    return null; 
  }

  /**
   * Return the contents of the <CODE>ByX</CODE> field. The type of the
   * returned object will be the same as the type of the object when
   * using <CODE>setBy()</CODE>, however, for <CODE>ByHash</CODE> this method
   * of course cannot return the key itself, it will a byte array of the hash
   * of the key as it is contained in the label. 
   * <P>
   * In case of an error, this method returns null.
   * <P>
   * The SignatureSuite base class only provides a dummy function
   * the subclasses need to override.
   */
  public Object getBy()
  {
    return null;
  }

  /**
   * Removes all ByX entries from the signature suite. This is automatically
   * called before a new ByX option is set, so there is no need for
   * a user program to use this method.
   */
  protected void removeBy()
  {
    if( suiteValues.containsKey("ByKey") ) suiteValues.remove("ByKey");
    if( suiteValues.containsKey("ByName") ) suiteValues.remove("ByName");
    if( suiteValues.containsKey("ByHash") ) suiteValues.remove("ByHash");
    if( suiteValues.containsKey("ByCert") ) suiteValues.remove("ByCert");
  }

  /**
   * Exclude label options, extension options, and ratings from the label
   * and create a new copy of the excluded label. This is for use
   * by subclasses.
   */
//   protected PICSLabel exclude(PICSLabel label)
//   {
//     String s = "";
//     PICSLabel tmp_label = (PICSLabel) label.clone();
//     // always remove the SigBlock Extension
//     tmp_label.removeOption(SigBlockExtension.EXTENSION_ID);
//     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);
//           }
//         }
//       }
//     }
//     return tmp_label;
//   }


  /**
   * Parse the input data into a geneirc Sigature Suite object. It contains
   * two types of information: the SigSuite URL and a Hashtable of the
   * key-value pair.
   * @exception DSigException if the data cannot be parsed correctly.
   */
  public void parse(Vector data)
    throws DSigException
  {
    String id = (String)data.elementAt(0);
    if (! id.equals(SIGSUITE_ID)) {
      throw new DSigException (this, id, SIGSUITE_ID, "Bad SigSuite identifier");
    }
    this.suiteURL = (String)data.elementAt(1);
    for (int i=2; i<data.size(); i++) {
      Vector pair = (Vector) data.elementAt(i);
      Object val = pair.elementAt(1);
      if (val instanceof Vector) {
        Hashtable tbl = new Hashtable();
        for (int j=0; j< ((Vector)val).size(); j++) {
  	      Vector subpair = (Vector) ((Vector) val).elementAt(j);
  	      tbl.put(subpair.elementAt(0), subpair.elementAt(1));
        }
        val = tbl;
      }
      suiteValues.put(pair.elementAt(0), val);
    }
  }

  /**
   * Another method for the parser.
   * @exception DSigException if the data cannot be parsed correctly.
   */
  public static SignatureSuite parse2(Vector data)
    throws DSigException
  {
    if(DEBUG) System.out.println("SignatureSuite: parse2 called");
    String id = (String)data.elementAt(0);
    if (! id.equalsIgnoreCase(SIGSUITE_ID))
      throw new DSigException ("Bad SigSuite identifier");
    String URL = (String)data.elementAt(1);
    SignatureSuite suite;
    if(DEBUG) System.out.print("SignatureSuite: trying to get suite for " + URL + "...");
    try {
      suite = SigSuiteRegistry.getInstance(URL);
      if(DEBUG) System.out.println("succeeded.");
    } catch( NoSuchAlgorithmException e ) {
      suite = new SignatureSuite();
      if(DEBUG) System.out.println("failed.");
    }
    suite.parse(data);
    return suite;
  }

  /**
   * Encode an array of bytes in Base 64 encoding. Included here as
   * signature suite implementations will frequently need it.
   */
  public static String ByteArrayToBase64(byte[] ba)
  {
    String bastring = new String(ba);
    Base64Encoder base64enc = new Base64Encoder(bastring);
    return base64enc.processString();
  }
  
  /**
   * Decode a Base 64 encoded byte array. Returns null
   * if the input was not properly encoded.
   * Included here as signature suite implementations will
   * frequently need it.
   */
  public static byte[] Base64ToByteArray(String b64)
  {
    try {
      Base64Decoder base64dec = new Base64Decoder(b64);
      String bastring = base64dec.processString();
      return bastring.getBytes();
    } catch( Base64FormatException e ) {
      return null;
    }
  }

  /**
   * Encode a BigInteger in Base 64 encoding. Included here as
   * signature suite implementations will frequently need it.
   */
  public static String BigIntToBase64(BigInteger bi)
  {
    return ByteArrayToBase64(bi.toByteArray());
  }

  /**
   * Decode a Base 64 encoded BigInteger. Return the BigInteger
   * object or null if the input was not properly encoded.
   * Included here as signature suite implementations will
   * frequently need it.
   */
  public static BigInteger Base64ToBigInt(String b64)
  {
    byte[] bibytes = Base64ToByteArray(b64);
    if( bibytes == null ) return null;
    return new BigInteger(bibytes);
  }

  /**
   * Convert a BigInteger to a byte array in the format suitable
   * for hashing of integers. That is, prefixed with one or more
   * length bytes as defined in the sample signature specifications
   * for DSS and RSA from the W3C.
   * <P>
   * The format produced is compatible with ASN.1 BER encoding of
   * integers, it only lacks the leading object identifier.
   */
  public static byte[] BigInteger2ByteArrayWithLengthPrefix(BigInteger bi)
  {
    byte[] bibytes = bi.toByteArray();
    int bilen = bibytes.length;
    byte [] res;
    if( bilen <= 127 ) {
      res = new byte[1+bilen];
      res[0] = (byte)bilen;
      System.arraycopy(bibytes, 0, res, 1, bilen);
    } else {
      if( bilen > 32767 ) return null;
      res = new byte[3+bilen];
      res[0] = (byte) 0x80 | 2;
      res[1] = (byte)((bilen >> 8) & 0xff);
      res[2] = (byte)((bilen) & 0xff);
      System.arraycopy(bibytes, 0, res, 3, bilen);
    }
    return res;
  }

  private Vector dictionaryToVector(Dictionary dict, Vector v)
  {
    Enumeration keys = dict.keys();
    while (keys.hasMoreElements()) {
      String key = (String) keys.nextElement();
      Object val = dict.get(key);
      if (val instanceof Dictionary)
      val = dictionaryToVector((Dictionary) val, new Vector());  // recursion
      Vector pair = new Vector();
      pair.addElement(key);   // return to work on!!!
      pair.addElement(val);
      v.addElement(pair);
    }
    return v;
  }

  public Vector toVector()
  {
    Vector v = new Vector();
    v.addElement("Signature");
    v.addElement(getURL());
    Vector tmp = DSigUtil.dictionaryToVector(this.suiteValues);
    for (int i=0; i<tmp.size(); i++)
      v.addElement(tmp.elementAt(i));
    return v;
  }


  /**
   * Return a string representation of this signature suite,
   * suitable for inclusion in a Signature Label.
   */
  public final String toString()
  {
    return DSigUtil.toString(toVector());
  }

//   /**
//    * Convert this signature suite to a vector. For internal use.
//    */
//   Vector toVector()
//   {
//     Vector v = new Vector();
//     v.addElement("Signature");
//     v.addElement(getURL());
//     return dictionaryToVector(this.suiteValues, v);
//   }

//   /**
//    * Return a string representation of this signature suite,
//    * suitable for inclusion in a Signature Label.
//    */
//   public String toString()
//   {
//     return PICSStream.toString(toVector());
//   }
}
