Improve path building

This CL changes certificate path building from building the first
possible chain only to building all possible chains until a valid chain
is found or all potential chains are exhausted. This will allow us to
more gracefully handle CA and intermediate changes.

This CL does _not_ change the verification step in any way, all chains
generated are still verified the same as they were before.

(cherry-picked from commit 381c900af12815e6f0c01519d8ebdd57297303e9)
Change-Id: Ia8c4cd4131eb6ddf299da144b963a24cd1b64605
diff --git a/src/main/java/org/conscrypt/ChainStrengthAnalyzer.java b/src/main/java/org/conscrypt/ChainStrengthAnalyzer.java
index ffbb909..43b3f74 100644
--- a/src/main/java/org/conscrypt/ChainStrengthAnalyzer.java
+++ b/src/main/java/org/conscrypt/ChainStrengthAnalyzer.java
@@ -21,6 +21,7 @@
 import java.security.interfaces.DSAPublicKey;
 import java.security.interfaces.ECPublicKey;
 import java.security.interfaces.RSAPublicKey;
+import java.util.List;
 
 public final class ChainStrengthAnalyzer {
 
@@ -39,11 +40,27 @@
 
     public static final void check(X509Certificate[] chain) throws CertificateException {
         for (X509Certificate cert : chain) {
-            checkCert(cert);
+            try {
+                checkCert(cert);
+            } catch (CertificateException e) {
+                throw new CertificateException("Unacceptable certificate: "
+                        + cert.getSubjectX500Principal(), e);
+            }
         }
     }
 
-    private static final void checkCert(X509Certificate cert) throws CertificateException {
+    public static final void check(List<X509Certificate> chain) throws CertificateException {
+        for (X509Certificate cert : chain) {
+            try {
+                checkCert(cert);
+            } catch (CertificateException e) {
+                throw new CertificateException("Unacceptable certificate: "
+                        + cert.getSubjectX500Principal(), e);
+            }
+        }
+    }
+
+    public static final void checkCert(X509Certificate cert) throws CertificateException {
         checkKeyLength(cert);
         checkSignatureAlgorithm(cert);
     }
diff --git a/src/platform/java/org/conscrypt/CertificatePriorityComparator.java b/src/platform/java/org/conscrypt/CertificatePriorityComparator.java
new file mode 100644
index 0000000..038a439
--- /dev/null
+++ b/src/platform/java/org/conscrypt/CertificatePriorityComparator.java
@@ -0,0 +1,169 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.conscrypt;
+
+import java.security.PublicKey;
+import java.security.cert.X509Certificate;
+import java.security.interfaces.ECPublicKey;
+import java.security.interfaces.RSAPublicKey;
+
+import java.util.Comparator;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.Locale;
+import java.util.Map;
+
+/**
+ * {@link Comparator} for prioritizing certificates in path building.
+ *
+ * <p>
+ * The sort order is as follows:
+ * <ol>
+ * <li>Self-issued certificates first.</li>
+ * <li>Strength of certificates descending (EC before RSA, key size descending, signature
+ * algorithm strength descending).</li>
+ * <li>notAfter date descending.</li>
+ * <li>notBefore date descending.</li>
+ * </ol>
+ * </p>
+ */
+public final class CertificatePriorityComparator implements Comparator<X509Certificate> {
+
+    /**
+     * Map of signature algorithm OIDs to priorities. OIDs with a lower priority will be sorted
+     * before those with higher.
+     */
+    private static final Map<String, Integer> ALGORITHM_OID_PRIORITY_MAP;
+
+    /*
+     * Priorities of digest algorithms. Lower is better.
+     */
+    private static final Integer PRIORITY_MD5 = 6;
+    private static final Integer PRIORITY_SHA1 = 5;
+    private static final Integer PRIORITY_SHA224 = 4;
+    private static final Integer PRIORITY_SHA256 = 3;
+    private static final Integer PRIORITY_SHA384 = 2;
+    private static final Integer PRIORITY_SHA512 = 1;
+    private static final Integer PRIORITY_UNKNOWN = -1;
+    static {
+        ALGORITHM_OID_PRIORITY_MAP = new HashMap<String, Integer>();
+        // RSA oids
+        ALGORITHM_OID_PRIORITY_MAP.put("1.2.840.113549.1.1.13", PRIORITY_SHA512);
+        ALGORITHM_OID_PRIORITY_MAP.put("1.2.840.113549.1.1.12", PRIORITY_SHA384);
+        ALGORITHM_OID_PRIORITY_MAP.put("1.2.840.113549.1.1.11", PRIORITY_SHA256);
+        ALGORITHM_OID_PRIORITY_MAP.put("1.2.840.113549.1.1.14", PRIORITY_SHA224);
+        ALGORITHM_OID_PRIORITY_MAP.put("1.2.840.113549.1.1.5", PRIORITY_SHA1);
+        ALGORITHM_OID_PRIORITY_MAP.put("1.2.840.113549.1.1.4", PRIORITY_MD5);
+        // ECDSA oids
+        ALGORITHM_OID_PRIORITY_MAP.put("1.2.840.10045.4.3.4", PRIORITY_SHA512);
+        ALGORITHM_OID_PRIORITY_MAP.put("1.2.840.10045.4.3.3", PRIORITY_SHA384);
+        ALGORITHM_OID_PRIORITY_MAP.put("1.2.840.10045.4.3.2", PRIORITY_SHA256);
+        ALGORITHM_OID_PRIORITY_MAP.put("1.2.840.10045.4.3.1", PRIORITY_SHA224);
+        ALGORITHM_OID_PRIORITY_MAP.put("1.2.840.10045.4.1", PRIORITY_SHA1);
+    }
+
+    @Override
+    public int compare(X509Certificate lhs, X509Certificate rhs) {
+        int result;
+        boolean lhsSelfSigned = lhs.getSubjectDN().equals(lhs.getIssuerDN());
+        boolean rhsSelfSigned = rhs.getSubjectDN().equals(rhs.getIssuerDN());
+        // Self-issued before not self-issued to avoid trying bridge certs first.
+        if (lhsSelfSigned != rhsSelfSigned) {
+            return rhsSelfSigned ? 1 : -1;
+        }
+        // Strength descending.
+        result = compareStrength(rhs, lhs);
+        if (result != 0) {
+            return result;
+        }
+        // notAfter descending.
+        Date lhsNotAfter = lhs.getNotAfter();
+        Date rhsNotAfter = rhs.getNotAfter();
+        result = rhsNotAfter.compareTo(lhsNotAfter);
+        if (result != 0) {
+            return result;
+        }
+        // notBefore descending.
+        Date lhsNotBefore = lhs.getNotBefore();
+        Date rhsNotBefore = rhs.getNotBefore();
+        return rhsNotBefore.compareTo(lhsNotBefore);
+    }
+
+    private int compareStrength(X509Certificate lhs, X509Certificate rhs) {
+        int result;
+        PublicKey lhsPublicKey = lhs.getPublicKey();
+        PublicKey rhsPublicKey = rhs.getPublicKey();
+        result = compareKeyAlgorithm(lhsPublicKey, rhsPublicKey);
+        if (result != 0) {
+            return result;
+        }
+        result = compareKeySize(lhsPublicKey, rhsPublicKey);
+        if (result != 0) {
+            return result;
+        }
+        return compareSignatureAlgorithm(lhs, rhs);
+    }
+
+    private int compareKeyAlgorithm(PublicKey lhs, PublicKey rhs) {
+        String lhsAlgorithm = lhs.getAlgorithm().toUpperCase(Locale.US);
+        String rhsAlgorithm = rhs.getAlgorithm().toUpperCase(Locale.US);
+
+        if (lhsAlgorithm.equals(rhsAlgorithm)) {
+            return 0;
+        }
+
+        // Prefer EC to RSA.
+        if ("EC".equals(lhsAlgorithm)) {
+            return 1;
+        } else {
+            return -1;
+        }
+    }
+
+    private int compareKeySize(PublicKey lhs, PublicKey rhs) {
+        String lhsAlgorithm = lhs.getAlgorithm().toUpperCase(Locale.US);
+        String rhsAlgorithm = rhs.getAlgorithm().toUpperCase(Locale.US);
+        if (!lhsAlgorithm.equals(rhsAlgorithm)) {
+            throw new IllegalArgumentException("Keys are not of the same type");
+        }
+        int lhsSize = getKeySize(lhs);
+        int rhsSize = getKeySize(rhs);
+        return lhsSize - rhsSize;
+    }
+
+    private int getKeySize(PublicKey pkey) {
+        if (pkey instanceof ECPublicKey) {
+            return ((ECPublicKey) pkey).getParams().getCurve().getField().getFieldSize();
+        } else if (pkey instanceof RSAPublicKey) {
+            return ((RSAPublicKey) pkey).getModulus().bitLength();
+        } else {
+            throw new IllegalArgumentException(
+                    "Unsupported public key type: " + pkey.getClass().getName());
+        }
+    }
+
+    private int compareSignatureAlgorithm(X509Certificate lhs, X509Certificate rhs) {
+        Integer lhsPriority = ALGORITHM_OID_PRIORITY_MAP.get(lhs.getSigAlgOID());
+        Integer rhsPriority = ALGORITHM_OID_PRIORITY_MAP.get(rhs.getSigAlgOID());
+        if (lhsPriority == null) {
+            lhsPriority = PRIORITY_UNKNOWN;
+        }
+        if (rhsPriority == null) {
+            rhsPriority = PRIORITY_UNKNOWN;
+        }
+        return rhsPriority - lhsPriority;
+    }
+}
diff --git a/src/platform/java/org/conscrypt/TrustManagerImpl.java b/src/platform/java/org/conscrypt/TrustManagerImpl.java
index 5719b5a..4bbf593 100644
--- a/src/platform/java/org/conscrypt/TrustManagerImpl.java
+++ b/src/platform/java/org/conscrypt/TrustManagerImpl.java
@@ -36,6 +36,8 @@
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Collections;
+import java.util.Comparator;
+import java.util.Date;
 import java.util.Enumeration;
 import java.util.HashSet;
 import java.util.List;
@@ -60,6 +62,12 @@
 public final class TrustManagerImpl extends X509ExtendedTrustManager {
 
     /**
+     * Comparator used for ordering trust anchors during certificate path building.
+     */
+    private static final TrustAnchorComparator TRUST_ANCHOR_COMPARATOR =
+            new TrustAnchorComparator();
+
+    /**
      * The AndroidCAStore if non-null, null otherwise.
      */
     private final KeyStore rootKeyStore;
@@ -331,203 +339,269 @@
         return checkTrusted(chain, authType, hostname, clientAuth);
     }
 
-    private List<X509Certificate> checkTrusted(X509Certificate[] chain, String authType,
+    private List<X509Certificate> checkTrusted(X509Certificate[] certs, String authType,
             String host, boolean clientAuth) throws CertificateException {
-        if (chain == null || chain.length == 0 || authType == null || authType.length() == 0) {
+        if (certs == null || certs.length == 0 || authType == null || authType.length() == 0) {
             throw new IllegalArgumentException("null or zero-length parameter");
         }
         if (err != null) {
             throw new CertificateException(err);
         }
+        Set<X509Certificate> used = new HashSet<X509Certificate>();
+        ArrayList<X509Certificate> untrustedChain = new ArrayList<X509Certificate>();
+        ArrayList<TrustAnchor> trustedChain = new ArrayList<TrustAnchor>();
+        // Initialize the chain to contain the leaf certificate. This potentially could be a trust
+        // anchor. If the leaf is a trust anchor we still continue with path building to build the
+        // complete trusted chain for additional validation such as certificate pinning.
+        X509Certificate leaf = certs[0];
+        TrustAnchor leafAsAnchor = findTrustAnchorBySubjectAndPublicKey(leaf);
+        if (leafAsAnchor != null) {
+            trustedChain.add(leafAsAnchor);
+            used.add(leafAsAnchor.getTrustedCert());
+        } else {
+            untrustedChain.add(leaf);
+        }
+        used.add(leaf);
+        return checkTrustedRecursive(certs, host, clientAuth, untrustedChain, trustedChain, used);
+    }
 
-        // get the cleaned up chain and trust anchor
-        Set<TrustAnchor> trustAnchor = new HashSet<TrustAnchor>(); // there can only be one!
-        X509Certificate[] newChain = cleanupCertChainAndFindTrustAnchors(chain, trustAnchor);
-
-        // add the first trust anchor to the chain, which may be an intermediate
-        List<X509Certificate> wholeChain = new ArrayList<X509Certificate>();
-        wholeChain.addAll(Arrays.asList(newChain));
-        // trustAnchor is actually just a single element
-        for (TrustAnchor trust : trustAnchor) {
-            wholeChain.add(trust.getTrustedCert());
+    /**
+     * Recursively build certificate chains until a valid chain is found or all possible paths are
+     * exhausted.
+     *
+     * The chain is built in two sections, the complete trusted path is the the combination of
+     * {@code untrustedChain} and {@code trustAnchorChain}. The chain begins at the leaf
+     * certificate and ends in the final trusted root certificate.
+     *
+     * @param certs the bag of certs provided by the peer. No order is assumed.
+     * @param host the host being connected to.
+     * @param clientAuth if a client is being authorized instead of a server.
+     * @param untrustedChain the untrusted section of the chain built so far. Must be mutable.
+     * @param trustAnchorChain the trusted section of the chain built so far. Must be mutable.
+     * @param used the set certificates used so far in path building. Must be mutable.
+     *
+     * @return The entire valid chain starting with the leaf certificate. This is the
+     * concatenation of untrustedChain and trustAnchorChain.
+     *
+     * @throws CertificateException If no valid chain could be constructed. Note that there may be
+     * multiple reasons why no valid chain exists and there is no guarantee that the most severe is
+     * reported in this exception. As such applications MUST NOT use the specifics of this error
+     * for trust decisions (e.g. showing the user a click through page based on the specific error).
+     */
+    private List<X509Certificate> checkTrustedRecursive(X509Certificate[] certs,
+            String host, boolean clientAuth, ArrayList<X509Certificate> untrustedChain,
+            ArrayList<TrustAnchor> trustAnchorChain,
+            Set<X509Certificate> used) throws CertificateException {
+        CertificateException lastException = null;
+        X509Certificate current;
+        if (trustAnchorChain.isEmpty()) {
+            current = untrustedChain.get(untrustedChain.size() - 1);
+        } else {
+            current = trustAnchorChain.get(trustAnchorChain.size() - 1).getTrustedCert();
         }
 
-        // add all the cached certificates from the cert index, avoiding loops
-        // this gives us a full chain from leaf to root, which we use for cert pinning and pass
-        // back out to callers when we return.
-        X509Certificate last = wholeChain.get(wholeChain.size() - 1);
-        while (true) {
-            TrustAnchor cachedTrust = trustedCertificateIndex.findByIssuerAndSignature(last);
-            // the cachedTrust can be null if there isn't anything in the index or if a user has
-            // trusted a non-self-signed cert.
-            if (cachedTrust == null) {
-                break;
-            }
-
-            // at this point we have a cached trust anchor, but don't know if its one we got from
-            // the server. Extract the cert, compare it to the last element in the chain, and add it
-            // if we haven't seen it before.
-            X509Certificate next = cachedTrust.getTrustedCert();
-            if (next != last) {
-                wholeChain.add(next);
-                last = next;
-            } else {
-                // if next == last then we found a self-signed cert and the chain is done
-                break;
-            }
+        // 1. If the current certificate in the chain is self-signed verify the chain as is.
+        if (current.getIssuerDN().equals(current.getSubjectDN())) {
+            return verifyChain(untrustedChain, trustAnchorChain, host, clientAuth);
         }
 
-        // build the cert path from the array of certs sans trust anchors
-        CertPath certPath = factory.generateCertPath(Arrays.asList(newChain));
-
-        if (host != null) {
-            boolean isChainValid = false;
+        // 2. Try building a chain via any trust anchors that issued the current certificate.
+        // Note that we do not stop at the first trust anchor since it is possible that the trust
+        // anchor is not self-signed and its issuer may be needed for additional validation such as
+        // certificate pinning. In the common case the first trust anchor will be self-signed or
+        // its issuer's certificate will be missing.
+        Set<TrustAnchor> anchors = findAllTrustAnchorsByIssuerAndSignature(current);
+        boolean seenIssuer = false;
+        for (TrustAnchor anchor : sortPotentialAnchors(anchors)) {
+            X509Certificate anchorCert = anchor.getTrustedCert();
+            // Avoid using certificates that have already been used.
+            if (used.contains(anchorCert)) {
+                continue;
+            }
+            seenIssuer = true;
+            used.add(anchorCert);
+            trustAnchorChain.add(anchor);
             try {
-                isChainValid = pinManager.isChainValid(host, wholeChain);
-            } catch (PinManagerException e) {
-                throw new CertificateException(e);
+                return checkTrustedRecursive(certs, host, clientAuth, untrustedChain,
+                        trustAnchorChain, used);
+            } catch (CertificateException ex) {
+                lastException = ex;
             }
-            if (!isChainValid) {
-                throw new CertificateException(new CertPathValidatorException(
-                        "Certificate path is not properly pinned.", null, certPath, -1));
+            // Could not form a valid chain via this certificate, remove it from this chain.
+            trustAnchorChain.remove(trustAnchorChain.size() - 1);
+            used.remove(anchorCert);
+        }
+
+        // 3. If we were unable to find additional trusted issuers, verify the current chain.
+        // This may happen if the root of trust is not self-signed and the issuer is not
+        // present in the trusted set.
+        if (!trustAnchorChain.isEmpty()) {
+            if (!seenIssuer) {
+                return verifyChain(untrustedChain, trustAnchorChain, host, clientAuth);
+            }
+
+            // Otherwise all chains based on the current trust anchor were rejected, fail.
+            throw lastException;
+        }
+
+        // 4. Use the certificates provided by the peer to grow the chain.
+        // Ignore the first certificate, as that is the leaf certificate.
+        for (int i = 1; i < certs.length; i++) {
+            X509Certificate candidateIssuer = certs[i];
+            // Avoid using certificates that have already been used.
+            if (used.contains(candidateIssuer)) {
+                continue;
+            }
+            if (current.getIssuerDN().equals(candidateIssuer.getSubjectDN())) {
+                // Check the strength and validity of the certificate to prune bad certificates
+                // early.
+                try {
+                    candidateIssuer.checkValidity();
+                    ChainStrengthAnalyzer.checkCert(candidateIssuer);
+                } catch (CertificateException ex) {
+                    lastException = new CertificateException("Unacceptable certificate: "
+                            + candidateIssuer.getSubjectX500Principal(), ex);
+                    continue;
+                }
+                used.add(candidateIssuer);
+                untrustedChain.add(candidateIssuer);
+                try {
+                    return checkTrustedRecursive(certs, host, clientAuth, untrustedChain,
+                            trustAnchorChain, used);
+                } catch (CertificateException ex) {
+                    lastException = ex;
+                }
+                // Could not form a valid chain via this certificate, remove it from this chain.
+                used.remove(candidateIssuer);
+                untrustedChain.remove(untrustedChain.size() - 1);
             }
         }
 
-        if (newChain.length == 0) {
-            // chain was entirely trusted, skip the validator
-            return wholeChain;
+        // 5. Finally try the cached intermediates to handle server that failed to send them.
+        Set<TrustAnchor> intermediateAnchors =
+                intermediateIndex.findAllByIssuerAndSignature(current);
+        for (TrustAnchor intermediate : sortPotentialAnchors(intermediateAnchors)) {
+            X509Certificate intermediateCert = intermediate.getTrustedCert();
+            // Avoid using certificates that have already been used.
+            if (used.contains(intermediateCert)) {
+                continue;
+            }
+            used.add(intermediateCert);
+            untrustedChain.add(intermediateCert);
+            try {
+                return checkTrustedRecursive(certs, host, clientAuth, untrustedChain,
+                        trustAnchorChain, used);
+            } catch (CertificateException ex) {
+                lastException = ex;
+            }
+            // Could not form a valid chain via this certificate, remove it from this chain.
+            untrustedChain.remove(untrustedChain.size() - 1);
+            used.remove(intermediateCert);
         }
 
-        if (trustAnchor.isEmpty()) {
+        // 6. We were unable to build a valid chain, throw the last error encountered.
+        if (lastException != null) {
+            throw lastException;
+        }
+
+        // 7. If no errors were encountered above then verifyChain was never called because it was
+        // not possible to build a valid chain to a trusted certificate.
+        CertPath certPath = factory.generateCertPath(untrustedChain);
+        throw new CertificateException(new CertPathValidatorException(
+                "Trust anchor for certification path not found.", null, certPath, -1));
+    }
+
+    private List<X509Certificate> verifyChain(List<X509Certificate> untrustedChain,
+            List<TrustAnchor> trustAnchorChain, String host, boolean clientAuth)
+            throws CertificateException {
+        // build the cert path from the list of certs sans trust anchors
+        // TODO: check whether this is slow and should be replaced by a minimalistic CertPath impl
+        // since we already have built the path.
+        CertPath certPath = factory.generateCertPath(untrustedChain);
+
+        // Check that there are at least some trust anchors
+        if (trustAnchorChain.isEmpty()) {
             throw new CertificateException(new CertPathValidatorException(
                     "Trust anchor for certification path not found.", null, certPath, -1));
         }
 
-        // There's no point in checking trust anchors here, and it will throw off the MD5 check,
-        // so we just hand it the chain without anchors
-        ChainStrengthAnalyzer.check(newChain);
-
-        try {
-            PKIXParameters params = new PKIXParameters(trustAnchor);
-            params.setRevocationEnabled(false);
-            params.addCertPathChecker(new ExtendedKeyUsagePKIXCertPathChecker(clientAuth,
-                                                                              newChain[0]));
-            validator.validate(certPath, params);
-            // Add intermediate CAs to the index to tolerate sites
-            // that assume that the browser will have cached these.
-            // The server certificate is skipped by skipping the
-            // zeroth element of new chain and note that the root CA
-            // will have been removed in
-            // cleanupCertChainAndFindTrustAnchors.  http://b/3404902
-            for (int i = 1; i < newChain.length; i++) {
-                intermediateIndex.index(newChain[i]);
-            }
-        } catch (InvalidAlgorithmParameterException e) {
-            throw new CertificateException(e);
-        } catch (CertPathValidatorException e) {
-            throw new CertificateException(e);
+        List<X509Certificate> wholeChain = new ArrayList<X509Certificate>();
+        wholeChain.addAll(untrustedChain);
+        for (TrustAnchor anchor : trustAnchorChain) {
+            wholeChain.add(anchor.getTrustedCert());
         }
 
+        if (host != null) {
+            boolean chainValid = false;
+            try {
+                chainValid = pinManager.isChainValid(host, wholeChain);
+            } catch (PinManagerException e) {
+                throw new CertificateException("Failed to check pinning", e);
+            }
+            if (!chainValid) {
+                throw new CertificateException("Pinning failure", new CertPathValidatorException(
+                        "Certificate path is not properly pinned.", null, certPath, -1));
+            }
+        }
+
+        if (untrustedChain.isEmpty()) {
+            // The chain consists of only trust anchors, skip the validator
+            return wholeChain;
+        }
+
+        ChainStrengthAnalyzer.check(untrustedChain);
+
+        // Validate the untrusted part of the chain
+        try {
+            Set<TrustAnchor> anchorSet = new HashSet<TrustAnchor>();
+            // We know that untrusted chains to the first trust anchor, only add that.
+            anchorSet.add(trustAnchorChain.get(0));
+            PKIXParameters params = new PKIXParameters(anchorSet);
+            params.setRevocationEnabled(false);
+            params.addCertPathChecker(new ExtendedKeyUsagePKIXCertPathChecker(clientAuth,
+                        untrustedChain.get(0)));
+            validator.validate(certPath, params);
+        } catch (InvalidAlgorithmParameterException e) {
+            throw new CertificateException("Chain validation failed", e);
+        } catch (CertPathValidatorException e) {
+            throw new CertificateException("Chain validation failed", e);
+        }
+        // Add intermediate CAs to the index to tolerate sites
+        // that assume that the browser will have cached these.
+        // http://b/3404902
+        for (int i = 1; i < untrustedChain.size(); i++) {
+            intermediateIndex.index(untrustedChain.get(i));
+        }
         return wholeChain;
     }
 
     /**
-     * Clean up the certificate chain, returning a cleaned up chain,
-     * which may be a new array instance if elements were removed.
-     * Theoretically, we shouldn't have to do this, but various web
-     * servers in practice are mis-configured to have out-of-order
-     * certificates, expired self-issued root certificate, or CAs with
-     * unsupported signature algorithms such as
-     * md2WithRSAEncryption. This also handles removing old certs
-     * after bridge CA certs.
+     * Sort potential anchors so that the most preferred for use come first.
+     *
+     * @see CertificatePriorityComparator
      */
-    private X509Certificate[] cleanupCertChainAndFindTrustAnchors(X509Certificate[] chain,
-                                                                  Set<TrustAnchor> trustAnchors) {
-        X509Certificate[] original = chain;
-
-        // 1. Clean the received certificates chain.
-        int currIndex;
-        // Start with the first certificate in the chain, assuming it
-        // is the leaf certificate (server or client cert).
-        for (currIndex = 0; currIndex < chain.length; currIndex++) {
-            // Walk the chain to find a "subject" matching
-            // the "issuer" of the current certificate. In a properly
-            // ordered chain this should be the next cert and be fast.
-            // If not, we reorder things to be as the validator will
-            // expect.
-            boolean foundNext = false;
-            for (int nextIndex = currIndex + 1; nextIndex < chain.length; nextIndex++) {
-                if (chain[currIndex].getIssuerDN().equals(chain[nextIndex].getSubjectDN())) {
-                    foundNext = true;
-                    // Exchange certificates so that 0 through currIndex + 1 are in proper order
-                    if (nextIndex != currIndex + 1) {
-                        // don't mutuate original chain, which may be directly from an SSLSession
-                        if (chain == original) {
-                            chain = original.clone();
-                        }
-                        X509Certificate tempCertificate = chain[nextIndex];
-                        chain[nextIndex] = chain[currIndex + 1];
-                        chain[currIndex + 1] = tempCertificate;
-                    }
-                    break;
-                }
-            }
-            // If we can't find the next in the chain, just give up
-            // and use what we found so far. This drops unrelated
-            // certificates that have nothing to do with the cert
-            // chain.
-            if (!foundNext) {
-                break;
-            }
+    private static Collection<TrustAnchor> sortPotentialAnchors(Set<TrustAnchor> anchors) {
+        if (anchors.size() <= 1) {
+            return anchors;
         }
+        List<TrustAnchor> sortedAnchors = new ArrayList<TrustAnchor>(anchors);
+        Collections.sort(sortedAnchors, TRUST_ANCHOR_COMPARATOR);
+        return sortedAnchors;
+    }
 
-        // 2. Add any missing intermediates to the chain
-        while (true) {
-            TrustAnchor nextIntermediate =
-                    intermediateIndex.findByIssuerAndSignature(chain[currIndex]);
-            if (nextIntermediate == null) {
-                break;
-            }
-            // Append intermediate
-            X509Certificate cert = nextIntermediate.getTrustedCert();
-            // don't mutate original chain, which may be directly from an SSLSession
-            if (chain == original) {
-                chain = original.clone();
-            }
-            // Grow the chain if needed
-            if (currIndex == chain.length - 1) {
-                chain = Arrays.copyOf(chain, chain.length * 2);
-            }
-            chain[currIndex + 1] = cert;
-            currIndex++;
+
+    /**
+     * Comparator for sorting {@link TrustAnchor}s using a {@link CertificateComparator}.
+     */
+    private static class TrustAnchorComparator implements Comparator<TrustAnchor> {
+        private static final CertificatePriorityComparator CERT_COMPARATOR =
+                new CertificatePriorityComparator();
+        @Override
+        public int compare(TrustAnchor lhs, TrustAnchor rhs) {
+            X509Certificate lhsCert = lhs.getTrustedCert();
+            X509Certificate rhsCert = rhs.getTrustedCert();
+            return CERT_COMPARATOR.compare(lhsCert, rhsCert);
         }
-
-        // 3. Find the trust anchor in the chain, if any
-        int anchorIndex;
-        for (anchorIndex = 0; anchorIndex <= currIndex; anchorIndex++) {
-            // If the current cert is a TrustAnchor, we can ignore the rest of the chain.
-            // This avoids including "bridge" CA certs that added for legacy compatibility.
-            TrustAnchor trustAnchor = findTrustAnchorBySubjectAndPublicKey(chain[anchorIndex]);
-            if (trustAnchor != null) {
-                trustAnchors.add(trustAnchor);
-                break;
-            }
-        }
-
-        // 4. If the chain is now shorter, copy to an appropriately sized array.
-        int chainLength = anchorIndex;
-        X509Certificate[] newChain = ((chainLength == chain.length)
-                                      ? chain
-                                      : Arrays.copyOf(chain, chainLength));
-
-        // 5. If we didn't find a trust anchor earlier, look for one now
-        if (trustAnchors.isEmpty()) {
-            TrustAnchor trustAnchor = findTrustAnchorByIssuerAndSignature(newChain[anchorIndex-1]);
-            if (trustAnchor != null) {
-                trustAnchors.add(trustAnchor);
-            }
-        }
-        return newChain;
     }
 
     /**
@@ -632,22 +706,24 @@
         }
     }
 
-    private TrustAnchor findTrustAnchorByIssuerAndSignature(X509Certificate lastCert) {
-        TrustAnchor trustAnchor = trustedCertificateIndex.findByIssuerAndSignature(lastCert);
-        if (trustAnchor != null) {
-            return trustAnchor;
+    /**
+     * Find all possible issuing trust anchors of {@code cert}.
+     */
+    private Set<TrustAnchor> findAllTrustAnchorsByIssuerAndSignature(X509Certificate cert) {
+        Set<TrustAnchor> indexedAnchors =
+                trustedCertificateIndex.findAllByIssuerAndSignature(cert);
+        if (!indexedAnchors.isEmpty() || trustedCertificateStore == null) {
+            return indexedAnchors;
         }
-        if (trustedCertificateStore == null) {
-            return null;
+        Set<X509Certificate> storeAnchors = trustedCertificateStore.findAllIssuers(cert);
+        if (storeAnchors.isEmpty()) {
+            return indexedAnchors;
         }
-        // we have a KeyStore and the issuer of the last cert in
-        // the chain seems to be missing from the
-        // TrustedCertificateIndex, check the KeyStore for a hit
-        X509Certificate issuer = trustedCertificateStore.findIssuer(lastCert);
-        if (issuer != null) {
-            return trustedCertificateIndex.index(issuer);
+        Set<TrustAnchor> result = new HashSet<TrustAnchor>(storeAnchors.size());
+        for (X509Certificate storeCert : storeAnchors) {
+            result.add(trustedCertificateIndex.index(storeCert));
         }
-        return null;
+        return result;
     }
 
     /**
@@ -669,9 +745,13 @@
         // this faster than scanning all key store entries.
         X509Certificate systemCert = trustedCertificateStore.getTrustAnchor(cert);
         if (systemCert != null) {
-            // add new TrustAnchor to params index to avoid
-            // checking filesystem next time around.
-            return trustedCertificateIndex.index(systemCert);
+            // Don't index the system certificate here, that way the only place that adds anchors to
+            // the index are findAllTrustAnchorsByIssuerAndSignature.
+            // This allows findAllTrustAnchorsByIssuerAndSignature to avoid checking the
+            // TrustedCertificateStore if the TrustedCertificateIndex contains any issuers for the
+            // certificate because it will have cached all certificates contained in the
+            // TrustedCertificateStore.
+            return new TrustAnchor(systemCert, null);
         }
         return null;
     }