jdk/src/share/classes/sun/security/provider/certpath/SunCertPathBuilder.java
changeset 2 90ce3da70b43
child 1238 6d1f4b722acd
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/src/share/classes/sun/security/provider/certpath/SunCertPathBuilder.java	Sat Dec 01 00:00:00 2007 +0000
@@ -0,0 +1,852 @@
+/*
+ * Copyright 2000-2007 Sun Microsystems, Inc.  All Rights Reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Sun designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Sun in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
+ * CA 95054 USA or visit www.sun.com if you need additional information or
+ * have any questions.
+ */
+
+package sun.security.provider.certpath;
+
+import java.io.IOException;
+import java.security.GeneralSecurityException;
+import java.security.InvalidAlgorithmParameterException;
+import java.security.Principal;
+import java.security.PublicKey;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.LinkedList;
+import java.util.Set;
+
+import java.security.cert.*;
+import java.security.interfaces.DSAPublicKey;
+
+import javax.security.auth.x500.X500Principal;
+
+import sun.security.x509.X500Name;
+import sun.security.x509.PKIXExtensions;
+import sun.security.util.Debug;
+
+/**
+ * This class is able to build certification paths in either the forward
+ * or reverse directions.
+ *
+ * <p> If successful, it returns a certification path which has succesfully
+ * satisfied all the constraints and requirements specified in the
+ * PKIXBuilderParameters object and has been validated according to the PKIX
+ * path validation algorithm defined in RFC 3280.
+ *
+ * <p> This implementation uses a depth-first search approach to finding
+ * certification paths. If it comes to a point in which it cannot find
+ * any more certificates leading to the target OR the path length is too long
+ * it backtracks to previous paths until the target has been found or
+ * all possible paths have been exhausted.
+ *
+ * <p> This implementation is not thread-safe.
+ *
+ * @since       1.4
+ * @author      Sean Mullan
+ * @author      Yassir Elley
+ */
+public final class SunCertPathBuilder extends CertPathBuilderSpi {
+
+    private static final Debug debug = Debug.getInstance("certpath");
+
+    /*
+     * private objects shared by methods
+     */
+    private PKIXBuilderParameters buildParams;
+    private CertificateFactory cf;
+    private boolean pathCompleted = false;
+    private X500Principal targetSubjectDN;
+    private PolicyNode policyTreeResult;
+    private TrustAnchor trustAnchor;
+    private PublicKey finalPublicKey;
+    private X509CertSelector targetSel;
+    private List<CertStore> orderedCertStores;
+
+    /**
+     * Create an instance of <code>SunCertPathBuilder</code>.
+     *
+     * @throws CertPathBuilderException if an error occurs
+     */
+    public SunCertPathBuilder() throws CertPathBuilderException {
+        try {
+            cf = CertificateFactory.getInstance("X.509");
+        } catch (CertificateException e) {
+            throw new CertPathBuilderException(e);
+        }
+    }
+
+    /**
+     * Attempts to build a certification path using the Sun build
+     * algorithm from a trusted anchor(s) to a target subject, which must both
+     * be specified in the input parameter set. By default, this method will
+     * attempt to build in the forward direction. In order to build in the
+     * reverse direction, the caller needs to pass in an instance of
+     * SunCertPathBuilderParameters with the buildForward flag set to false.
+     *
+     * <p>The certification path that is constructed is validated
+     * according to the PKIX specification.
+     *
+     * @param params the parameter set for building a path. Must be an instance
+     *  of <code>PKIXBuilderParameters</code>.
+     * @return a certification path builder result.
+     * @exception CertPathBuilderException Exception thrown if builder is
+     *  unable to build a complete certification path from the trusted anchor(s)
+     *  to the target subject.
+     * @throws InvalidAlgorithmParameterException if the given parameters are
+     *  inappropriate for this certification path builder.
+     */
+    public CertPathBuilderResult engineBuild(CertPathParameters params)
+        throws CertPathBuilderException, InvalidAlgorithmParameterException {
+
+        if (debug != null) {
+            debug.println("SunCertPathBuilder.engineBuild(" + params + ")");
+        }
+
+        if (!(params instanceof PKIXBuilderParameters)) {
+            throw new InvalidAlgorithmParameterException("inappropriate " +
+                "parameter type, must be an instance of PKIXBuilderParameters");
+        }
+
+        boolean buildForward = true;
+        if (params instanceof SunCertPathBuilderParameters) {
+            buildForward =
+                ((SunCertPathBuilderParameters)params).getBuildForward();
+        }
+
+        buildParams = (PKIXBuilderParameters)params;
+
+        /* Check mandatory parameters */
+
+        // Make sure that none of the trust anchors include name constraints
+        // (not supported).
+        for (TrustAnchor anchor : buildParams.getTrustAnchors()) {
+            if (anchor.getNameConstraints() != null) {
+                throw new InvalidAlgorithmParameterException
+                    ("name constraints in trust anchor not supported");
+            }
+        }
+
+        CertSelector sel = buildParams.getTargetCertConstraints();
+        if (!(sel instanceof X509CertSelector)) {
+            throw new InvalidAlgorithmParameterException("the "
+                + "targetCertConstraints parameter must be an "
+                + "X509CertSelector");
+        }
+        targetSel = (X509CertSelector)sel;
+        targetSubjectDN = targetSel.getSubject();
+        if (targetSubjectDN == null) {
+            X509Certificate targetCert = targetSel.getCertificate();
+            if (targetCert != null) {
+                targetSubjectDN = targetCert.getSubjectX500Principal();
+            }
+        }
+        // reorder CertStores so that local CertStores are tried first
+        orderedCertStores =
+            new ArrayList<CertStore>(buildParams.getCertStores());
+        Collections.sort(orderedCertStores, new CertStoreComparator());
+        if (targetSubjectDN == null) {
+            targetSubjectDN = getTargetSubjectDN(orderedCertStores, targetSel);
+        }
+        if (targetSubjectDN == null) {
+            throw new InvalidAlgorithmParameterException
+                ("Could not determine unique target subject");
+        }
+
+        List<List<Vertex>> adjList = new ArrayList<List<Vertex>>();
+        CertPathBuilderResult result =
+            buildCertPath(buildForward, false, adjList);
+        if (result == null) {
+            if (debug != null) {
+                debug.println("SunCertPathBuilder.engineBuild: 2nd pass");
+            }
+            // try again
+            adjList.clear();
+            result = buildCertPath(buildForward, true, adjList);
+            if (result == null) {
+                throw new SunCertPathBuilderException("unable to find valid "
+                    + "certification path to requested target",
+                    new AdjacencyList(adjList));
+            }
+        }
+        return result;
+    }
+
+    private CertPathBuilderResult buildCertPath(boolean buildForward,
+        boolean searchAllCertStores, List<List<Vertex>> adjList)
+        throws CertPathBuilderException {
+
+        // Init shared variables and build certification path
+        pathCompleted = false;
+        trustAnchor = null;
+        finalPublicKey = null;
+        policyTreeResult = null;
+        LinkedList<X509Certificate> certPathList =
+            new LinkedList<X509Certificate>();
+        try {
+            if (buildForward) {
+                buildForward(adjList, certPathList, searchAllCertStores);
+            } else {
+                buildReverse(adjList, certPathList);
+            }
+        } catch (Exception e) {
+            if (debug != null) {
+                debug.println("SunCertPathBuilder.engineBuild() exception in "
+                    + "build");
+                e.printStackTrace();
+            }
+            throw new SunCertPathBuilderException("unable to find valid "
+                + "certification path to requested target", e,
+                new AdjacencyList(adjList));
+        }
+
+        // construct SunCertPathBuilderResult
+        try {
+            if (pathCompleted) {
+                if (debug != null)
+                    debug.println("SunCertPathBuilder.engineBuild() "
+                                  + "pathCompleted");
+
+                // we must return a certpath which has the target
+                // as the first cert in the certpath - i.e. reverse
+                // the certPathList
+                Collections.reverse(certPathList);
+
+                return new SunCertPathBuilderResult(
+                    cf.generateCertPath(certPathList), this.trustAnchor,
+                    policyTreeResult, finalPublicKey,
+                    new AdjacencyList(adjList));
+            }
+        } catch (Exception e) {
+            if (debug != null) {
+                debug.println("SunCertPathBuilder.engineBuild() exception "
+                              + "in wrap-up");
+                e.printStackTrace();
+            }
+            throw new SunCertPathBuilderException("unable to find valid "
+                + "certification path to requested target", e,
+                new AdjacencyList(adjList));
+        }
+
+        return null;
+    }
+
+    /*
+     * Private build reverse method.
+     *
+     */
+    private void buildReverse(List<List<Vertex>> adjacencyList,
+        LinkedList<X509Certificate> certPathList) throws Exception
+    {
+        if (debug != null) {
+            debug.println("SunCertPathBuilder.buildReverse()...");
+            debug.println("SunCertPathBuilder.buildReverse() InitialPolicies: "
+                + buildParams.getInitialPolicies());
+        }
+
+        ReverseState currentState = new ReverseState();
+        /* Initialize adjacency list */
+        adjacencyList.clear();
+        adjacencyList.add(new LinkedList<Vertex>());
+
+        /*
+         * Perform a search using each trust anchor, until a valid
+         * path is found
+         */
+        Iterator<TrustAnchor> iter = buildParams.getTrustAnchors().iterator();
+        while (iter.hasNext()) {
+            TrustAnchor anchor = iter.next();
+            /* check if anchor satisfies target constraints */
+            if (anchorIsTarget(anchor, targetSel)) {
+                this.trustAnchor = anchor;
+                this.pathCompleted = true;
+                this.finalPublicKey = anchor.getTrustedCert().getPublicKey();
+                break;
+            }
+
+            /* Initialize current state */
+            currentState.initState(buildParams.getMaxPathLength(),
+                       buildParams.isExplicitPolicyRequired(),
+                       buildParams.isPolicyMappingInhibited(),
+                       buildParams.isAnyPolicyInhibited(),
+                       buildParams.getCertPathCheckers());
+            currentState.updateState(anchor);
+            // init the crl checker
+            currentState.crlChecker =
+                new CrlRevocationChecker(null, buildParams);
+            try {
+                depthFirstSearchReverse(null, currentState,
+                new ReverseBuilder(buildParams, targetSubjectDN), adjacencyList,
+                certPathList);
+            } catch (Exception e) {
+                // continue on error if more anchors to try
+                if (iter.hasNext())
+                    continue;
+                else
+                    throw e;
+            }
+
+            // break out of loop if search is successful
+            break;
+        }
+
+        if (debug != null) {
+            debug.println("SunCertPathBuilder.buildReverse() returned from "
+                + "depthFirstSearchReverse()");
+            debug.println("SunCertPathBuilder.buildReverse() "
+                + "certPathList.size: " + certPathList.size());
+        }
+    }
+
+    /*
+     * Private build forward method.
+     */
+    private void buildForward(List<List<Vertex>> adjacencyList,
+        LinkedList<X509Certificate> certPathList, boolean searchAllCertStores)
+        throws GeneralSecurityException, IOException
+    {
+        if (debug != null) {
+            debug.println("SunCertPathBuilder.buildForward()...");
+        }
+
+        /* Initialize current state */
+        ForwardState currentState = new ForwardState();
+        currentState.initState(buildParams.getCertPathCheckers());
+
+        /* Initialize adjacency list */
+        adjacencyList.clear();
+        adjacencyList.add(new LinkedList<Vertex>());
+
+        // init the crl checker
+        currentState.crlChecker = new CrlRevocationChecker(null, buildParams);
+
+        depthFirstSearchForward(targetSubjectDN, currentState,
+          new ForwardBuilder(buildParams, targetSubjectDN, searchAllCertStores),
+          adjacencyList, certPathList);
+    }
+
+    /*
+     * This method performs a depth first search for a certification
+     * path while building forward which meets the requirements set in
+     * the parameters object.
+     * It uses an adjacency list to store all certificates which were
+     * tried (i.e. at one time added to the path - they may not end up in
+     * the final path if backtracking occurs). This information can
+     * be used later to debug or demo the build.
+     *
+     * See "Data Structure and Algorithms, by Aho, Hopcroft, and Ullman"
+     * for an explanation of the DFS algorithm.
+     *
+     * @param dN the distinguished name being currently searched for certs
+     * @param currentState the current PKIX validation state
+     */
+    void depthFirstSearchForward(X500Principal dN, ForwardState currentState,
+        ForwardBuilder builder, List<List<Vertex>> adjList,
+        LinkedList<X509Certificate> certPathList)
+        throws GeneralSecurityException, IOException
+    {
+        //XXX This method should probably catch & handle exceptions
+
+        if (debug != null) {
+            debug.println("SunCertPathBuilder.depthFirstSearchForward(" + dN
+                + ", " + currentState.toString() + ")");
+        }
+
+        /*
+         * Find all the certificates issued to dN which
+         * satisfy the PKIX certification path constraints.
+         */
+        List<Vertex> vertices = addVertices
+           (builder.getMatchingCerts(currentState, orderedCertStores), adjList);
+        if (debug != null) {
+            debug.println("SunCertPathBuilder.depthFirstSearchForward(): "
+                + "certs.size=" + vertices.size());
+        }
+
+        /*
+         * For each cert in the collection, verify anything
+         * that hasn't been checked yet (signature, revocation, etc)
+         * and check for loops. Call depthFirstSearchForward()
+         * recursively for each good cert.
+         */
+
+               vertices:
+        for (Vertex vertex : vertices) {
+            /**
+             * Restore state to currentState each time through the loop.
+             * This is important because some of the user-defined
+             * checkers modify the state, which MUST be restored if
+             * the cert eventually fails to lead to the target and
+             * the next matching cert is tried.
+             */
+            ForwardState nextState = (ForwardState) currentState.clone();
+            X509Certificate cert = (X509Certificate) vertex.getCertificate();
+
+            try {
+                builder.verifyCert(cert, nextState, certPathList);
+            } catch (GeneralSecurityException gse) {
+                if (debug != null) {
+                    debug.println("SunCertPathBuilder.depthFirstSearchForward()"
+                        + ": validation failed: " + gse);
+                    gse.printStackTrace();
+                }
+                vertex.setThrowable(gse);
+                continue;
+            }
+
+            /*
+             * Certificate is good.
+             * If cert completes the path,
+             *    process userCheckers that don't support forward checking
+             *    and process policies over whole path
+             *    and backtrack appropriately if there is a failure
+             * else if cert does not complete the path,
+             *    add it to the path
+             */
+            if (builder.isPathCompleted(cert)) {
+
+                BasicChecker basicChecker = null;
+                if (debug != null)
+                    debug.println("SunCertPathBuilder.depthFirstSearchForward()"
+                        + ": commencing final verification");
+
+                ArrayList<X509Certificate> appendedCerts =
+                    new ArrayList<X509Certificate>(certPathList);
+
+                /*
+                 * if the trust anchor selected is specified as a trusted
+                 * public key rather than a trusted cert, then verify this
+                 * cert (which is signed by the trusted public key), but
+                 * don't add it yet to the certPathList
+                 */
+                if (builder.trustAnchor.getTrustedCert() == null) {
+                    appendedCerts.add(0, cert);
+                }
+
+                HashSet<String> initExpPolSet = new HashSet<String>(1);
+                initExpPolSet.add(PolicyChecker.ANY_POLICY);
+
+                PolicyNodeImpl rootNode = new PolicyNodeImpl(null,
+                    PolicyChecker.ANY_POLICY, null, false, initExpPolSet, false);
+
+                PolicyChecker policyChecker
+                    = new PolicyChecker(buildParams.getInitialPolicies(),
+                                appendedCerts.size(),
+                                buildParams.isExplicitPolicyRequired(),
+                                buildParams.isPolicyMappingInhibited(),
+                                buildParams.isAnyPolicyInhibited(),
+                                buildParams.getPolicyQualifiersRejected(),
+                                rootNode);
+
+                List<PKIXCertPathChecker> userCheckers = new
+                    ArrayList<PKIXCertPathChecker>
+                        (buildParams.getCertPathCheckers());
+                int mustCheck = 0;
+                userCheckers.add(mustCheck, policyChecker);
+                mustCheck++;
+
+                if (nextState.keyParamsNeeded()) {
+                    PublicKey rootKey = cert.getPublicKey();
+                    if (builder.trustAnchor.getTrustedCert() == null) {
+                        rootKey = builder.trustAnchor.getCAPublicKey();
+                        if (debug != null)
+                            debug.println("SunCertPathBuilder.depthFirstSearchForward" +
+                                          " using buildParams public key: " +
+                                          rootKey.toString());
+                    }
+                    TrustAnchor anchor = new TrustAnchor
+                        (cert.getSubjectX500Principal(), rootKey, null);
+                    basicChecker = new BasicChecker(anchor,
+                                           builder.date,
+                                           buildParams.getSigProvider(),
+                                           true);
+                    userCheckers.add(mustCheck, basicChecker);
+                    mustCheck++;
+                    if (buildParams.isRevocationEnabled()) {
+                        userCheckers.add(mustCheck,
+                            new CrlRevocationChecker(anchor, buildParams));
+                        mustCheck++;
+                    }
+                }
+
+                for (int i=0; i<appendedCerts.size(); i++) {
+                    X509Certificate currCert = appendedCerts.get(i);
+                    if (debug != null)
+                        debug.println("current subject = "
+                                      + currCert.getSubjectX500Principal());
+                    Set<String> unresCritExts =
+                        currCert.getCriticalExtensionOIDs();
+                    if (unresCritExts == null) {
+                        unresCritExts = Collections.<String>emptySet();
+                    }
+
+                    for (int j=0; j<userCheckers.size(); j++) {
+                        PKIXCertPathChecker currChecker = userCheckers.get(j);
+                        if (j < mustCheck ||
+                            !currChecker.isForwardCheckingSupported())
+                        {
+                            if (i == 0) {
+                                currChecker.init(false);
+                            }
+
+                            try {
+                                currChecker.check(currCert, unresCritExts);
+                            } catch (CertPathValidatorException cpve) {
+                                if (debug != null)
+                                    debug.println
+                                    ("SunCertPathBuilder.depthFirstSearchForward(): " +
+                                    "final verification failed: " + cpve);
+                                vertex.setThrowable(cpve);
+                                continue vertices;
+                            }
+                        }
+                    }
+
+                    /*
+                     * Remove extensions from user checkers that support
+                     * forward checking. After this step, we will have
+                     * removed all extensions that all user checkers
+                     * are capable of processing.
+                     */
+                    for (PKIXCertPathChecker checker :
+                         buildParams.getCertPathCheckers())
+                    {
+                        if (checker.isForwardCheckingSupported()) {
+                            Set<String> suppExts =
+                                checker.getSupportedExtensions();
+                            if (suppExts != null) {
+                                unresCritExts.removeAll(suppExts);
+                            }
+                        }
+                    }
+
+                    if (!unresCritExts.isEmpty()) {
+                        unresCritExts.remove
+                            (PKIXExtensions.BasicConstraints_Id.toString());
+                        unresCritExts.remove
+                            (PKIXExtensions.NameConstraints_Id.toString());
+                        unresCritExts.remove
+                            (PKIXExtensions.CertificatePolicies_Id.toString());
+                        unresCritExts.remove
+                            (PKIXExtensions.PolicyMappings_Id.toString());
+                        unresCritExts.remove
+                            (PKIXExtensions.PolicyConstraints_Id.toString());
+                        unresCritExts.remove
+                            (PKIXExtensions.InhibitAnyPolicy_Id.toString());
+                        unresCritExts.remove(PKIXExtensions.
+                            SubjectAlternativeName_Id.toString());
+                        unresCritExts.remove
+                            (PKIXExtensions.KeyUsage_Id.toString());
+                        unresCritExts.remove
+                            (PKIXExtensions.ExtendedKeyUsage_Id.toString());
+
+                        if (!unresCritExts.isEmpty()) {
+                            throw new CertPathValidatorException("unrecognized "
+                                + "critical extension(s)");
+                        }
+                    }
+                }
+                if (debug != null)
+                    debug.println("SunCertPathBuilder.depthFirstSearchForward()"
+                        + ": final verification succeeded - path completed!");
+                pathCompleted = true;
+
+                /*
+                 * if the user specified a trusted public key rather than
+                 * trusted certs, then add this cert (which is signed by
+                 * the trusted public key) to the certPathList
+                 */
+                if (builder.trustAnchor.getTrustedCert() == null)
+                    builder.addCertToPath(cert, certPathList);
+                // Save the trust anchor
+                this.trustAnchor = builder.trustAnchor;
+
+                /*
+                 * Extract and save the final target public key
+                 */
+                if (basicChecker != null) {
+                    finalPublicKey = basicChecker.getPublicKey();
+                } else {
+                    Certificate finalCert;
+                    if (certPathList.size() == 0) {
+                        finalCert = builder.trustAnchor.getTrustedCert();
+                    } else {
+                        finalCert = certPathList.get(certPathList.size()-1);
+                    }
+                    finalPublicKey = finalCert.getPublicKey();
+                }
+
+                policyTreeResult = policyChecker.getPolicyTree();
+                return;
+            } else {
+                builder.addCertToPath(cert, certPathList);
+            }
+
+            /* Update the PKIX state */
+            nextState.updateState(cert);
+
+            /*
+             * Append an entry for cert in adjacency list and
+             * set index for current vertex.
+             */
+            adjList.add(new LinkedList<Vertex>());
+            vertex.setIndex(adjList.size() - 1);
+
+            /* recursively search for matching certs at next dN */
+            depthFirstSearchForward(cert.getIssuerX500Principal(), nextState, builder,
+                adjList, certPathList);
+
+            /*
+             * If path has been completed, return ASAP!
+             */
+            if (pathCompleted) {
+                return;
+            } else {
+                /*
+                 * If we get here, it means we have searched all possible
+                 * certs issued by the dN w/o finding any matching certs.
+                 * This means we have to backtrack to the previous cert in
+                 * the path and try some other paths.
+                 */
+                if (debug != null)
+                    debug.println("SunCertPathBuilder.depthFirstSearchForward()"
+                        + ": backtracking");
+                builder.removeFinalCertFromPath(certPathList);
+            }
+        }
+    }
+
+    /*
+     * This method performs a depth first search for a certification
+     * path while building reverse which meets the requirements set in
+     * the parameters object.
+     * It uses an adjacency list to store all certificates which were
+     * tried (i.e. at one time added to the path - they may not end up in
+     * the final path if backtracking occurs). This information can
+     * be used later to debug or demo the build.
+     *
+     * See "Data Structure and Algorithms, by Aho, Hopcroft, and Ullman"
+     * for an explanation of the DFS algorithm.
+     *
+     * @param dN the distinguished name being currently searched for certs
+     * @param currentState the current PKIX validation state
+     */
+    void depthFirstSearchReverse(X500Principal dN, ReverseState currentState,
+        ReverseBuilder builder, List<List<Vertex>> adjList,
+        LinkedList<X509Certificate> certPathList)
+        throws GeneralSecurityException, IOException
+    {
+        if (debug != null)
+            debug.println("SunCertPathBuilder.depthFirstSearchReverse(" + dN
+                + ", " + currentState.toString() + ")");
+
+        /*
+         * Find all the certificates issued by dN which
+         * satisfy the PKIX certification path constraints.
+         */
+        List<Vertex> vertices = addVertices
+           (builder.getMatchingCerts(currentState, orderedCertStores), adjList);
+        if (debug != null)
+            debug.println("SunCertPathBuilder.depthFirstSearchReverse(): "
+                + "certs.size=" + vertices.size());
+
+        /*
+         * For each cert in the collection, verify anything
+         * that hasn't been checked yet (signature, revocation, etc)
+         * and check for loops. Call depthFirstSearchReverse()
+         * recursively for each good cert.
+         */
+        for (Vertex vertex : vertices) {
+            /**
+             * Restore state to currentState each time through the loop.
+             * This is important because some of the user-defined
+             * checkers modify the state, which MUST be restored if
+             * the cert eventually fails to lead to the target and
+             * the next matching cert is tried.
+             */
+            ReverseState nextState = (ReverseState) currentState.clone();
+            X509Certificate cert = (X509Certificate) vertex.getCertificate();
+            try {
+                builder.verifyCert(cert, nextState, certPathList);
+            } catch (GeneralSecurityException gse) {
+                if (debug != null)
+                    debug.println("SunCertPathBuilder.depthFirstSearchReverse()"
+                        + ": validation failed: " + gse);
+                vertex.setThrowable(gse);
+                continue;
+            }
+
+            /*
+             * Certificate is good, add it to the path (if it isn't a
+             * self-signed cert) and update state
+             */
+            if (!currentState.isInitial())
+                builder.addCertToPath(cert, certPathList);
+            // save trust anchor
+            this.trustAnchor = currentState.trustAnchor;
+
+            /*
+             * Check if path is completed, return ASAP if so.
+             */
+            if (builder.isPathCompleted(cert)) {
+                if (debug != null)
+                    debug.println("SunCertPathBuilder.depthFirstSearchReverse()"
+                        + ": path completed!");
+                pathCompleted = true;
+
+                PolicyNodeImpl rootNode = nextState.rootNode;
+
+                if (rootNode == null)
+                    policyTreeResult = null;
+                else {
+                    policyTreeResult = rootNode.copyTree();
+                    ((PolicyNodeImpl)policyTreeResult).setImmutable();
+                }
+
+                /*
+                 * Extract and save the final target public key
+                 */
+                finalPublicKey = cert.getPublicKey();
+                if (finalPublicKey instanceof DSAPublicKey &&
+                    ((DSAPublicKey)finalPublicKey).getParams() == null)
+                {
+                    finalPublicKey =
+                        BasicChecker.makeInheritedParamsKey
+                            (finalPublicKey, currentState.pubKey);
+                }
+
+                return;
+            }
+
+            /* Update the PKIX state */
+            nextState.updateState(cert);
+
+            /*
+             * Append an entry for cert in adjacency list and
+             * set index for current vertex.
+             */
+            adjList.add(new LinkedList<Vertex>());
+            vertex.setIndex(adjList.size() - 1);
+
+            /* recursively search for matching certs at next dN */
+            depthFirstSearchReverse(cert.getSubjectX500Principal(), nextState,
+                builder, adjList, certPathList);
+
+            /*
+             * If path has been completed, return ASAP!
+             */
+            if (pathCompleted) {
+                return;
+            } else {
+                /*
+                 * If we get here, it means we have searched all possible
+                 * certs issued by the dN w/o finding any matching certs. This
+                 * means we have to backtrack to the previous cert in the path
+                 * and try some other paths.
+                 */
+                if (debug != null)
+                    debug.println("SunCertPathBuilder.depthFirstSearchReverse()"
+                        + ": backtracking");
+                if (!currentState.isInitial())
+                    builder.removeFinalCertFromPath(certPathList);
+            }
+        }
+        if (debug != null)
+            debug.println("SunCertPathBuilder.depthFirstSearchReverse() all "
+                + "certs in this adjacency list checked");
+    }
+
+    /*
+     * Adds a collection of matching certificates to the
+     * adjacency list.
+     */
+    private List<Vertex> addVertices(Collection<X509Certificate> certs,
+        List<List<Vertex>> adjList) {
+        List<Vertex> l = adjList.get(adjList.size() - 1);
+
+        for (X509Certificate cert : certs) {
+           Vertex v = new Vertex(cert);
+           l.add(v);
+        }
+
+        return l;
+    }
+
+    /**
+     * Returns true if trust anchor certificate matches specified
+     * certificate constraints.
+     */
+    private boolean anchorIsTarget(TrustAnchor anchor, X509CertSelector sel) {
+        X509Certificate anchorCert = anchor.getTrustedCert();
+        if (anchorCert != null) {
+            return sel.match(anchorCert);
+        }
+        return false;
+    }
+
+    /**
+     * Comparator that orders CertStores so that local CertStores come before
+     * remote CertStores.
+     */
+    private static class CertStoreComparator implements Comparator<CertStore> {
+        public int compare(CertStore store1, CertStore store2) {
+            if (Builder.isLocalCertStore(store1)) {
+                return -1;
+            } else {
+                return 1;
+            }
+        }
+    }
+
+    /**
+     * Returns the target subject DN from the first X509Certificate that
+     * is fetched that matches the specified X509CertSelector.
+     */
+    private X500Principal getTargetSubjectDN(List<CertStore> stores,
+        X509CertSelector targetSel) {
+        for (CertStore store : stores) {
+            try {
+                Collection<? extends Certificate> targetCerts =
+                    (Collection<? extends Certificate>)
+                        store.getCertificates(targetSel);
+                if (!targetCerts.isEmpty()) {
+                    X509Certificate targetCert =
+                        (X509Certificate)targetCerts.iterator().next();
+                    return targetCert.getSubjectX500Principal();
+                }
+            } catch (CertStoreException e) {
+                // ignore but log it
+                if (debug != null) {
+                    debug.println("SunCertPathBuilder.getTargetSubjectDN: " +
+                        "non-fatal exception retrieving certs: " + e);
+                    e.printStackTrace();
+                }
+            }
+        }
+        return null;
+    }
+}