// SigCert.java
// $Id: SigCert.java,v 1.1 1997/04/15 20:39:07 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.dsig;

import java.security.*;
import java.util.*;
import java.net.*;
import java.io.*;
import w3c.tools.sexpr.*;
import w3c.tools.codec.*;


/**
 * DSig SigBlock's Certificate class.
 *
 * This class implements certificates for inclusion in the AttribInfo section
 * of a DSig SigBlock. Instances of this class cannot be modified once
 * they have been created.
 * <P>
 * As defined in the SigBlock specification, the SigBlock supports arbitrary
 * certificates types, where the type is indicated by a URL. The certicate
 * itself can either be included as a BASE64 encoded string or referenced by
 * a URL. When creating an object, BASE64 encoded certificates are automatically
 * decoded and certificates referenced via a URL are automatically fetched.
 * <P>
 * If the certificate could be decoded successfully the CertFormatRegistry
 * is automatically queried for the appropriate certificate handling
 * class. If it is present, certificate is created (implements
 * java.security.Certificate) and added to the CertDatabase.
 * <P>
 *
 * @version 1.0beta (last modified 19-December-1997)
 * @author Andreas Sterbenz
 */
public class SigCert
{
  private static final boolean DEBUG = false;

  /**
   * Tag for certificates of undefined/ empty type.
   */
  protected static final int CERT_UNDEF = 0;
  /**
   * Tag for certificates present as base64 encoded data.
   */
  protected static final int CERT_BASE64 = 1;
  /**
   * Tag for certificates referenced via a URL.
   */
  protected static final int CERT_URLREF = 2;

  /**
   * How this certificate is referenced (CERT_UNDEF, CERT_BASE64,
   * CERT_URLREF).
   */
  private int certRefType;
  /**
   * The URL identifying the certificate format.
   */
  private String certType;
  /**
   * The encoded data.
   */
  private String certData;
  /**
   * The decoded data, or null if decoding failed.
   */
  private byte[] certBytes;
  /**
   * The certificate object created from this SigCert.
   */
  private Certificate certObj;

  final int BUFFER_SIZE = 8192;

  /**
   * Create an empty certificate object (mainly for use
   * by the parser).
   */
  SigCert()
  {
    this.certRefType = CERT_UNDEF;
    this.certType = null;
    this.certData = null;
    this.certBytes = null;
    this.certObj = null;
  }

  /**
   * Create a certificate with the format and value.
   * @exception DSigException if the certData cannot be parsed correctly.
   */
  public SigCert(String certType, String certData) throws DSigException
  {
    this.certType = certType;
    this.certData = certData;
    decodeCert();
    createCertObject();
  }

  /**
   * Create a new certificate of type certType, the certificate is read
   * from the InputStream ins and will be stored in BASE64 format.
   * @exception DSigException if the input cert cannot be parsed correctly.
   */
  public SigCert(String certType, InputStream ins) throws DSigException
  {
    this.certRefType = CERT_BASE64;
    this.certType = certType;
    try {
      this.certBytes = readStream(ins);
    } catch( IOException e ) {
      throw new DSigException();
    }
    Base64Encoder base64 = new Base64Encoder(new String(this.certBytes));
    this.certData = base64.processString();
    createCertObject();
  }

  /**
   * Create a SigCert from the given Certificate.
   * Requires that an appropriate certificate format is installed
   * in the CertFormatRegistry. In case of an error, a DSigException
   * is thrown.
   * @exception DSigException if the cert cannot be parsed correctly.
   */
  public SigCert(Certificate cert) throws DSigException
  {
    // this code is a little bit weird, but I don't think it's too bad
    CertDatabase.addCert(cert);
    this.certType = null;
    for( Enumeration e = CertFormatRegistry.formats(); e.hasMoreElements(); ) {
      String currentCertType = (String)e.nextElement();
      try {
        Class clazz = CertFormatRegistry.getImplementor(currentCertType);
        if( clazz.isInstance(cert) ) {
          this.certType = currentCertType;
          break;
        }
      } catch( NoSuchAlgorithmException exc ) {
        // ignore
      }
    }
    if( certType == null ) throw new DSigException();
    this.certRefType = CERT_BASE64;
    ByteArrayOutputStream outstream = new ByteArrayOutputStream();
    try {
      cert.encode(outstream);
    } catch( Exception e ) { // KeyException, IOException
      throw new DSigException();
    }
    this.certBytes = outstream.toByteArray();
    this.certObj = cert;
    Base64Encoder base64 = new Base64Encoder(new String(this.certBytes));
    this.certData = base64.processString();
  }

  /**
   * Read the complete InputStream from ins and return its contents as
   * a byte array.
   */
  private byte[] readStream(InputStream ins)
    throws IOException
  {
    byte buffer[] = new byte[BUFFER_SIZE];
    byte oldbuffer[] = null;
    int nmax = BUFFER_SIZE;
    int offset = 0;
    int n = -1;

    while( (n = ins.read(buffer, offset, nmax-offset)) > 0 ) {
      offset += n;
      if( offset >= nmax ) {
        oldbuffer = buffer;
        buffer = new byte[2*nmax];
        System.arraycopy(oldbuffer, 0, buffer, 0, nmax);
        nmax <<= 1;
      }
    }
    oldbuffer = buffer;
    buffer = new byte[offset];
    System.arraycopy(oldbuffer, 0, buffer, 0, offset);
    return buffer;
  }

  /**
   * Decode the certificate, i.e. decode BASE64 encoded certs or fetch the
   * certificate if specified via a URL. This method sets
   * certRefType and certBytes.
   * @exception DSigException for invalid certificates.
   */
  private void decodeCert() throws DSigException
  {
    if( certData.indexOf(":") != -1 ) {
      // cert is a URL reference
      this.certRefType = CERT_URLREF;
      try {
        InputStream ins = new URL(certData).openStream();
        this.certBytes = readStream(ins);
      } catch( IOException e ) {
        this.certBytes = null;
        if(DEBUG) System.out.println("problem fetching URL " + certData);
        throw new DSigException("problem fetching URL " + certData);
      }
    } else {
      // cert is base64 encoded
      this.certRefType = CERT_BASE64;
      try {
        Base64Decoder decoder = new Base64Decoder(certData);
        this.certBytes = (decoder.processString()).getBytes();
      } catch( Base64FormatException e ) {
        this.certBytes = null;
        if(DEBUG) System.out.println("error decoding base64");
        throw new DSigException("error decoding base64");
      }
    }
  }

  /**
   * Return the format URL.
   */
  public String getFormat()
  {
    return this.certType;
  }

  /**
   * Return the encoded data of this certificate (BASE64 or URL).
   * Application programmers will generally not need this method.
   */
  public String getData()
  {
    return this.certData;
  }

  /**
   * Return the raw certificate data. This is the actual certificate data,
   * BASE64 decoded or fetched from the given URL.
   * Application programmers will generally not need this method.
   */
  public byte[] getRawCert()
  {
    return this.certBytes;
  }

  /**
   * Return a Certificate object conforming to the java.security API
   * for this object. Returns null if none is available (certicate format
   * not installed in the registry, certificate invalid).
   */
  public Certificate getCertObj()
  {
    createCertObject();
    return this.certObj;
  }

  /**
   * Return true if the certificate is Base64 encoded.
   */
  public boolean isBase64Cert()
  {
    return certRefType == CERT_BASE64;
  }

  /**
   * Return true if the certificate is referenced via a URL.
   */
  public boolean isUrlCert()
  {
    return certRefType == CERT_URLREF;
  }

  /**
   * Return true if this certificate is valid, that means
   * not corrupted. For base64 encoded
   * certificates, that means they could not be decoded, for URL
   * references, that the object could not be retrieved.
   */
  public boolean isValid()
  {
    return certBytes != null;
  }

  /**
   * Try to create a certificate object from the decoded certBytes.
   * If successful the certificate will automatically added to
   * the certificate database.
   * Return true on success.
   */
  private boolean createCertObject()
  {
    if( certObj != null ) return true;
    if( certBytes != null ) {
      try {
        certObj = CertFormatRegistry.getInstance(certType);
        certObj.decode(new ByteArrayInputStream(certBytes));
        CertDatabase.addCert(certObj);
        return true;
      } catch( Exception e ) {
        // ignore
      }
    }
    certObj = null;
    return false;
  }

  /**
   * Parse the certificate
   */
  void parse(Vector data)
  {
    certType = (String) data.elementAt(0);
    certData = (String) data.elementAt(1);
    try {
      decodeCert();
      createCertObject();
    } catch( DSigException e ) {
      // catch the exception as it would cause the parser to halt
    }
  }

  /**
   * Return the SigCert as a Vector
   */
  Vector toVector()
  {
    Vector v = new Vector();
    v.addElement(certType);
    v.addElement(certData);
    return v;
  }

  /**
   * Return the SigCert as a String
   */
  public String toString()
  {
      return DSigUtil.toString(toVector());
  }
//   public String toString()
//   {
//     return PICSStream.toString(toVector());
//   }

}
