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

import java.io.IOException;
import java.util.Set;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Vector;
import java.math.BigInteger;
import java.security.PublicKey;
import java.security.PrivateKey;
import java.security.KeyFactory;
import java.security.cert.X509Certificate;
import java.security.cert.X509CRL;
import java.security.spec.X509EncodedKeySpec;
import com.dstc.security.provider.OID;
import com.dstc.security.x509.Validity;
import com.dstc.security.x509.X500Name;
import com.dstc.security.x509.Attribute;
import com.dstc.security.x509.AlgorithmId;
import com.dstc.security.x509.SubjectPublicKeyInfo;
import com.dstc.security.x509.X509CertImpl;
import com.dstc.security.x509.X509CRLImpl;
import com.dstc.security.x509.ExtensionFactory;
import com.dstc.security.x509.Extension;
import com.dstc.security.x509.Extensions;
import com.dstc.security.x509.extns.NetscapeCertType;
import com.dstc.security.x509.extns.BasicConstraints;
import com.dstc.security.x509.extns.SubjectAltName;
import com.dstc.security.x509.extns.AuthorityKeyIdentifier;
import com.dstc.security.x509.extns.SubjectKeyIdentifier;
import com.dstc.security.pki.pkcs10.PKCS10CertificationRequest;
import com.dstc.security.cms.v1.ContentInfo;
import com.dstc.security.cms.v1.EncapContentInfo;
import com.dstc.security.cms.v1.SignedData;
import com.dstc.security.cms.v1.CertificateSet;
import com.dstc.security.asn1.UTCTime;
import com.dstc.security.asn1.IA5String;
import com.dstc.security.asn1.Asn1Exception;
import com.dstc.security.asn1.Asn1;

import com.dstc.security.pki.CertRequestFactory;
import com.dstc.security.pki.CertificationRequest;

public class CertGenerator
{
  protected static final int UNINITIALIZED = 0;
  protected static final int INITIALIZED = 1;

  private int state;
  private int validityPeriod = 100;
  private String sigAlgName;
  private X500Name issuer;
  private PrivateKey caPrivateKey;
  private byte[] caSubjectKeyId = null;
  private X509Certificate caCert;

  private PublicKey subjectPubKey;
  private String subjectName;
  private String email;
  private Vector extns;

  public CertGenerator()
  {
    this.state = UNINITIALIZED;
  }

  public void setValidityPeriod(int days)
  {
    this.validityPeriod = days;
  }

  public void setSignatureAlgorithm(String alg)
  {
    this.sigAlgName = alg;
  }

  public void setIssuerDN(String dn)
  {
    try
    {
      this.issuer = new X500Name(dn);
    }
    catch (IOException e)
    {
      e.printStackTrace();
    }
  }

  /**
   * Initializes this CATool with the issuer private key and
   * associated X.509 certificate
   */
  public void init(PrivateKey caPrivateKey, X509Certificate cert)
    throws ToolException
  {
    try
    {
      this.caPrivateKey = caPrivateKey;
      
      if (cert != null)
      {
        this.caCert = cert;
        SubjectKeyIdentifier ski
          = (SubjectKeyIdentifier)ExtensionFactory.
              getExtension(OID.subjectKeyIdentifier, cert);
        if (ski != null)
          caSubjectKeyId = ski.getKeyID();
      }
  
      this.state = INITIALIZED;
    }
    catch (Exception e)
    {
      e.printStackTrace();
      throw new ToolException(e.getMessage());
    }
  }

  /**
   * Returns true if this CATool is already initialized with 
   * a Private Key
   */
  public boolean isInitialized()
  {
    return (this.state == INITIALIZED);
  }

  /**
   * Sets the subject's public key
   */
  public void setSubjectPubKey(PublicKey pub)
  {
    this.subjectPubKey = pub;
  }

  /**
   * Sets the subject's distinguished name
   */
  public void setSubjectDN(String dn)
  {
    this.subjectName = dn;
  }

  /**
   * Sets the subject's email address
   */
  public void setEmail(String email)
  {
    this.email = email;
  }

  /**
   * Processes a PKCS#10 certification request, setting the subject's 
   * name, public key, etc as a result.
   */
  public void processPKCS10(byte[] encoded) throws ToolException
  {
    try
    {
      PKCS10CertificationRequest certReq
        = (PKCS10CertificationRequest)
            CertRequestFactory.getRequest("PKCS10", encoded);

      certReq.verifySignature();

      setSubjectDN(certReq.getSubjectName());

      KeyFactory keyFact 
        = KeyFactory.getInstance(certReq.getKeyAlgName());

      setSubjectPubKey(keyFact.generatePublic(
        new X509EncodedKeySpec(
          certReq.getSubjectPublicKeyInfo().encode())));

      Set signedAtts 
        = certReq.getCertificationRequestInfo().getSignedAttributes();

      Iterator it = signedAtts.iterator();
      while (it.hasNext())
      {
        Attribute att = (Attribute)it.next();
        if (att.getType().equals(OID.emailAddress))
        {
          setEmail(((IA5String)att.getValues().elementAt(0)).getName());
          break;
        }
      }
    }
    catch (Exception e)
    {
      e.printStackTrace();
      throw new ToolException(e.getMessage());
    }
  }

  /**
   * Adds an extension to be included in the user certificate to be
   * generated
   */
  public void addExtension(Extension extn)
  {
    if (extns == null)
      extns = new Vector();

    extns.add(extn);
  }

  /**
   * Generates a certificate from the public key in the buffer and 
   * supplied user info
   */
  public X509Certificate generateCertificate(int version, 
    BigInteger serialNumber)
      throws ToolException
  {
    if (subjectPubKey == null)
      throw new ToolException("Subject Public key not yet set");

    if (subjectName == null)
      throw new ToolException("Subject Distinguished Name not yet set");

    try
    {
      Validity validity = new Validity(validityPeriod);
      X500Name subject = new X500Name(subjectName);
      SubjectPublicKeyInfo spki =
        new SubjectPublicKeyInfo(subjectPubKey.getEncoded());

      if (email != null)
        addExtension(new SubjectAltName(email));

      if (caSubjectKeyId != null)
        addExtension(new AuthorityKeyIdentifier(true, caSubjectKeyId));

      X509Certificate retval 
        = new X509CertImpl(version, serialNumber, this.issuer, 
                           validity, subject, spki, this.sigAlgName,
                           this.caPrivateKey, new Extensions(extns));
      resetSubjectInfo();
      return retval;
    }
    catch (Exception e)
    {
      e.printStackTrace();
      throw new ToolException(e.getMessage());
    }
  }

  public void resetSubjectInfo()
  {
    email = null;
    subjectName = null;
    subjectPubKey = null;
    extns = null;
  }

  /**
   * Generates crl 
   */
  public X509CRL generateCRL(UTCTime thisUpdate, UTCTime nextUpdate, 
                             Set revCerts)
    throws ToolException
  {
    try
    {
      X509CRL retval 
        = new X509CRLImpl(this.sigAlgName, this.issuer, thisUpdate, 
                          nextUpdate, revCerts, null, caPrivateKey);
      return retval;
    }
    catch (Exception e)
    {
      e.printStackTrace();
      throw new ToolException(e.getMessage());
    }
  }

  /**
   * Returns a Certificate Chain for the supplied certificate
   * as a PKCS#7 ContentInfo encapsulating a degenerate SignedData 
   */
  public ContentInfo makeCertChain(X509Certificate cert)
  {
    Set certs = new HashSet();
    certs.add(caCert);
    certs.add(cert);

    SignedData sd
      = new SignedData(new HashSet(), new EncapContentInfo(OID.id_data, null),
                       certs, null, new HashSet());

    ContentInfo retval = new ContentInfo(OID.id_signedData, sd);
    return retval;
  }

  /**
   * <p>An inner class for CATool-specific exceptions
   */
  protected class ToolException extends Exception
  {
    protected ToolException(String msg)
    {
      super(msg);
    }
  }
}
