// SigSuiteECDSS.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.security.*;
import java.security.interfaces.*;
import java.math.BigInteger;
import java.util.Dictionary;
import java.util.Vector;

import w3c.www.dsig.*;
import w3c.www.pics.*;
import w3c.tools.codec.*;

/**
 * This class implements the DSS signature suite for the W3C's DSig
 * as defined in <TT>http://www.w3.org/PICS/DSig/ECDSS-1_0.html</TT>.
 * <P>
 * Application programmers should never access this class via its name
 * <CODE>w3c.www.dsig.SigSuiteECDSS</CODE>. You should use
 * <CODE>SigSuiteRegistry.getInstance()</CODE>,
 * <CODE>sigblock.getSigSuite()</CODE>, etc.
 * <P>
 *
 * @author Mark Champine
 * @version 1.0beta (last modified 25-November-1997)
 */
public final class SigSuiteECDSS extends SignatureSuite
{
  private static eccDsig ecdss = null;
  /**
   * Internal flag indicating debug mode.
   */
  private final static boolean DEBUG = false;

  private int keyLength = -1;

  /**
   * The string defined to indentify the ECDSS (Elliptic Curve with
   * Digital Signature Standard) signature suite implemented by this class,
   * <TT>http://www.w3.org/PICS/DSig/ECDSS-1_0.html</TT>.
   */
  public final static String ECDSSURL = 
  "http://www.w3.org/PICS/DSig/ECDSS-1_0.html";

  static {
    initialize();
  }

  /**
   * Initialize function that makes sure that provider implementing
   * DSS is present and stores the Class object of that class.
   */
  private static void initialize()
  {
    if( ecdss == null ) {
        ecdss = new eccDsig();    //create a signature object (which also loads the dll)
        ecdss.ecc_init();         //set up ecc parameters
        if (DEBUG) System.out.println("SigSuiteECDSS: Using Elliptic Curve implementation from Hewlett Packard");
    }
  }

  /**
   * Constructor for use by
   * <CODE>w3c.www.dsig.SigSuiteRegistry.getInstance()</CODE>.
   * Application programmers should not need to access this
   * constructor directly.
   * @exception NoSuchAlgorithmException if either ECC or DSS algorithms
   * cannot be found.
   */
  public SigSuiteECDSS() throws NoSuchAlgorithmException
  {
    super(ECDSSURL, "ECDSS");
    initialize();
    if( ecdss == null ) throw new NoSuchAlgorithmException();
  }

  /**
   * Return the length of the key used to generate this signature.
   * To be more precise, return the length of the number p, which is only
   * one part of the key. Note that the keylength will only be available
   * after <CODE>sign()</CODE> or <CODE>verify()</CODE> has been called,
   * otherwise this method will return -1.
   */
  public int getKeyLength()
  {
    return keyLength;
  }

  /**
   * Sign the given label using the given private key, which
   * needs to be an instance of a DSAPublicKey. 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 DSigLabelInterface sign(DSigLabelInterface label, PrivateKey privkey)
  {
    if (DEBUG) System.out.println("SigSuiteECDSS: sign called");
    try {
      EccPrivateKey eccprivkey = (EccPrivateKey)privkey;
      byte [] excluded = performExclude(label);
      byte [] tmp2 = eccprivkey.getEncoded();
      ecSig sig = ecdss.ecc_Sign(excluded, tmp2);

      if(DEBUG) System.out.println("Sig r: " + new String(sig.r) + ".");
      if(DEBUG) System.out.println("Sig s: " + new String(sig.s) + ".");

      addValuePair("SigCrypto", "R", new String(sig.r));
      addValuePair("SigCrypto", "S", new String(sig.s));

      //yc-d: label.getSigBlock().addSigSuite(this);
      setByName("[anonymous]");
      keyLength = 0;
      return label;
    } catch( Exception e ) {
      return null;
    }
  }

  /**
   * Verify if this signature suite object correctly signs the
   * given label with the given public key. pubkey must either
   * be an instance of DSAPublicKey or null. In that case the
   * public key will be extracted from the signature suite
   * if it is specified either as ByKey or as ByName (trying
   * a certificate lookup). If no valid public key can be found,
   * verify returns unknown. Else the signature will be verified
   * using that public key returning unknown if there was any
   * other problem verifying the signature or one of true/ false
   * to indicate the result.
   */
  public Trivalue verify(DSigLabelInterface label, PublicKey pubkey)
  {
    EccPubKey pubkey2;

    // check date (?)
    if( pubkey != null ) {
      if( !(pubkey instanceof EccPubKey) ) {
        if(DEBUG) System.out.println("SigSuiteECDSS: invalid public key parameter!");
        return Trivalue.newUnknown();
      }
      if(DEBUG) System.out.println("SigSuiteECDSS: verifying using the specified public key");
      pubkey2 = (EccPubKey)pubkey;
      return doVerify(label, pubkey2);
    } else { // pubkey == null, extract key from ByX in signature...
      String type = getByType();
      if( type.equals("ByKey") ) {
        if(DEBUG) System.out.println("SigSuiteECDSS: verifying using ByKey");
        pubkey2 = (EccPubKey)getBy();
        return doVerify(label, pubkey2);
      } else if( type.equals("ByName") ) {
        String bynamestr = (String)getBy();
        if(DEBUG) System.out.println("SigSuiteECDSS: verifying using ByName");
        if(DEBUG) System.out.println("SigSuiteECDSS: Signer name is '" + bynamestr + "'");
        if(DEBUG) System.out.println("SigSuiteECDSS: trying to obtain certificate for signer...");
        Certificate[] certs = CertDatabase.certForName(bynamestr);
        if(DEBUG) System.out.println("SigSuiteECDSS: " + certs.length + " certificates found for '" + bynamestr + "'");
        // try all certificates available for the individual
        for( int i=0; i<certs.length; i++ ) {
          try {
            pubkey2 = (EccPubKey)certs[i].getPublicKey();
            Trivalue res = doVerify(label, pubkey2);
            // if result is true, we are done
            // if result is unknown, we are done, too, because that
            // only happens if the DSS algorithm is not available at all
            // or if the signature is invalid
            // if result is false, we might have tried the wrong
            // DSA key for that individual and need to try again
            if( ! res.isFalse() ) return res;
          } catch( ClassCastException e ) {
            // ignore
          }
        }
        if(DEBUG) System.out.println("SigSuiteECDSS: no certificate with a DSA key found for this signer, cannot verify.");
        return Trivalue.newUnknown();
      } else if( type.equals("ByHash") ) {
        if(DEBUG) System.out.println("SigSuiteECDSS: verifying using ByHash (not implemented!)");
        // should somehow try to find a key matching that key-hash...
        return Trivalue.newUnknown();
      } else {
        if(DEBUG) System.out.println("SigSuiteECDSS: no by option found for signature, cannot verify");
        return Trivalue.newUnknown();
      }
    }
    // this point can never be reached
  }

  /**
   * Internal verify function, pubkey is never null and always
   * an instance of EccPubKey.
   */
  private Trivalue doVerify(DSigLabelInterface label, EccPubKey pubkey)
  {
    if(DEBUG) System.out.println("SigSuiteECDSS: doVerify called");
    try {
      byte [] excluded = performExclude(label);
      String r = (String) ((Dictionary)suiteValues.get("SigCrypto")).get("R");
      String s = (String) ((Dictionary)suiteValues.get("SigCrypto")).get("S");
      if(DEBUG) System.out.println("r = " + r);
      if(DEBUG) System.out.println("s = " + s);

      //build ecdss signature object from r and s values
      ecSig sig = new ecSig(r.getBytes(),s.getBytes());

	  //verify sig with pubKey and ecSig objects
	  boolean sv = ecdss.ecc_Verify(sig, excluded, pubkey);

      Trivalue result = new Trivalue(sv);
      if(DEBUG) System.out.println("SigSuiteECDSS: result is " + result);
      return result;
    } catch( Exception e ) { // NoSuchAlgorithmException, Base64FormatException
      return Trivalue.newUnknown();
    }
  }

  /**
   * Perform the hashing of the label data as defined in the DSS
   * signature suite specification. This includes stripping
   * the sigblock and other parts of the label as specified in
   * its exclude extension. The data is hashed into the given
   * signature object using the update method.
   * Internal use only.
   */
  // private byte [] performExclude(SigLabel label)
  private byte [] performExclude(DSigLabelInterface label)
    throws SignatureException, DSigException
  {
    // just hash the excluded label
    //     label.updateLabel();
    //     PICSLabel excludedLabel = exclude(label);
    //     if(DEBUG) System.out.println("\nThe excluded label is :" + excludedLabel.toString());
    //     return ((excludedLabel.toString()).getBytes());

    label.updateDSig();
    String excludedLabel = label.toExcludedDSigString(super.getSuiteValues());
    if (DEBUG) 
      System.out.println("\nThe excluded label is :"+excludedLabel);
    return excludedLabel.getBytes();
  }

  public String getByType()
  {
    Object byobj;
    if( (byobj = suiteValues.get("ByName")) != null ) return "ByName";
    if( (byobj = suiteValues.get("ByKey")) != null ) return "ByKey";
    if( (byobj = suiteValues.get("ByHash")) != null ) return "ByHash";
    return null;
  }

  public Object getBy()
  {
    String type = getByType();
    if( type != null ) {
      if( type.equals("ByName") ) return getByName();
      if( type.equals("ByKey") ) return getByKey();
      if( type.equals("ByHash") ) return getByHash();
      // no ByCert at the moment
    }
    return null;
  }

  private String getByName()
  {
    Object byobj;
    if( (byobj = suiteValues.get("ByName")) != null ) {
      try {
        String bynamestr = (String)byobj;
        return bynamestr;
      } catch( ClassCastException e ) {
      }
    }
    return null;
  }

  private PublicKey getByKey()
  {
    try {
      Object byobj = suiteValues.get("ByKey");
      Dictionary bykeydict = (Dictionary)byobj;
      byte [] x = ((String)bykeydict.get("X")).getBytes();
      byte [] y = ((String)bykeydict.get("Y")).getBytes();
      return new EccPubKey(x, y);
    } catch( Exception e ) { // ClassCastException, InvalidKeyException
      if(DEBUG) System.out.println("SigSuiteECDSS: invalid ByKey element found!");
    }
    return null;
  }

  private byte[] getByHash()
  {
    Object byobj;
    if( (byobj = suiteValues.get("ByHash")) != null ) {
      try {
        String b64digest = (String)byobj;
        return Base64ToByteArray(b64digest);
      } catch( ClassCastException e ) {
      }
    }
    return null;
  }

  public SignatureSuite setBy(String type, Object signer)
  {
    if( signer == null ) return null;
    if( type.equals("ByName") ) {
      return setByName(signer.toString());
    }
    if( type.equals("ByKey") && (signer instanceof PublicKey) ) {
      return setByKey((PublicKey)signer);
    }
    if( type.equals("ByHash") && (signer instanceof PublicKey) ) {
      return setByHash((PublicKey)signer);
    }
    if( type.equals("ByCert") ) {
      if(DEBUG) System.out.println("SigSuite: ByCert not implemented yet.");
    }
    if(DEBUG) System.out.println("SigSuite: Unknown type for By '" + type + "'.");
    return null;
  }

  /**
   * Set the ByX option for this signature suite to ByKey with the
   * values taken from the given public key. The key has to be an
   * instance of DSAPublicKey and should be the public key matching
   * the private key used for signing for this to make any sense.
   * This method has to be called <STRONG>after</STRONG> after
   * signing to have any effect.
   * @return the SignatureSuite itself if successful, null otherwise.
   */
  private SignatureSuite setByKey(PublicKey pubkeyin)
  {
    removeBy();
    try {
      EccPubKey pubkey = (EccPubKey)pubkeyin;
      addValuePair("ByKey", "Y", new String(pubkey.y));
      addValuePair("ByKey", "X", new String(pubkey.x));
    } catch( ClassCastException e ) {
      return null;
    }
    return this;
  }

  /**
   * Set the ByX option for this signature suite to ByHash with the
   * values taken from the given public key. Hashing is performed
   * using the SHA-1 message digest algorithm with the key encoded
   * as defined in the DSS Signature Suite Specification.
   * The key has to be an
   * instance of DSAPublicKey and should be the public key matching
   * the private key used for signing for this to make any sense.
   * This method has to be called <STRONG>after</STRONG> after
   * signing to have any effect.
   * @return the SignatureSuite itself if successful, null otherwise.
   */
  private SignatureSuite setByHash(PublicKey pubkeyin)
  {
    removeBy();
    try {
      EccPubKey pubkey = (EccPubKey)pubkeyin;
      MessageDigest md = HashRegistry.getInstance(HashRegistry.SHA1URL);
      md.update(pubkey.x);
      md.update(pubkey.y);
      byte[] bindigest = md.digest();
      suiteValues.put("ByHash", ByteArrayToBase64(bindigest));
    } catch( NoSuchAlgorithmException e ) {
      return null;
    } catch( ClassCastException e ) {
      return null;
    }
    return this;
  }

  /**
   * Set the ByX option for this signature suite to ByName with the
   * given name. In order to allow the verifier to successfully
   * verify the signature, the name should match the name of
   * an individual that has a certificate included in the attribution
   * info of the label, although this is not required.
   * This method has to be called <STRONG>after</STRONG> after
   * signing to have any effect.
   * @return the SignatureSuite itself, it never fails.
   */
  private SignatureSuite setByName(String name)
  {
    removeBy();
    suiteValues.put("ByName", name);
    return this;
  }
}
