/*
 * @(#)BuildCertPath.java       1.1 11/22/00
 *
 * Copyright (c) 1998-2000 Sun Microsystems, Inc. All Rights Reserved.
 *
 * This software is the confidential and proprietary information of Sun
 * Microsystems, Inc. ("Confidential Information").  You shall not
 * disclose such Confidential Information and shall use it only in
 * accordance with the terms of the license agreement you entered into
 * with Sun.
 *
 * SUN MAKES NO REPRESENTATIONS OR WARRANTIES ABOUT THE SUITABILITY OF THE
 * SOFTWARE, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
 * PURPOSE, OR NON-INFRINGEMENT. SUN SHALL NOT BE LIABLE FOR ANY DAMAGES
 * SUFFERED BY LICENSEE AS A RESULT OF USING, MODIFYING OR DISTRIBUTING
 * THIS SOFTWARE OR ITS DERIVATIVES.
 *
 * CopyrightVersion 1.0_beta
 *
 */

import sun.security.util.Debug;

import java.io.FileInputStream;
import java.security.GeneralSecurityException;
import java.security.KeyStore;
import java.security.Provider;
import java.security.PublicKey;
import java.security.Security;
import java.security.cert.CertificateException;
import java.security.cert.CollectionCertStoreParameters;
import java.security.cert.TrustAnchor;
import java.security.cert.X509Certificate;
import java.security.cert.X509CertSelector;
import java.text.DateFormat;
import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import java.util.Vector;
import java.security.cert.CertPathBuilder;
import java.security.cert.CertPathBuilderException;
import java.security.cert.CertPathValidator;
import java.security.cert.CertStore;
import java.security.cert.LDAPCertStoreParameters;
import java.security.cert.PKIXCertPathChecker;
import java.security.cert.PKIXBuilderParameters;
import java.security.cert.PKIXCertPathBuilderResult;

import sun.security.provider.certpath.SunCertPathBuilderParameters;
import sun.security.x509.GeneralNameInterface;
import sun.security.x509.OIDMap;
import sun.security.x509.X500Name;
import sun.security.x509.X509CertImpl;
import sun.security.util.DerValue;
import sun.security.util.DerOutputStream;
import java.util.Locale;

/**
 * BuildCertPath tests the functionality of the CertPathBuilder,
 * PKIXBuilderParameters, and CertPath classes.
 *
 * Synopsis:
 * <pre>
 *   BuildCertPath
 *      -verbose
 *      -lhost [ldap host name]
 *      -lport [ldap port]
 *      -ccsCert [path]                    certificate to go into a CollectionCertStore
 *                                         (can have multiple)
 *      -ccsCRL [path]                     CRL to go into a CollectionCertStore
 *                                         (can have multiple)
 *      -keystore [path]                   (keystore password MUST be "changeit")
 *      -trusted [path]                    (can have multiple)
 *      -keyAndName [ca public key] [ca DN]
 *      -target  [DN]
 *      -subjectAltName [className:name]   (can have multiple)
 *      -date   [date]
 *      -keyUsage  [keyUsage (int value)]  (can have multiple)
 *      -maxLength [int]
 *      -requireExpPol
 *      -inhibitPolMap
 *      -inhibitAnyPol
 *      -acceptPolQual
 *      -initPolId [OID]                   (can have multiple)
 *      -minTrust [trustLevel]
 *      -enforceTop
 *      -initialUID [bits]
 *      -extKeyUsage [OID]                 (can have multiple)
 *      -checker [java Class name]
 *      -disableRev
 *      -buildReverse
 *      -sigProvider [provider]
 *</pre>
 * Note: the initial trusted certificate has to be a self-signed
 * certificate (to get the initial CA's public key and name)
 *
 * @version     1.1 11/22/00
 * @since       1.4
 * @author      Sean Mullan
 * @author      Yassir Elley
 * @see         CertPathBuilder
 * @see         PKIXBuilderParameters
 */
public final class BuildCertPath {

    private static final Debug debug = Debug.getInstance("certpath");

    static {
        try {
            OIDMap.addAttribute("TrustRatingsExtension",
                                "1.3.6.1.4.1.42.2.31.2.1",
                                Class.forName("TrustRatingsExtension"));
                                //Class.forName("x509.info.extensions." + TrustRatingsExtension.NAME));
        } catch (CertificateException ce) {
            if (debug != null)
                debug.println("BuildCertPath static init OIDMap.addAttribute() exception: "+ ce.getMessage());
        } catch (Exception e) {
            if (debug != null) {
                debug.println("BuildCertPath static init unexpected exception");
                e.printStackTrace();
            }
        }

    }

    //XXX It is public so that callers of parseArgs can
    //XXX find out if -verbose option was set.
    public static boolean verbose = false;

    public static void main(String[] args) {

        try {
            PKIXBuilderParameters buildParams = parseArgs(args, null, null);
            doBuild(buildParams, verbose);
        } catch (Exception e) {
            System.out.println("BuildCertPath error: " + e);
            e.printStackTrace();
            System.exit(1);
        }
    }

    public static PKIXBuilderParameters parseArgs(String[] args,
                                                  String certDir,
                                                  String keyDir)
        throws Exception
    {
        Arguments                    argList = null;
        SunCertPathBuilderParameters buildParams = null;
        String                       caName = null;
        Vector                       collectionCerts = new Vector();
        boolean                      enforceTop = false;
        Set                          extKeyUsageOIDs = new HashSet();
        Set                          initPolIds = new HashSet();
        String                       ldapHost = null;
        int                          ldapPort = 389;
        int                          minTrust = 0;
        PublicKey                    pubKey = null;
        Vector                       storeList = new Vector();
        X509CertSelector             targetSel = new X509CertSelector();
        Set                          trustAnchors = new HashSet(Collections.singleton(new TrustAnchor("cn=sean", new PublicKey () {
            public String getAlgorithm() { return "bogus"; }
            public byte[] getEncoded() { return null; }
            public String getFormat() { return null; } }, null)));

        buildParams = new SunCertPathBuilderParameters(trustAnchors, null);
        trustAnchors.clear();

        argList = new Arguments(args);
        /* check if keystore option was specified */
        while (argList.hasMoreElements()) {
            String option = "";
            try {
                option = argList.getArgLower();
                if (option.equals("-keystore")) {
                    FileInputStream fis = new FileInputStream(argList.getParameter());
                    KeyStore ks = KeyStore.getInstance("jks");
                    ks.load(fis, "changeit".toCharArray());
                    buildParams = new SunCertPathBuilderParameters(ks, null);
                    break;
                }
            } catch (IllegalArgumentException ex) {
                if (debug != null) {
                    debug.println("BuildCertPath.parseArgs exception while" +
                                  " looking for -keystore; option: " + option +
                                  ": " + ex.getMessage());
                    ex.printStackTrace();
                }
                throw ex;
            }
        }

        String option = "";
        try {
            /* parse command line arguments into parameter instance */
            argList = new Arguments(args);
            while (argList.hasMoreElements()) {
                option = argList.getFlagLower();
                if (option.equals("-disablerev")) {
                    buildParams.setRevocationEnabled(false);
                } else if (option.equals("-buildreverse")) {
                    buildParams.setBuildForward(false);
                } else if (option.equals("-requireexppol")) {
                    buildParams.setExplicitPolicyRequired(true);
                } else if (option.equals("-inhibitpolmap")) {
                    buildParams.setPolicyMappingInhibited(true);
                } else if (option.equals("-inhibitanypol")) {
                    buildParams.setAnyPolicyInhibited(true);
                } else if (option.equals("-acceptpolqual")){
                    buildParams.setPolicyQualifiersRejected(false);
                } else if (option.equals("-maxlength")) {
                    buildParams.setMaxPathLength(Integer.parseInt(argList.getParameter()));
                } else if (option.equals("-target")) {
                    targetSel.setSubject(argList.getParameter());
                } else if (option.equals("-subjectaltname")) {
                    String saName = argList.getParameter();
                    TypedName tn = new TypedName(saName);
                    GeneralNameInterface gni = tn.getGeneralNameInterface();
                    DerOutputStream tmp = new DerOutputStream();
                    gni.encode(tmp);
                    targetSel.addSubjectAlternativeName(tn.getType(),
                            tmp.toByteArray());
                } else if (option.equals("-date")) {
                    buildParams.setDate(
                        DateFormat.getDateInstance(
                            DateFormat.SHORT, Locale.US).parse(argList.getParameter()));
                } else if (option.equals("-keyandname")) {
                    String keyFile = null;
                    if (keyDir != null) {
                        keyFile = keyDir + System.getProperty("file.separator")
                            + argList.getParameter();
                    } else
                        keyFile = argList.getParameter();
                    pubKey = TestData.getKeyFromFile(keyFile);
                    caName = argList.getParameter();
                } else if (option.equals("-sigprovider")) {
                    buildParams.setSigProvider(argList.getParameter());
                } else if (option.equals("-trusted")) {
                    String certFile = null;
                    if (certDir != null) {
                        certFile = certDir + System.getProperty("file.separator")
                            + argList.getParameter();
                    } else
                        certFile = argList.getParameter();
                    X509Certificate cert = TestData.getCertFromFile(certFile);
                    if (debug != null)
                        debug.println("PubKey format: " + cert.getPublicKey().getFormat());
                    trustAnchors.add(new TrustAnchor(cert, null));
                } else if (option.equals("-initpolid")) {
                    initPolIds.add(argList.getParameter());
                } else if (option.equals("-mintrust")) {
                    minTrust = Integer.parseInt(argList.getParameter());
                } else if (option.equals("-enforcetop")) {
                    enforceTop = true;
                } else if (option.equals("-keyusage")) {
                    boolean[] keyUsage = new boolean[9];
                    keyUsage[Integer.parseInt(argList.getParameter())] = true;
                    targetSel.setKeyUsage(keyUsage);
                } else if (option.equals("-initialuid")) {
                    String bitString = argList.getParameter();
                    boolean[] bitSet = new boolean[bitString.length()];
                    for (int x = 0; x < bitString.length(); x++) {
                        switch (bitString.charAt(x)) {
                            case '0':
                                bitSet[x] = false;
                                break;
                            case '1':
                                bitSet[x] = true;
                                break;
                            default:
                                printUsage();
                        }
                    }
                    //uniqueID = bitSet;
                } else if (option.equals("-lhost")) {
                    String host = argList.getParameter();
                    if (host.equals("null"))
                        ldapHost = null;
                    else
                        ldapHost = host;
                } else if (option.equals("-lport")) {
                    try { ldapPort = Integer.parseInt(argList.getParameter()); }
                    catch (Exception e) { printUsage(); }
                } else if (option.equals("-extkeyusage")) {
                    extKeyUsageOIDs.add(argList.getParameter());
                } else if (option.equals("-ccscert")) {
                    collectionCerts.add(TestData.getCertFromFile(argList.getParameter()));
                } else if (option.equals("-ccscrl")) {
                    collectionCerts.add(TestData.getCRLFromFile(argList.getParameter()));
                } else if (option.equals("-verbose")) {
                    verbose = true;
                } else if (option.equals("-keystore")) {
                    /* already parsed this option so just ignore and continue */
                    String foo = argList.getParameter();
                    continue;
                } else printUsage();
            }
        } catch (Exception e) {
            if (debug != null) {
                debug.println("BuildCertPath.parseArgs exception while" +
                              " parsing option: "  + option + ": " +
                              e.getMessage());
                e.printStackTrace();
            }
            throw new GeneralSecurityException("Problem parsing option: " +
                                               option + ": " + e.getMessage());
        }

        String initWhat = "";
        try {
            initWhat = "setting trusted certs";
            if (!trustAnchors.isEmpty())
                buildParams.setTrustAnchors(trustAnchors);

            initWhat = "setting CA name and public key";
            if (caName != null) {
                buildParams.setTrustAnchors(Collections.singleton
                    (new TrustAnchor(caName, pubKey, null)));
            }

            initWhat = "setting initial policies";
            if (!initPolIds.isEmpty())
                buildParams.setInitialPolicies(initPolIds);

            initWhat = "setting extended key usage";
            if (!extKeyUsageOIDs.isEmpty())
                targetSel.setExtendedKeyUsage(extKeyUsageOIDs);

            initWhat = "setting trust level";
            if (minTrust > 0 || enforceTop) {
                SampleChecker ic = new SampleChecker(minTrust, targetSel.getSubjectAsString());
                if (!enforceTop)
                    ic.setTopologyChecking(false);
                buildParams.addCertPathChecker(ic);
            }

            initWhat = "testing SunCertPathProvider and services";
            // Test that we can find provider and services
            // Dynamically add provider so that the JRE java.security file does not have to be modified
            //Security.addProvider(new sun.security.provider.certpath.SunCertPathProvider());
            //Provider p = Security.getProvider("SunCertPath");

            initWhat = "creating collection certstore";
            //Create certstores
            if (collectionCerts.size() > 0) {
                CollectionCertStoreParameters params = new
                    CollectionCertStoreParameters(collectionCerts);
                CertStore store = CertStore.getInstance("Collection", params);
                storeList.add(store);
            }
            initWhat = "creating LDAP certstore";
            if (ldapHost != null) {
                /* create LDAPCertStore */
                CertStore store = CertStore.getInstance("LDAP",
                    new LDAPCertStoreParameters(ldapHost, ldapPort));

                /* Test RFC attribute is set correctly for LDAP algorithm */
                String rfc = (String) store.getProvider().get("CertStore.LDAP LDAPSchema");
                storeList.add(store);
            }

            initWhat = "setting cert stores";
            if (storeList.size() > 0)
                buildParams.setCertStores(storeList);

            initWhat = "setting target constraints";
            buildParams.setTargetCertConstraints(targetSel);
        } catch (Exception e) {
            if (debug != null) {
                debug.println("BuildCertPath.parseArgs exception: " + e.getMessage());
                e.printStackTrace();
            }
            throw new GeneralSecurityException("problem " + initWhat + ": " + e.getMessage());
        }

        return buildParams;
    }

    public static void doBuild(PKIXBuilderParameters buildParams, boolean verbose) throws GeneralSecurityException {

        PKIXCertPathBuilderResult buildResult = null;

        try{
            if (debug != null)
                debug.println("BuildCertPath.doBuild() about to get provider");
            /* test PKIX certpath builder */
            CertPathBuilder cpb = CertPathBuilder.getInstance("PKIX");

            /* Test RFC attribute is set correctly for PKIX algorithm */
            String rfc = (String) cpb.getProvider().get("CertPathBuilder.PKIX ValidationAlgorithm");


            buildResult = (PKIXCertPathBuilderResult) cpb.build(buildParams);
            TrustAnchor trustAnchor = (TrustAnchor) buildResult.getTrustAnchor();
            //System.out.println("BuildCertPath: trustAnchor = " + trustAnchor);
            List certificates = buildResult.getCertPath().getCertificates();
            //displayCertPath(certificates, verbose);
        //    System.out.println("BuildCertPath: policyTree: " +
        //                       buildResult.getPolicyTree());
        } catch (GeneralSecurityException e){
            System.out.println("PATH BUILD FAILED");
            throw e;
        }

        System.out.println("PATH BUILD SUCCEEDED");

        /* Run chain through validator to validate build algorithm */
        try{
            CertPathValidator cpv = CertPathValidator.getInstance("PKIX");
            cpv.validate(buildResult.getCertPath(), buildParams);
        } catch (GeneralSecurityException e) {
            System.out.println("PATH VALIDATION FAILED");
            throw e;
        }

        System.out.println("PATH VALIDATION SUCCEEDED");
    }

    private static void displayCertPath(List certificates, boolean verbose)
        throws CertificateException {

        for (int i=0;i<certificates.size();i++) {
            X509CertImpl c = (X509CertImpl) certificates.get(i);
            System.out.println("-------------------------------------------");
            System.out.println("BuildCertPath: certificate# " + (i+1));

            if (verbose) {
                System.out.println(c);
            } else {
                System.out.println("BuildCertPath: topology = " + (new CertTopology(c)));
                System.out.println("BuildCertPath: issuer = " + c.getIssuerDN());
                System.out.println("BuildCertPath: subject = " + c.getSubjectDN());
            }
        }
    }

    private static void printUsage(){
        StringBuffer sb = new StringBuffer();
        sb.append("Usage: BuildCertPath\n");
        sb.append("  -verbose                    : enables verbose output\n");
        sb.append("  -lhost <ldap host name>     : the ldap host\n");
        sb.append("  -lport <ldap port>          : port that the ldap host is using (default: 389)\n");
        sb.append("  -ccsCert <path>             : certificate to go into a CollectionCertStore\n");
        sb.append("  -ccsCRL <path>              : CRL to go into a CollectionCertStore\n");
        sb.append("  -keystore <path>            : keystore holding trusted certs\n");
        sb.append("                              : password MUST be \"changeit\"\n");
        sb.append("  -trusted <path>             : trust anchor certificate\n");
        sb.append("  -keyAndName <pubkey> <DN>   : CA trusted public key and issuer name\n");
        sb.append("  -target <DN>                : target distinguished name\n\n");

        sb.append("  -subjectAltName <type:name> : subject alternative name\n");
        sb.append("  -date <date>                : check validity as of (default: now)\n");
        sb.append("  -keyUsage  <integer>        : keyUsage bit\n");
        sb.append("  -maxLength <integer>        : max path length\n");
        sb.append("  -requireExpPol              : set require explicit policy\n");
        sb.append("  -inhibitPolMap              : set inhibit policy mapping\n");
        sb.append("  -inhibitAnyPol              : set inhibit any policy\n");
        sb.append("  -acceptPolQual              : set accept policy qualifiers\n");
        sb.append("  -initPolID                  : required policy identifier\n");
        sb.append("  -minTrust <integer>         : minimum acceptable trust rating\n");
        sb.append("  -enforceTop                 : enforce topology rules\n");
        sb.append("  -initialUID <bitstring>     : set initial UID\n");
        sb.append("  -extKeyUsage <OID>          : extended key usage\n");
        sb.append("  -checker <java Class name>  : certpath checker class\n");
        sb.append("  -disableRev                 : disable revocation checking\n");
        sb.append("  -buildReverse               : build the certpath from the trusted cert to the target subject\n");
        sb.append("  -sigProvider <path>         : signature provider\n");
        sb.append("The -target AND (-trusted OR -keyAndName) options must be specified.\n");
        sb.append("The -ccsCert, -ccsCRL, -trusted, -subjectAltName, -checker, -extKeyUsage, initPolID, and\n");
        sb.append("-keyUsage options can be specified more than once.\n");
        System.out.println(sb.toString());
        System.exit(1);
    }
}
