//////////////////////////////////////////////////////////////////////////// 
// 
// 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.cms;

import java.util.Arrays;
import java.util.Collection;
import java.util.Vector;
import java.util.Iterator;

import java.math.BigInteger;
import java.security.Principal;
import java.security.SignatureException;
import java.security.cert.X509Certificate;

import com.dstc.security.cms.v1.IssuerAndSerialNumber;

/**
 * <p>A class for certificate path processing.
 *
 * <p>To be used in the verification process of a CMS signed message
 *
 * @version 0.98, 98/07/01
 * @author Ming Yung
 */
public class CertPath
{
  private Vector path = new Vector();
  private X509Certificate signerCert = null;

  /** Constructs a CertPath from a Collection of X509Certificates,
   *  and an IssuerAndSerialNumber for the signing certificate
   */
  public CertPath(Collection certColl, IssuerAndSerialNumber iss)
    throws CMSException
  {
    Vector certs = new Vector(certColl);

    //Find signerCert
    Iterator iterator = certs.iterator();

    X509Certificate cert;

    while (true)
    {
      cert = (X509Certificate)iterator.next();

      if (iss.equals(new IssuerAndSerialNumber(cert)))
      {
        this.signerCert = cert;
        path.addElement(cert);
        certs.removeElement(cert);
        break;
      }

      if (!iterator.hasNext())
        throw new CMSException("Signer cert not found");
    }

    //Iteratively find signer of previous cert until 
    //a self-signed cert is found or there are no more certs left

    while (true)
    {
      //no more certs left
      if (certs.isEmpty()) break;

      iterator = certs.iterator();

      //issuer -> subject
      Principal subject = cert.getIssuerDN();

      //self-signed
      if (cert.getSubjectDN().equals(subject)) break;

      while (true)
      {
        //no more certs
        if (!iterator.hasNext())
          return;

        cert = (X509Certificate)iterator.next();

        //got subject's cert
        if (cert.getSubjectDN().equals(subject))
        {
          path.addElement(cert);
          certs.removeElement(cert);
          break;
        }
      }
    }
  }

  /**
   * Returns the X509Certificate Path for this CertPath,
   * starting with the signer cert
  public Vector getPath()
  {
    return this.path;
  }

  /**
   * Returns the signer cert for this CertPath
   */
  public X509Certificate getSignerCert()
  {
    return signerCert;
  }

  /**
   * Verifies the cert path against a Collection of trusted certs
   */
  public void verify(Collection trustedCerts) throws CMSException
  {
    try
    {
      //Verify the cert chain as far as we can go

      Iterator iterator = this.path.iterator();

      X509Certificate thisCert, nextCert;
      thisCert = (X509Certificate)iterator.next();
  
      while (iterator.hasNext())
      {
        thisCert.checkValidity();
        nextCert = (X509Certificate)iterator.next();
        thisCert.verify(nextCert.getPublicKey());
        thisCert = nextCert;
      }
  
      //thisCert is either self-signed or not

      Principal issuer = thisCert.getIssuerDN();
      boolean selfSigned = issuer.equals(thisCert.getSubjectDN());

      //locate issuer cert amongst trusted certs

      iterator = trustedCerts.iterator();
      while (iterator.hasNext())
      {
        X509Certificate cert = (X509Certificate)iterator.next();

        if (issuer.getName().equals(cert.getSubjectDN().getName()))
        {
          //self-signed case, just test for cert equality
          if (selfSigned)
          {
            thisCert.checkValidity();
            boolean truth
              = Arrays.equals(thisCert.getEncoded(), cert.getEncoded());
            thisCert.verify(cert.getPublicKey());
            return;
          }            
          //otherwise verify thisCert against CA public key
          else
          {
            thisCert.checkValidity();
            thisCert.verify(cert.getPublicKey());
            return;
          }
        }
      }

      //out of certs, therefore no trust
      throw new CMSException("Cannot find trusted CA");
    }
    catch (SignatureException e)
    {
      throw new CMSException("Bad signature");
    }
    catch (Exception e)
    {
      e.printStackTrace();
      throw new CMSException(e.getMessage());
    }
  }
}
