//////////////////////////////////////////////////////////////////////////// 
// 
// Copyright (C) DSTC Pty Ltd (ACN 052 372 577) 1993, 1994, 1995.
// Unpublished work.  All Rights Reserved.
// 
// The software contained on this media is the property of the
// DSTC Pty Ltd.  Use of this software is strictly in accordance
// with the license agreement in the accompanying LICENSE.DOC 
// file. If your distribution of this software does not contain 
// a LICENSE.DOC file then you have no rights to use this 
// software in any manner and should contact DSTC at the address 
// below to determine an appropriate licensing arrangement.
// 
//      DSTC Pty Ltd
//      Level 7, GP South
//      University of Queensland
//      St Lucia, 4072
//      Australia
//      Tel: +61 7 3365 4310
//      Fax: +61 7 3365 4311
//      Email: jcsi@dstc.qut.edu.au
// 
// This software is being provided "AS IS" without warranty of
// any kind.  In no event shall DSTC Pty Ltd be liable for
// damage of any kind arising out of or in connection with
// the use or performance of this software.
// 
//////////////////////////////////////////////////////////////////////////// 

package com.dstc.security.x509;

import java.util.Set;
import java.util.HashSet;
import java.util.Date;
import java.util.Vector;
import java.math.BigInteger;
import java.security.*;
import java.security.cert.Certificate;
import java.security.cert.X509Certificate;
import java.security.cert.CertificateExpiredException;
import java.security.cert.CertificateNotYetValidException;
import java.security.cert.CertificateEncodingException;
import java.security.cert.CertificateException;
import java.security.spec.X509EncodedKeySpec;
import com.dstc.security.asn1.*;
import com.dstc.security.provider.OID;

/**
 * <p>Implements the java.security.cert.X509Certificate class.
 *
 * @version 0.98, 98/07/01
 * @author Ming Yung
 */
public class X509CertImpl extends X509Certificate 
{
  private Certificate cert;

  /**
   * Constructs a Certificate
   */
  public X509CertImpl(int version, BigInteger serialNumber,
                      X500Name issuer, Validity validity, X500Name subject,
                      SubjectPublicKeyInfo spki, String sigAlgName,
                      PrivateKey caKey, Extensions extensions)
  {
    this.cert = new Certificate(version, serialNumber, issuer,
                                validity, subject, spki, sigAlgName,
                                caKey, extensions);
  }

  /**
   * Constructs a Certificate from a DER encoding
   */
  public X509CertImpl(byte[] encoded) throws Asn1Exception 
  {
    this.cert = new Certificate(encoded);
  }

////////////////////////////////////////////////////////////////////////////
// Implements methods defined abstract in java.security.cert.X509Certificate
////////////////////////////////////////////////////////////////////////////

  /**
   * Checks if this Certificate is valid
   */
  public void checkValidity()
      throws CertificateExpiredException, CertificateNotYetValidException
  {
    Date now = new Date ();

    if (now.after (getNotAfter ())) 
    {
      throw new CertificateExpiredException 
          ("Certficate Expired on: " + getNotAfter ());
    }

    if (now.before (getNotBefore ()))
    {
      throw new CertificateNotYetValidException 
          ("Certficate Not Valid until " + getNotBefore ());
    }
  }

  /**
   * Checks if this Certificate is valid on date
   */
  public void checkValidity(Date date)
      throws CertificateExpiredException, CertificateNotYetValidException 
  {
    if (date.after (getNotAfter ())) 
    {
      throw new CertificateExpiredException 
          ("Certficate Expires on: " + getNotAfter ());
    }

    if (date.before (getNotBefore ()))
    {
      throw new CertificateNotYetValidException 
          ("Certficate Not Valid until " + getNotBefore ());
    }
  }

  /**
   * Returns this Certificate's Version
   */
  public int getVersion() 
  {
    return cert.tbs.getVersion() + 1;
  }

  /**
   * Returns this Certificate's Serial Number
   */
  public BigInteger getSerialNumber() 
  {
    return cert.tbs.getSerialNumber();
  }

  /**
   * Returns the Issuer's Distinguished Name of this Certificate
   */
  public Principal getIssuerDN() 
  {
    return cert.tbs.getIssuer();
  }

  /**
   * Returns the Subject's Distinguished Name of this Certificate
   */
  public Principal getSubjectDN() 
  {
    return cert.tbs.getSubject();
  }

  /**
   * Returns the Not Before time of this Certificate
   */
  public Date getNotBefore() 
  {
    return cert.tbs.getValidity().getNotBefore ();
  }

  /**
   * Returns the Not After time of this Certificate
   */
  public Date getNotAfter() 
  {
    return cert.tbs.getValidity().getNotAfter ();
  }

  /**
   * Returns the DER encoding of the TBSCertificate
   */
  public byte[] getTBSCertificate() throws CertificateEncodingException 
  {
    try
    {
      return cert.tbs.encode();
    }
    catch (Exception e)
    {
      throw new CertificateEncodingException(e.toString());
    }
  }

  /**
   * Returns the Signature bytes in this Certifcate
   */
  public byte[] getSignature() 
  {
    return cert.sigData;
  }

  /**
   * Returns the Signature Algorithm Name for this Certifcate
   */
  public String getSigAlgName() 
  {
    return OID.getAlgName(cert.sigAlg.getOid());
  }

  /**
   * Returns the Signature Algorithm Object Identifier for this Certificate
   */
  public String getSigAlgOID() 
  {
    return cert.sigAlg.getOid();
  }

  /**
   * Returns the DER encoded Signature Alg Parameters for this Certificate
   */
  public byte[] getSigAlgParams() 
  {
    try
    {
      return cert.sigAlg.getParams().getEncoded();
    }
    catch (Exception e)
    {
      e.printStackTrace();
      return null;
    }
  }

  public boolean[] getIssuerUniqueID() 
  {
    return null;
//    return cert.tbs.issuerUniqueId;
  }

  public boolean[] getSubjectUniqueID() 
  {
    return null;
    //return cert.tbs.subjectUniqueId;
  }
 
  /**
   * Returns the KeyUsage values for this certificate as a boolean array
   */
  public boolean[] getKeyUsage() 
  {
    byte[] val = getExtensionValue(OID.keyUsage);
    if (val == null) return null;

    boolean retval[] = new boolean[8];
    for (int i=0; i<8; i++)
    {
      retval[i] = ((val[0] >>> i) & 0x01) == 0 ? false : true;
    }
    return retval;
  }

  public int getBasicConstraints() 
  {
    return 0;
  }

  public boolean hasUnsupportedCriticalExtension()
  {
    return false;
  }

////////////////////////////////////////////////////////////////////////////
// Implements the methods defined abstract in java.security.cert.Certificate
////////////////////////////////////////////////////////////////////////////

  /**
   * Returns the DER encoding of this Certificate
   */
  public byte[] getEncoded() throws CertificateEncodingException 
  {
    try
    {
      return cert.encode();
    }
    catch (Exception e)
    {
      e.printStackTrace();
      throw new CertificateEncodingException(e.toString());
    }
  }

  /**
   * Returns the Public Key corresponding to this Certificate
   */
  public PublicKey getPublicKey () 
  {
    try
    {
      String algName =
        OID.getAlgName(cert.tbs.getSubjectPubKeyInfo().
          getAlgorithmId().getOid());
      KeyFactory keyFact = KeyFactory.getInstance(algName);
      return keyFact.generatePublic(
        new X509EncodedKeySpec(cert.tbs.getSubjectPubKeyInfo().encode()));
    } 
    catch (Exception e)
    {
      e.printStackTrace();
      return null;
    }
  }

  /**
   * Verfifies this Certificate against the signerKey
   */
  public void verify(PublicKey signerKey) 
      throws CertificateException, NoSuchAlgorithmException,
      InvalidKeyException, NoSuchProviderException,
      SignatureException 
  {
    verify(signerKey, "DSTC");
  }

  /**
   * Verifies this Certifcate against the signerKey with sigProvider
   */
  public void verify(PublicKey key, String sigProvider)
      throws CertificateException, NoSuchAlgorithmException,
      InvalidKeyException, NoSuchProviderException,
      SignatureException 
  {
    try 
    {
      Signature sig = Signature.getInstance(getSigAlgName(), sigProvider);
      sig.initVerify(key);
      sig.update(getTBSCertificate());
      boolean verifies = sig.verify(getSignature());
      if (!verifies)
        throw new CertificateException("Bad signature");
    } 
    catch (Exception e) 
    {
      e.printStackTrace();
      throw new CertificateException(e.toString ());
    }
  }

  public String toString()
  {
    StringBuffer sb = new StringBuffer();
    sb.append("Version: " + getVersion() + "\n");
    sb.append("Serial Number: " + getSerialNumber().toString() + "\n");
    sb.append("Issuer: " + getIssuerDN().getName() + "\n");
    sb.append("Subject: " + getSubjectDN().getName() + "\n");
    sb.append("Key Algorithm: " + getPublicKey().getAlgorithm() + "\n");
    sb.append("Signature Algorithm: " + getSigAlgName());
    sb.append(" (" + getSigAlgOID() + ")\n");
    sb.append("Valid Not Before: " + getNotBefore() + "\n");
    sb.append("Valid Not After: " + getNotAfter() + "\n");

    if (this.cert.tbs.getExtensions() != null)
      sb.append(this.cert.tbs.getExtensions().toString());

    return sb.toString();
  }

//////////////////////////////////////////////////////////////////////////
// Implements methods defined abstract in java.security.cert.X509Extension
//////////////////////////////////////////////////////////////////////////


  /**
   * Returns the critical extension oids.
   */
  public Set getCriticalExtensionOIDs() 
  {
    if (cert.tbs.getExtensions() == null)
      return new HashSet();
    else
      return cert.tbs.getExtensions().getCriticalOIDs();
  }

  /**
   * Returns the non-critical extension oids.
   */
  public Set getNonCriticalExtensionOIDs() 
  {
    if (cert.tbs.getExtensions() == null)
      return new HashSet();
    else
      return cert.tbs.getExtensions().getNonCriticalOIDs();
  }

  /**
   * Returns the extension value for the given oid
   */
  public byte[] getExtensionValue(String oid) 
  {
    if (cert.tbs.getExtensions() == null)
      return null;
    else
      return cert.tbs.getExtensions().getValue(oid);
  }

  /**
   * <p>An inner class representing the x.509 ASN.1 strcuture Certificate.
   *
   * <pre>
   *  <p>Certificate  ::=     SEQUENCE  {
   *       tbsCertificate       TBSCertificate,
   *       signatureAlgorithm   AlgorithmIdentifier,
   *       signature            BIT STRING  }
   *  
   *  <p>TBSCertificate  ::=  SEQUENCE  {
   *       version         [0]  EXPLICIT Version DEFAULT v1,
   *       serialNumber         CertificateSerialNumber,
   *       signature            AlgorithmIdentifier,
   *       issuer               Name,
   *       validity             Validity,
   *       subject              Name,
   *       subjectPublicKeyInfo SubjectPublicKeyInfo,
   *       issuerUniqueID  [1]  UniqueIdentifier OPTIONAL,
   *                            -- If present, version must be v2 or v3
   *       subjectUniqueID [2]  UniqueIdentifier OPTIONAL,
   *                            -- If present, version must be v2 or v3
   *       extensions      [3]  EXPLICIT Extensions OPTIONAL
   *                            -- If present, version must be v3
   *       }
   *  
   *  <p>Version  ::=  INTEGER  {  v1(0), v2(1), v3(2)  }
   *  
   *  <p>CertificateSerialNumber  ::=  INTEGER
   *  
   *  <p>UniqueIdentifier  ::=  BIT STRING
   *  
   *  <p>Extensions  ::=  SEQUENCE SIZE (1..MAX) OF Extension
   *  
   *  <p>Extension  ::=  SEQUENCE  {
   *       extnID      OBJECT IDENTIFIER,
   *       critical    BOOLEAN DEFAULT FALSE,
   *       extnValue   OCTET STRING  }
   * </pre>
   */
  protected class Certificate extends Sequence
  {
    private TBSCertificate tbs;
    private AlgorithmId sigAlg;
    private byte[] sigData;  

    /**
     * Constructs a Certificate
     */
    protected Certificate(int ver, BigInteger serialNumber,
                       X500Name issuer, Validity validity, X500Name subject,
                       SubjectPublicKeyInfo spki, String sigAlgName,
                       PrivateKey caKey, Extensions extensions)
    {
      try
      {
        this.sigAlg = new AlgorithmId(OID.getAlgOid(sigAlgName));

        this.tbs 
          = new TBSCertificate(ver, serialNumber, this.sigAlg, issuer,
                               validity, subject, spki, extensions);

        addComponent(this.tbs);
  
        addComponent(this.sigAlg);
  
        Signature sig = Signature.getInstance(sigAlgName);
        sig.initSign(caKey);
        sig.update(this.tbs.encode());
        this.sigData = sig.sign();
        addComponent(new BitString(this.sigData));
      }
      catch (Exception e)
      {
        e.printStackTrace();
      }
    }

    /**
     * Constructs a Certificate from a DER encoding
     */
    protected Certificate(byte[] encoded) throws Asn1Exception
    {
      doDecode(encoded);

      this.tbs = new TBSCertificate(((Asn1)components.elementAt(0)).encode());

      this.sigAlg = new AlgorithmId(((Asn1)components.elementAt(1)).encode());

      this.sigData = ((BitString)components.elementAt(2)).getBytes();
    }
  }
}
