// SigSuiteRSA.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.crypto.us.dsig;

import java.util.*;
import java.math.BigInteger;
import java.security.*;
import java.security.interfaces.*;
import w3c.tools.codec.*;
import w3c.www.dsig.*;
import COM.rsa.jsafe.*;

/**
 * Generic DSig Signature Suite for suites using the RSA signature
 * algorithm and the conventions like those defined for RSA/MD5 and
 * RSA/SHA1 in the DSig signature suite specifications. This class
 * cannot be used directly, it needs to be subclassed for each particular
 * hashing algorithm to be used. The subclass only needs to implemement
 * a public constructor with no arguments that calls
 * <CODE>super(String suiteURL, String shortname, MessageDigest hash)</CODE> 
 * where <CODE>hash</CODE> is an instance of the message digest algorithm 
 * to be used, and shortname be either RSAMD5 or RSASHA1.
 * <P>
 * Note: the implementation of RSA algorithm is provided by RSA Security
 * Inc. (JSAFE COM.rsa.jsafe Java package).  Therefore, you need to have
 * the JSAFE package in order for this class (and others) to run properly.
 * <P>
 *
 * @author Mark Champine
 * @see     w3c.crypto.us.dsig.SigSuiteRSASHA1
 * @see     w3c.crypto.us.dsig.SigSuiteRSAMD5
 * @version 1.0 (last modified 10-February-1998)
 */
public abstract class SigSuiteRSA extends SignatureSuite {

  private final static boolean DEBUG = false;
  private MessageDigest hash;
  private int keyLength;

  /**
   * Constructor for use by its subclasses.  <code>suiteURL</code>
   * is the URL of the SigSuite.  <code>shortname</code> 
   * is the short name of the URL.  <code>hash</code> is the message digest
   * algorithm used to hash to label before signing for verification
   * of RSA is performed.
   */
  protected SigSuiteRSA(String suiteURL, String shortName, MessageDigest hash)
  {
    super(suiteURL, shortName);
    this.hash = hash;
    this.keyLength = -1;
  }

  /**
   * Returns the key length of the RSA key used.  Returns -1 if
   * no key has been initialized.
   */
  public int getKeyLength()
  {
    return keyLength;
  }

  /**
   * Returns the hash of the label with options removed according to
   * the <code>include</code> and <code>exclude</code> options in sigblock.
   */
  private synchronized byte[] hashLabel(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());
    //     hash.reset();
    //     hash.update((excludedLabel.toString()).getBytes());
    //     byte[] hasharray = hash.digest();
    //     return hasharray;
    label.updateDSig();
    String excludedLabel = label.toExcludedDSigString(super.getSuiteValues());
    if (DEBUG) System.out.println("\nThe excluded label is :"+excludedLabel);
    hash.reset();
    hash.update(excludedLabel.getBytes());
    byte[] hasharray = hash.digest();
    return hasharray;
  }

  /** 
   * Verify if this signature suite object correctly signs the
   * given label with the given public key. pubkey must either
   * be an instance of w3c.crypto.us.dsig.RSAPublicKeyImpl 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.
   * @see  w3c.crypto.us.dsig.RSAPublicKeyImpl
   */
  public Trivalue verify(DSigLabelInterface label, PublicKey pubkey)
  {
    RSAPublicKey rsapubkey;
    // check date (?)
    if( pubkey != null ) {
      if( !(pubkey instanceof RSAPublicKey) ) {
        if (DEBUG) 
	  System.out.println("SigSuiteRSA: invalid public key parameter!");
        return Trivalue.newUnknown();
      }
      if (DEBUG) System.out.println("SigSuiteRSA: verifying using " + 
				    "the specified public key");
      rsapubkey = (RSAPublicKey)pubkey;
      return doVerify(label, rsapubkey);
    } else { // pubkey == null, extract key from ByX in signature...
      String type = getByType();
      if( type.equals("ByKey") ) {
        if(DEBUG) System.out.println("SigSuiteRSA: verifying using ByKey");
        rsapubkey = (RSAPublicKey)getBy();
        return doVerify(label, rsapubkey);
      } else if( type.equals("ByName") ) {
        String bynamestr = (String)getBy();
        if(DEBUG) System.out.println("SigSuiteRSA: verifying using ByName");
        if(DEBUG) System.out.println("SigSuiteRSA: Signer name is '" 
				     + bynamestr + "'");
        if(DEBUG) System.out.println("SigSuiteRSA: trying to obtain certificate for signer...");
        Certificate[] certs = CertDatabase.certForName(bynamestr);
        if(DEBUG) System.out.println("SigSuiteRSA: " + certs.length + " certificates found for '" + bynamestr + "'");
        // try all certificates available for the individual
        for( int i=0; i<certs.length; i++ ) {
          try {
            rsapubkey = (RSAPublicKey)certs[i].getPublicKey();
            Trivalue res = doVerify(label, rsapubkey);
            if( ! res.isFalse() ) return res;
          } catch( ClassCastException e ) {
            // ignore
          }
        }
        if(DEBUG) System.out.println("SigSuiteRSA: no certificate with a RSA key found for this signer, cannot verify.");
        return Trivalue.newUnknown();
      } else if( type.equals("ByHash") ) {
        if(DEBUG) System.out.println("SigSuiteRSA: 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("SigSuiteRSA: 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 RSAPublicKey.
   */
  private Trivalue doVerify(DSigLabelInterface label, RSAPublicKey pubkey)
  {
    boolean sv;

    RSAPublicKeyImpl rsapubkey = 
      new RSAPublicKeyImpl(pubkey.getExponent(), pubkey.getModulus());
    if(DEBUG) System.out.println("SigSuiteRSA: doVerify called");
    try {
      byte[] msg = hashLabel(label);
      //System.out.println("msg:\n" + hexDump(msg));
      String sigbase64 = (String)suiteValues.get("SigCrypto");
      byte[] sig = Base64ToByteArray(sigbase64);
      //System.out.println("sig:\n" + hexDump(sig));

      //JSAFE doesn't provide a "decrypt" interface, so we'll have to let it
      //do the actual verify, passing in the hashed message.
      //sv = rsapubkey.verify(1, msg, sig);
      sv = rsapubkey.verify(msg, sig);

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

  /** 
   * Sign the given label using the given private key, which
   * needs to be an instance of w3c.crypto.us.dsig.RSAPrivateKeyImpl.
   * 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.
   * @see  w3c.crypto.us.dsig.RSAPrivateKeyImpl
   */
  public DSigLabelInterface sign(DSigLabelInterface label, PrivateKey privkey)
  {
    if(DEBUG) System.out.println("SigSuiteRSA: sign called");
    RSAPrivateKeyImpl rsaprivkey;
    try {
      if (!(privkey instanceof RSAPrivateKeyImpl) ) 
	throw new DSigException("SigSuiteRSA: invalid private key parameter!");
      else 
	rsaprivkey = (RSAPrivateKeyImpl)privkey;
      byte[] msg = hashLabel(label);
      //      System.out.println(hexDump(msg));
      //yc: byte[] sig = rsaprivkey.encrypt(1, msg);
      byte[] sig = rsaprivkey.encrypt(msg);
      //      System.out.println(hexDump(sig));
      suiteValues.put("SigCrypto", ByteArrayToBase64(sig));
      //      label.getSigBlock().addSigSuite(this);
      // add date (?)
      setByName("[anonymous]");
      return label;
    } catch( Exception e ) {
      if (DEBUG) {
	System.out.println("SigSuiteRSA: error occured during signing");
	e.printStackTrace();
      }
      return null;
    }
  }

  /**
   * Set the By information for this signature suite to the type
   * type and the value to signer. Supported by this signature suite
   * are "ByName", which uses signer.toString() as the value;
   * and "ByKey" and "ByHash", in which cases signer has to be an 
   * instance of DSAPublicKey. The method returns this signature suite
   * itself for success or null if there was an error.
   * <EM>NOTE:</EM> This method has to be called after signing in order
   * to have any effect.
   */
  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.
   */
  private SignatureSuite setByKey(PublicKey pubkey)
  {
    removeBy();
    try {
      RSAPublicKey rsapubkey = (RSAPublicKey)pubkey;
      BigInteger n = rsapubkey.getModulus();
      BigInteger e = rsapubkey.getExponent();
      addValuePair("ByKey", "N", BigIntToBase64(n));
      addValuePair("ByKey", "E", BigIntToBase64(e));
    } 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 respective hashing algorithm when the class was initialized.
   * The key has to be an instance of w3c.crypto.us.dsig.RSAPublicKeyImpl
   * matching the private key used for signing for this to make any sense.
   */
  private synchronized SignatureSuite setByHash(PublicKey pubkey)
  {
    removeBy();
    try {
      RSAPublicKey rsapubkey = (RSAPublicKey)pubkey;
      BigInteger n = rsapubkey.getModulus();
      BigInteger e = rsapubkey.getExponent();
      hash.reset();
      hash.update(BigInteger2ByteArrayWithLengthPrefix(n));
      hash.update(BigInteger2ByteArrayWithLengthPrefix(e));
      byte[] bindigest = hash.digest();
      suiteValues.put("ByHash", ByteArrayToBase64(bindigest));
    } 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.
   */
  private SignatureSuite setByName(String name)
  {
    removeBy();
    suiteValues.put("ByName", name);
    return this;
  }

  /**
   * Return the type of the By information given in this signature suite.
   * This will return either one of the string  "ByName", "ByKey", or
   * "ByHash" or null.
   */
  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;
  }

  /**
   * Return the By information itself. For ByName, this will return the
   * name as string, for ByKey it will return the name as a DSAPublicKey,
   * and for ByHash it will return the hash data as a byte array. In case
   * of any error or if no known by information was found this method
   * 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;
      BigInteger e, n;
      e = Base64ToBigInt( (String) bykeydict.get("E"));
      n = Base64ToBigInt( (String) bykeydict.get("N"));
      return new RSAPublicKeyImpl(e, n);
    } catch( Exception e ) { // ClassCastException, InvalidKeyException
      if(DEBUG) System.out.println("SigSuiteRSA: 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;
  }
}
