//////////////////////////////////////////////////////////////////////////// 
// 
// 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.util.Set;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Vector;
import java.io.File;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.FileOutputStream;
import java.io.FileInputStream;
import java.io.IOException;
import java.math.BigInteger;
import java.util.Properties;
import java.util.Calendar;
import java.security.Security;
import java.security.SecureRandom;
import java.security.MessageDigest;
import java.security.PublicKey;
import java.security.PrivateKey;
import java.security.KeyFactory;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.cert.CertificateFactory;
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.RevokedCertificate;
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.pkcs8.PKCS8EncryptedPrivateKey;
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.util.Config;
import com.dstc.security.util.ConfigException;

/**
 * <p>A console-based CA tool for generating certificates from
 * self-generated key pairs or PKCS#10 certification requests.
 * To use, simply do
 * <pre>
 *    java com.dstc.security.pki.ConsoleCATool
 * </pre>
 * This CA tool makes heavy use of a properties file, whose location
 * is pointed to by the System Property jcsi.ca.conf (and defaults to
 * <user.home>/.jcsi/ca.properties) for configuration.
 *
 * @version 0.98, 98/11/01
 * @author Ming Yung
 */
public class ConsoleCATool extends CertGenerator
{
  private static final String CA_CONF = "jcsi.ca.conf";
  private static final String CA_PROPERTIES = "ca.properties";
  private String keyAlgName;
  private String keyDir;
  private CertificateFactory certFact;
  private Properties props;

  public static void main(String args[])
  {
    SecureRandom ran = null;
    FileInputStream is;
    FileOutputStream os;

    InputStreamReader isr = new InputStreamReader(System.in);
    BufferedReader in = new BufferedReader(isr);

    int validDays = 100;

    try
    {
      ConsoleCATool caTool = new ConsoleCATool();

      while (true)
      {
        String keyAlg;

        System.out.print("\nGenerate (S)elf-signed cert, (U)ser cert, (C)RL" +
                         " or (Q)uit? ");
        String resp = in.readLine();
        if (resp.equalsIgnoreCase("Q"))
        {
          break;
        }
        else if (resp.equalsIgnoreCase("S"))
        {
          if (ran == null)
          {
            System.out.println("Seeding random number generator. " +
                               "Please wait...");
            ran = new SecureRandom();
            ran.nextInt();
          }

          caTool.generateKeysAndSelfSignedCert(ran);
        }
        else if (resp.equalsIgnoreCase("U"))
        {
          if (!caTool.isInitialized())
          {
            caTool.loadPrivateKeyAndCert();
          }
  
          System.out.print("\n(G)enerate KeyPair, (I)mport public key "+ 
                           "from PKCS#10 request or (Q)uit? ");
          resp = in.readLine();

          if (resp.equalsIgnoreCase("Q"))
          {
            break;
          }
          else if (resp.equalsIgnoreCase("G"))
          {
            if (ran == null)
            {
              System.out.println("Seeding random number generator. " +
                                 "Please wait...");
              ran = new SecureRandom();
              ran.nextInt();
            }

            ConsoleKeyTool keyTool = new ConsoleKeyTool(ran);
            keyTool.setKeyAlgorithm();
            keyTool.setKeyLength();
            keyTool.setAlgParameters();
            keyTool.generateKeyPair();

            System.out.print("\nFile to save private key? ");
            os = new FileOutputStream(in.readLine());

            PKCS8EncryptedPrivateKey pkcs8
              = new PKCS8EncryptedPrivateKey(keyTool.getPrivate());
            System.out.print("\nEnter password to lock private key? ");
            pkcs8.encrypt(in.readLine());
            os.write(pkcs8.getEncoded());
            os.close();

            caTool.setSubjectPubKey(keyTool.getPublic());
    
            keyTool.setDistinguishedName();
            keyTool.setEmailAddress();

            caTool.setSubjectDN(keyTool.getDN());
            caTool.setEmail(keyTool.getEmail());
          }
          else if (resp.equalsIgnoreCase("I"))
          {
            System.out.print("\nFile to import PKCS#10 request? ");
            is = new FileInputStream(in.readLine());
            byte encoded[] = new byte[is.available()];
            is.read(encoded);

            caTool.processPKCS10(encoded);
          }

          caTool.setNetscapeExtension();

          BigInteger serialNumber =
            BigInteger.valueOf(Calendar.getInstance().getTime().getTime());

          X509Certificate cert = (X509Certificate)
            caTool.generateCertificate(2, serialNumber);

          System.out.print("\nSave as (C)ert or (P)KCS#7 cert-chain? ");
          String certOrChain = in.readLine();

          if (certOrChain.equalsIgnoreCase("C"))
          {
            System.out.print("\nFile to save cert? ");
  
            os = new FileOutputStream(in.readLine());
            os.write(cert.getEncoded());
          }
          else
          {
            ContentInfo chain = caTool.makeCertChain(cert);
            System.out.print("\nFile to save cert chain? ");
  
            os = new FileOutputStream(in.readLine());
            os.write(chain.encode());
          }
        }
        else if (resp.equalsIgnoreCase("C"))
        {
          if (!caTool.isInitialized())
          {
            caTool.loadPrivateKeyAndCert();
          }

          System.out.print("\nCRL to add to? ");
          is = new FileInputStream(in.readLine());

          CertificateFactory certFact
            = CertificateFactory.getInstance("X509");

          X509CRL oldcrl = (X509CRL)certFact.generateCRL(is);
          System.out.println(oldcrl.toString());

          System.out.print("\nCertificate to revoke? ");
          is = new FileInputStream(in.readLine());

          X509Certificate cert 
            = (X509Certificate)certFact.generateCertificate(is);

          RevokedCertificate revCert 
            = new RevokedCertificate(cert.getSerialNumber(), 
                                     new UTCTime(0), null);

          HashSet revCerts = new HashSet();
          revCerts.add(revCert);

          X509CRL crl = (X509CRL)
            caTool.generateCRL(new UTCTime(0), new UTCTime(10), revCerts);

          System.out.print("\nFile to save crl? ");

          os = new FileOutputStream(in.readLine());
          os.write(crl.getEncoded());
        }
      }
    }
    catch(Exception e)
    {
      e.printStackTrace();
    }
  }

  /**
   * Constructs a CATool from a Properties
   *
   * <p>The following keys are expected always
   * <pre>
   *        jcsi.ca.issuerDN       CA's distinguished name
   *        jcsi.ca.keyAlg         CA's key algorithm
   *        jcsi.ca.sigAlg         CA's signature algorithm
   * </pre>
   * <p>In addition, the following are needed (except for self-signing)
   * <pre>
   *        jcsi.ca.key.dir        directory for CA keys
   *        jcsi.ca.privKey        filename of CA's PKCS#8-protected private key
   *        jcsi.ca.cert           filename of CA's X.509 certificate
   * </pre>
   */
  public ConsoleCATool() throws ConfigException, ToolException
  {
    super();

    this.props = Config.getProperties(CA_CONF, CA_PROPERTIES);
    keyDir = (String)props.get("jcsi.ca.key.dir");

    String issuerDN = (String)props.get("jcsi.ca.issuerDN"); 
    if (issuerDN == null)
      throw new ToolException("Issuer's Distinguished Name unspecified");

    setIssuerDN(issuerDN);

    this.keyAlgName = (String)props.get("jcsi.ca.keyAlg");
    if (keyAlgName == null)
      throw new ToolException("Issuer's Key algorithm unspecified");

    String sigAlgName = (String)props.get("jcsi.ca.sigAlg");
    if (sigAlgName == null)
      throw new ToolException("Issuer's signature algorithm unspecified");

    setSignatureAlgorithm(sigAlgName);

    try
    {
      certFact = CertificateFactory.getInstance("X509");
    }
    catch (Exception e)
    {
      throw new ToolException(e.getMessage());
    }
  }

  /**
   * Initializes this CATool with an encrypted issuer private key and
   * associated X.509 certificate 
   */
  public void init(byte[] encKey, X509Certificate cert)
    throws ToolException
  {
    try
    {
      PKCS8EncryptedPrivateKey pkcs8
        = new PKCS8EncryptedPrivateKey(encKey);

      System.out.print("\nEnter password to unlock signer private key: ");
      InputStreamReader isr = new InputStreamReader(System.in);
      BufferedReader in = new BufferedReader(isr);

      pkcs8.decrypt(in.readLine());

      init(pkcs8.getPrivateKey(), cert);
    }
    catch (Exception e)
    {
      e.printStackTrace();
      throw new ToolException(e.getMessage());
    }
  }

  /**
   * A convenience method to load the CA's encrypted private key 
   * and associated X.509 certificate from locations pointed to by
   * <pre>
   *    jcsi.ca.privKey     and      jcsi.ca.cert
   * </pre>
   */
  public void loadPrivateKeyAndCert() throws ToolException
  {
    try
    {
      String fileName = (String)props.get("jcsi.ca.privKey");
      if (fileName == null)
        throw new ToolException("Location of encrypted " +
                                "PrivateKey unspecified");

      FileInputStream is 
        = new FileInputStream(keyDir + File.separator + fileName);

      byte encCAKey[] = new byte[is.available()];
      is.read(encCAKey);
    
      fileName = (String)props.get("jcsi.ca.cert");
      if (fileName == null)
        throw new ToolException("Location of CA certificate unspecified");

      is = new FileInputStream(keyDir + File.separator + fileName);
      X509Certificate caCert 
        = (X509Certificate)certFact.generateCertificate(is);

      init(encCAKey, caCert);
    }
    catch (Exception e)
    {
      e.printStackTrace();
      throw new ToolException(e.getMessage());
    }
  }

  /**
   * Prompts for and sets any chosen Netscape extensions for the
   * user certificate to be generated
   */
  public void setNetscapeExtension() throws IOException
  {
    byte flag = 0x00;

    while(true)
    {
      System.out.print("\nNetscape Certificate Extension for\n" +
                       "  1) S/MIME Client\n" +
                       "  2) SSL Client\n" +
                       "  3) SSL Server\n" +
                       " or (Q)uit? ");

      InputStreamReader isr = new InputStreamReader(System.in);
      BufferedReader in = new BufferedReader(isr);
      String certType = in.readLine();

      if (certType.equalsIgnoreCase("Q"))
      {
        break;
      }
      else if (certType.equalsIgnoreCase("1"))
      {
        flag |= NetscapeCertType.SMIME;
      }
      else if (certType.equalsIgnoreCase("2"))
      {
        flag |= NetscapeCertType.SSL_CLIENT;
      }
      else if (certType.equalsIgnoreCase("3"))
      {
        flag |= NetscapeCertType.SSL_SERVER;
      }
    }

    if (flag != 0x00) 
    {
      addExtension(new NetscapeCertType(true, flag));
    }
  }

  /**
   * Generates a key pair and a self-signed certificate for the public
   * key
   *
   * <p>The key algorithm, signature algorithm, and distinguished
   * name, and email address are determined from the properties file
   */
  public void generateKeysAndSelfSignedCert(SecureRandom ran)
  {
    try
    {
      KeyPairGenerator kpg 
        = KeyPairGenerator.getInstance(keyAlgName);

      int keyLength = Integer.parseInt((String)props.get("jcsi.ca.keyLength"));
      kpg.initialize(keyLength, ran);
      KeyPair kp = kpg.generateKeyPair();

      PrivateKey signerKey = kp.getPrivate();
  
      System.out.print("\nFile to save private key? ");

      InputStreamReader isr = new InputStreamReader(System.in);
      BufferedReader in = new BufferedReader(isr);
      FileOutputStream os = new FileOutputStream(in.readLine());

      PKCS8EncryptedPrivateKey pkcs8
        = new PKCS8EncryptedPrivateKey(signerKey);
      System.out.print("\nEnter password to lock private key? ");
      pkcs8.encrypt(in.readLine());
      os.write(pkcs8.getEncoded());

      String issuerDN = (String)props.get("jcsi.ca.issuerDN"); 
      String issuerEmail = (String)props.get("jcsi.ca.email");
      int validDays 
        = Integer.parseInt((String)props.get("jcsi.ca.validityPeriod"));

      init(signerKey, null);
      setSubjectDN(issuerDN);
      setEmail(issuerEmail);
      setSubjectPubKey(kp.getPublic());
      setValidityPeriod(validDays);
  
      addExtension(new BasicConstraints(true, true, 5));

      MessageDigest sha1 = MessageDigest.getInstance("SHA-1");
      sha1.update(kp.getPublic().getEncoded());
      addExtension(new SubjectKeyIdentifier(true, sha1.digest()));

      addExtension(new NetscapeCertType(true, 
        (byte)(NetscapeCertType.SMIME_CA | NetscapeCertType.SSL_CA)));

      BigInteger serialNumber =
        BigInteger.valueOf(Calendar.getInstance().getTime().getTime());
  
      X509Certificate cert 
        = generateCertificate(2, serialNumber);

      System.out.print("\nFile to save cert? ");

      os = new FileOutputStream(in.readLine());
      os.write(cert.getEncoded());
      os.flush();
    }
    catch (Exception e)
    {
      e.printStackTrace();
    }
  }
}
