//////////////////////////////////////////////////////////////////////////// 
// 
// 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.Iterator;
import java.math.BigInteger;
import java.util.Date;
import java.util.Vector;

import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.InvalidKeyException;
import java.security.SignatureException;
import java.security.Principal;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.Signature;
import java.security.cert.X509CRL;
import java.security.cert.X509CRLEntry;
import java.security.cert.Certificate;
import java.security.cert.X509Certificate;
import java.security.cert.CRLException;

import com.dstc.security.asn1.Asn1;
import com.dstc.security.asn1.Explicit;
import com.dstc.security.asn1.Asn1Exception;
import com.dstc.security.asn1.Sequence;
import com.dstc.security.asn1.BitString;
import com.dstc.security.asn1.UTCTime;
import com.dstc.security.provider.OID;

/**
 * Implements X509 Certifcate Revocation Lists.
 *
 * @see CRL
 * @see CertificateFactory
 * @see X509Extension
 */

public class X509CRLImpl extends X509CRL
{
  private CertificateList certList;

  /**
   * Constructs an X509CRLImpl
   */
  public X509CRLImpl(String sigAlgName, X500Name issuer,
                     UTCTime thisUpdate, UTCTime nextUpdate,
                     Set revCerts, Extensions extns, PrivateKey caKey)
  {
    this.certList = new CertificateList(sigAlgName, issuer, thisUpdate,
                                        nextUpdate, revCerts, extns,
                                        caKey);
  }

  /**
   * Constructs an X509CRLImpl from a DER encoding
   */
  public X509CRLImpl(byte[] encoded) throws Asn1Exception
  {
    this.certList = new CertificateList(encoded); 
  } 

  public byte[] getEncoded() throws CRLException
  {
    return this.certList.encode();
  }

  public void verify(PublicKey key)
      throws CRLException,  NoSuchAlgorithmException,
      InvalidKeyException, NoSuchProviderException,
      SignatureException
  {
    verify(key, "DSTC");
  }

  public void verify(PublicKey key, String sigProvider)
      throws CRLException, NoSuchAlgorithmException,
      InvalidKeyException, NoSuchProviderException,
      SignatureException
  {
    Signature sig = Signature.getInstance (getSigAlgName(), sigProvider);
    sig.initVerify (key);
    sig.update (getTBSCertList());
    boolean verifies = sig.verify (getSignature());
    System.out.println ("Signature verifies: " + verifies);
  }

  public int getVersion()
  {
    return this.certList.tbsCertList.version;
  }

  public Principal getIssuerDN()
  {
    return this.certList.tbsCertList.issuer;
  }

  public Date getThisUpdate()
  {
    return this.certList.tbsCertList.thisUpdate.getDate();
  }

  public Date getNextUpdate()
  {
    return this.certList.tbsCertList.nextUpdate.getDate();
  }

  public X509CRLEntry
      getRevokedCertificate(BigInteger serialNumber)
  {
    try
    {
      //Linear search
      Iterator it = getRevokedCertificates().iterator();
      while (it.hasNext())
      {
        X509CRLEntry next 
          = new X509CRLEntryImpl(((RevokedCertificate)it.next()).encode());
        if (next.getSerialNumber().equals(serialNumber))
          return next;
      }
      return null;
    }
    catch (Exception e)
    {
      e.printStackTrace();
      return null;
    }
  }

  public Set getRevokedCertificates()
  {
    return certList.tbsCertList.revCerts.certs;
  }

  public byte[] getTBSCertList() throws CRLException
  {
    return this.certList.tbsCertList.encode();
  }

  public byte[] getSignature()
  {
    return this.certList.signature;
  }

  public String getSigAlgName()
  {
    return OID.getAlgName(this.certList.sigAlg.getOid());
  }

  public String getSigAlgOID()
  {
    return this.certList.sigAlg.getOid();
  }

  public byte[] getSigAlgParams()
  {
    try
    {
      return this.certList.sigAlg.getParams().getEncoded();
    }
    catch (Exception e)
    {
      e.printStackTrace();
      return null;
    }
  }

  public boolean isRevoked(Certificate cert)
  {
    return 
      (getRevokedCertificate(((X509Certificate)cert).getSerialNumber()) 
        == null ? false : true);
  }

  public String toString()
  {
    return "Not yet implemented";
  }

  public Set getCriticalExtensionOIDs()
  {
    return this.certList.tbsCertList.extns.getCriticalOIDs();
  }

  public Set getNonCriticalExtensionOIDs()
  {
    return this.certList.tbsCertList.extns.getNonCriticalOIDs();
  }

  public byte[] getExtensionValue(String oid)
  {
    return this.certList.tbsCertList.extns.getValue(oid);
  }

  public boolean hasUnsupportedCriticalExtension()
  {
    return false;
  }

  /**
   * <p>Inner class representing the ASN.1 structure CertificateList.
   * <pre>
   * CertificateList  ::=  SEQUENCE  {
   *     tbsCertList          TBSCertList,
   *     signatureAlgorithm   AlgorithmIdentifier,
   *     signature            BIT STRING  }
   * </pre>
   */
  protected class CertificateList extends Sequence
  {
    private TbsCertList tbsCertList;
    private AlgorithmId sigAlg;
    private byte[] signature;

    //Constructs a CertificateList
    protected CertificateList(String sigAlgName, X500Name issuer,
                              UTCTime thisUpdate, UTCTime nextUpdate,
                              Set revCerts, Extensions extns,
                              PrivateKey caKey)
    {
      try
      {
        this.tbsCertList = new TbsCertList(sigAlgName, issuer, thisUpdate,
                                           nextUpdate, revCerts, extns);
        addComponent(tbsCertList);
  
        this.sigAlg = new AlgorithmId(sigAlgName, null);
        addComponent(this.sigAlg);
  
        Signature sig = Signature.getInstance(sigAlgName);
        sig.initSign(caKey);
        sig.update(this.tbsCertList.encode());
        this.signature = sig.sign();
        addComponent(new BitString(this.signature));
      }
      catch (Exception e)
      {
        e.printStackTrace();
      }
    }

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

      this.tbsCertList 
        = new TbsCertList(((Asn1)components.elementAt(0)).encode());

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

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

    /**
     * <p>Inner class representing the ASN.1 structure tbsCertList.
     * <pre>
     * TBSCertList  ::=  SEQUENCE  {
     *     version                 Version OPTIONAL,
     *                             -- if present, must be v2
     *     signature               AlgorithmIdentifier,
     *     issuer                  Name,
     *     thisUpdate              ChoiceOfTime,
     *     nextUpdate              ChoiceOfTime OPTIONAL,
     *     revokedCertificates     SEQUENCE OF SEQUENCE  {
     *         userCertificate         CertificateSerialNumber,
     *         revocationDate          ChoiceOfTime,
     *         crlEntryExtensions      Extensions OPTIONAL
     *                                 -- if present, must be v2
     *         }  OPTIONAL,
     *     crlExtensions           [0]  EXPLICIT Extensions OPTIONAL
     *                                  -- if present, must be v2
     *     }
     * </pre>
     */
    protected class TbsCertList extends Sequence
    {
      private int version = 1;
      private AlgorithmId sigAlgo;
      private X500Name issuer;
      private UTCTime thisUpdate;
      private UTCTime nextUpdate;
      private RevokedCertificates revCerts;
      private Extensions extns;

      /**
       * Constructs a TbsCertList
       */
      protected TbsCertList(String sigAlgName, X500Name issuer,
                            UTCTime thisUpdate, UTCTime nextUpdate,
                            Set revCerts, Extensions extns)
      {
        try
        {
          addComponent(new com.dstc.security.asn1.Integer(version));
  
          this.sigAlgo = new AlgorithmId(sigAlgName, null);
          addComponent(sigAlgo);
  
          this.issuer = issuer;
          addComponent(issuer);
  
          this.thisUpdate = thisUpdate;
          addComponent(thisUpdate);
  
          this.nextUpdate = nextUpdate;
          addComponent(nextUpdate);
  
          this.revCerts = new RevokedCertificates(revCerts);
          addComponent(this.revCerts);
  
          if (extns != null)
          {
            this.extns = extns;
            Explicit exp = new Explicit(Asn1.TAG_CLASS_CONTEXT, 0);
            exp.addComponent(extns);
            addComponent(exp);
          }
        }
        catch (Exception e)
        {
          e.printStackTrace();
        }
      }

      /**
       * Constructs a TbsCertList from a DER encoding
       */
      protected TbsCertList(byte[] encoded) throws Asn1Exception
      {
        doDecode(encoded);
        int i = 0;
  
        try
        {
          this.version 
            = ((com.dstc.security.asn1.Integer)components.elementAt(i)).getInt(); 
          i++;
        }
        catch (ClassCastException e)
        {
          //Ignore
        }
  
        this.sigAlgo 
          = new AlgorithmId(((Asn1)components.elementAt(i++)).encode());
  
        this.issuer = new X500Name(((Asn1)components.elementAt(i++)).encode());
  
        this.thisUpdate 
          = (UTCTime)components.elementAt(i++);
  
        this.nextUpdate 
          = (UTCTime)components.elementAt(i++);
  
        this.revCerts 
          = new RevokedCertificates(((Asn1)components.elementAt(i++)).encode());
  
        if (i == components.size()) return;
  
        this.extns 
          = new Extensions(((Explicit)components.elementAt(i)
                            ).getComponent().encode());
      }
    }

    //Inner class
    protected class RevokedCertificates extends Sequence
    {
      //Set of RevokedCertificate
      private HashSet certs = new HashSet();
  
      /**
       * Constructs a RevokedCertificates from a DER encoding
       */
      protected RevokedCertificates(byte[] encoded) throws Asn1Exception
      {
        doDecode(encoded);
        for (int i=0; i<components.size(); i++)
        {
          certs.add(new
            RevokedCertificate(((Asn1)components.elementAt(i)).encode()));
        }
      }
  
      /**
       * Constructs a RevokedCertificates from a Set of X509CRLEntry
       */
      protected RevokedCertificates(Set certs)
      {
        this.certs = new HashSet(certs);
        Iterator it = certs.iterator();
        while (it.hasNext())
        {
          addComponent((RevokedCertificate)it.next());
        }
      }
    }
  }
}
