1/*
2 * Copyright (c) 2004, 2011, Oracle and/or its affiliates. All rights reserved.
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4 *
5 * This code is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License version 2 only, as
7 * published by the Free Software Foundation.  Oracle designates this
8 * particular file as subject to the "Classpath" exception as provided
9 * by Oracle in the LICENSE file that accompanied this code.
10 *
11 * This code is distributed in the hope that it will be useful, but WITHOUT
12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
14 * version 2 for more details (a copy is included in the LICENSE file that
15 * accompanied this code).
16 *
17 * You should have received a copy of the GNU General Public License version
18 * 2 along with this work; if not, write to the Free Software Foundation,
19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20 *
21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22 * or visit www.oracle.com if you need additional information or have any
23 * questions.
24 */
25
26package sun.security.ssl;
27
28import java.lang.ref.*;
29import java.util.*;
30import static java.util.Locale.ENGLISH;
31import java.util.concurrent.atomic.AtomicLong;
32import java.net.Socket;
33
34import java.security.*;
35import java.security.KeyStore.*;
36import java.security.cert.*;
37import java.security.cert.Certificate;
38
39import javax.net.ssl.*;
40
41import sun.security.provider.certpath.AlgorithmChecker;
42
43/**
44 * The new X509 key manager implementation. The main differences to the
45 * old SunX509 key manager are:
46 *  . it is based around the KeyStore.Builder API. This allows it to use
47 *    other forms of KeyStore protection or password input (e.g. a
48 *    CallbackHandler) or to have keys within one KeyStore protected by
49 *    different keys.
50 *  . it can use multiple KeyStores at the same time.
51 *  . it is explicitly designed to accomodate KeyStores that change over
52 *    the lifetime of the process.
53 *  . it makes an effort to choose the key that matches best, i.e. one that
54 *    is not expired and has the appropriate certificate extensions.
55 *
56 * Note that this code is not explicitly performance optimzied yet.
57 *
58 * @author  Andreas Sterbenz
59 */
60final class X509KeyManagerImpl extends X509ExtendedKeyManager
61        implements X509KeyManager {
62
63    private static final Debug debug = Debug.getInstance("ssl");
64
65    private final static boolean useDebug =
66                            (debug != null) && Debug.isOn("keymanager");
67
68    // for unit testing only, set via privileged reflection
69    private static Date verificationDate;
70
71    // list of the builders
72    private final List<Builder> builders;
73
74    // counter to generate unique ids for the aliases
75    private final AtomicLong uidCounter;
76
77    // cached entries
78    private final Map<String,Reference<PrivateKeyEntry>> entryCacheMap;
79
80    X509KeyManagerImpl(Builder builder) {
81        this(Collections.singletonList(builder));
82    }
83
84    X509KeyManagerImpl(List<Builder> builders) {
85        this.builders = builders;
86        uidCounter = new AtomicLong();
87        entryCacheMap = Collections.synchronizedMap
88                        (new SizedMap<String,Reference<PrivateKeyEntry>>());
89    }
90
91    // LinkedHashMap with a max size of 10
92    // see LinkedHashMap JavaDocs
93    private static class SizedMap<K,V> extends LinkedHashMap<K,V> {
94        @Override protected boolean removeEldestEntry(Map.Entry<K,V> eldest) {
95            return size() > 10;
96        }
97    }
98
99    //
100    // public methods
101    //
102
103    public X509Certificate[] getCertificateChain(String alias) {
104        PrivateKeyEntry entry = getEntry(alias);
105        return entry == null ? null :
106                (X509Certificate[])entry.getCertificateChain();
107    }
108
109    public PrivateKey getPrivateKey(String alias) {
110        PrivateKeyEntry entry = getEntry(alias);
111        return entry == null ? null : entry.getPrivateKey();
112    }
113
114    public String chooseClientAlias(String[] keyTypes, Principal[] issuers,
115            Socket socket) {
116        return chooseAlias(getKeyTypes(keyTypes), issuers, CheckType.CLIENT,
117                        getAlgorithmConstraints(socket));
118    }
119
120    public String chooseEngineClientAlias(String[] keyTypes,
121            Principal[] issuers, SSLEngine engine) {
122        return chooseAlias(getKeyTypes(keyTypes), issuers, CheckType.CLIENT,
123                        getAlgorithmConstraints(engine));
124    }
125
126    public String chooseServerAlias(String keyType,
127            Principal[] issuers, Socket socket) {
128        return chooseAlias(getKeyTypes(keyType), issuers, CheckType.SERVER,
129                        getAlgorithmConstraints(socket));
130    }
131
132    public String chooseEngineServerAlias(String keyType,
133            Principal[] issuers, SSLEngine engine) {
134        return chooseAlias(getKeyTypes(keyType), issuers, CheckType.SERVER,
135                        getAlgorithmConstraints(engine));
136    }
137
138    public String[] getClientAliases(String keyType, Principal[] issuers) {
139        return getAliases(keyType, issuers, CheckType.CLIENT, null);
140    }
141
142    public String[] getServerAliases(String keyType, Principal[] issuers) {
143        return getAliases(keyType, issuers, CheckType.SERVER, null);
144    }
145
146    //
147    // implementation private methods
148    //
149
150    // Gets algorithm constraints of the socket.
151    private AlgorithmConstraints getAlgorithmConstraints(Socket socket) {
152        if (socket != null && socket.isConnected() &&
153                                        socket instanceof SSLSocket) {
154
155            SSLSocket sslSocket = (SSLSocket)socket;
156            SSLSession session = sslSocket.getHandshakeSession();
157
158            if (session != null) {
159                ProtocolVersion protocolVersion =
160                    ProtocolVersion.valueOf(session.getProtocol());
161                if (protocolVersion.v >= ProtocolVersion.TLS12.v) {
162                    String[] peerSupportedSignAlgs = null;
163
164                    if (session instanceof ExtendedSSLSession) {
165                        ExtendedSSLSession extSession =
166                            (ExtendedSSLSession)session;
167                        peerSupportedSignAlgs =
168                            extSession.getPeerSupportedSignatureAlgorithms();
169                    }
170
171                    return new SSLAlgorithmConstraints(
172                        sslSocket, peerSupportedSignAlgs, true);
173                }
174            }
175
176            return new SSLAlgorithmConstraints(sslSocket, true);
177        }
178
179        return new SSLAlgorithmConstraints((SSLSocket)null, true);
180    }
181
182    // Gets algorithm constraints of the engine.
183    private AlgorithmConstraints getAlgorithmConstraints(SSLEngine engine) {
184        if (engine != null) {
185            SSLSession session = engine.getHandshakeSession();
186            if (session != null) {
187                ProtocolVersion protocolVersion =
188                    ProtocolVersion.valueOf(session.getProtocol());
189                if (protocolVersion.v >= ProtocolVersion.TLS12.v) {
190                    String[] peerSupportedSignAlgs = null;
191
192                    if (session instanceof ExtendedSSLSession) {
193                        ExtendedSSLSession extSession =
194                            (ExtendedSSLSession)session;
195                        peerSupportedSignAlgs =
196                            extSession.getPeerSupportedSignatureAlgorithms();
197                    }
198
199                    return new SSLAlgorithmConstraints(
200                        engine, peerSupportedSignAlgs, true);
201                }
202            }
203        }
204
205        return new SSLAlgorithmConstraints(engine, true);
206    }
207
208    // we construct the alias we return to JSSE as seen in the code below
209    // a unique id is included to allow us to reliably cache entries
210    // between the calls to getCertificateChain() and getPrivateKey()
211    // even if tokens are inserted or removed
212    private String makeAlias(EntryStatus entry) {
213        return uidCounter.incrementAndGet() + "." + entry.builderIndex + "."
214                + entry.alias;
215    }
216
217    private PrivateKeyEntry getEntry(String alias) {
218        // if the alias is null, return immediately
219        if (alias == null) {
220            return null;
221        }
222
223        // try to get the entry from cache
224        Reference<PrivateKeyEntry> ref = entryCacheMap.get(alias);
225        PrivateKeyEntry entry = (ref != null) ? ref.get() : null;
226        if (entry != null) {
227            return entry;
228        }
229
230        // parse the alias
231        int firstDot = alias.indexOf('.');
232        int secondDot = alias.indexOf('.', firstDot + 1);
233        if ((firstDot == -1) || (secondDot == firstDot)) {
234            // invalid alias
235            return null;
236        }
237        try {
238            int builderIndex = Integer.parseInt
239                                (alias.substring(firstDot + 1, secondDot));
240            String keyStoreAlias = alias.substring(secondDot + 1);
241            Builder builder = builders.get(builderIndex);
242            KeyStore ks = builder.getKeyStore();
243            Entry newEntry = ks.getEntry
244                    (keyStoreAlias, builder.getProtectionParameter(alias));
245            if (newEntry instanceof PrivateKeyEntry == false) {
246                // unexpected type of entry
247                return null;
248            }
249            entry = (PrivateKeyEntry)newEntry;
250            entryCacheMap.put(alias, new SoftReference(entry));
251            return entry;
252        } catch (Exception e) {
253            // ignore
254            return null;
255        }
256    }
257
258    // Class to help verify that the public key algorithm (and optionally
259    // the signature algorithm) of a certificate matches what we need.
260    private static class KeyType {
261
262        final String keyAlgorithm;
263
264        // In TLS 1.2, the signature algorithm  has been obsoleted by the
265        // supported_signature_algorithms, and the certificate type no longer
266        // restricts the algorithm used to sign the certificate.
267        // However, because we don't support certificate type checking other
268        // than rsa_sign, dss_sign and ecdsa_sign, we don't have to check the
269        // protocol version here.
270        final String sigKeyAlgorithm;
271
272        KeyType(String algorithm) {
273            int k = algorithm.indexOf("_");
274            if (k == -1) {
275                keyAlgorithm = algorithm;
276                sigKeyAlgorithm = null;
277            } else {
278                keyAlgorithm = algorithm.substring(0, k);
279                sigKeyAlgorithm = algorithm.substring(k + 1);
280            }
281        }
282
283        boolean matches(Certificate[] chain) {
284            if (!chain[0].getPublicKey().getAlgorithm().equals(keyAlgorithm)) {
285                return false;
286            }
287            if (sigKeyAlgorithm == null) {
288                return true;
289            }
290            if (chain.length > 1) {
291                // if possible, check the public key in the issuer cert
292                return sigKeyAlgorithm.equals(
293                        chain[1].getPublicKey().getAlgorithm());
294            } else {
295                // Check the signature algorithm of the certificate itself.
296                // Look for the "withRSA" in "SHA1withRSA", etc.
297                X509Certificate issuer = (X509Certificate)chain[0];
298                String sigAlgName = issuer.getSigAlgName().toUpperCase(ENGLISH);
299                String pattern = "WITH" + sigKeyAlgorithm.toUpperCase(ENGLISH);
300                return sigAlgName.contains(pattern);
301            }
302        }
303    }
304
305    private static List<KeyType> getKeyTypes(String ... keyTypes) {
306        if ((keyTypes == null) ||
307                (keyTypes.length == 0) || (keyTypes[0] == null)) {
308            return null;
309        }
310        List<KeyType> list = new ArrayList<>(keyTypes.length);
311        for (String keyType : keyTypes) {
312            list.add(new KeyType(keyType));
313        }
314        return list;
315    }
316
317    /*
318     * Return the best alias that fits the given parameters.
319     * The algorithm we use is:
320     *   . scan through all the aliases in all builders in order
321     *   . as soon as we find a perfect match, return
322     *     (i.e. a match with a cert that has appropriate key usage
323     *      and is not expired).
324     *   . if we do not find a perfect match, keep looping and remember
325     *     the imperfect matches
326     *   . at the end, sort the imperfect matches. we prefer expired certs
327     *     with appropriate key usage to certs with the wrong key usage.
328     *     return the first one of them.
329     */
330    private String chooseAlias(List<KeyType> keyTypeList, Principal[] issuers,
331            CheckType checkType, AlgorithmConstraints constraints) {
332        if (keyTypeList == null || keyTypeList.size() == 0) {
333            return null;
334        }
335
336        Set<Principal> issuerSet = getIssuerSet(issuers);
337        List<EntryStatus> allResults = null;
338        for (int i = 0, n = builders.size(); i < n; i++) {
339            try {
340                List<EntryStatus> results = getAliases(i, keyTypeList,
341                                    issuerSet, false, checkType, constraints);
342                if (results != null) {
343                    // the results will either be a single perfect match
344                    // or 1 or more imperfect matches
345                    // if it's a perfect match, return immediately
346                    EntryStatus status = results.get(0);
347                    if (status.checkResult == CheckResult.OK) {
348                        if (useDebug) {
349                            debug.println("KeyMgr: choosing key: " + status);
350                        }
351                        return makeAlias(status);
352                    }
353                    if (allResults == null) {
354                        allResults = new ArrayList<EntryStatus>();
355                    }
356                    allResults.addAll(results);
357                }
358            } catch (Exception e) {
359                // ignore
360            }
361        }
362        if (allResults == null) {
363            if (useDebug) {
364                debug.println("KeyMgr: no matching key found");
365            }
366            return null;
367        }
368        Collections.sort(allResults);
369        if (useDebug) {
370            debug.println("KeyMgr: no good matching key found, "
371                        + "returning best match out of:");
372            debug.println(allResults.toString());
373        }
374        return makeAlias(allResults.get(0));
375    }
376
377    /*
378     * Return all aliases that (approximately) fit the parameters.
379     * These are perfect matches plus imperfect matches (expired certificates
380     * and certificates with the wrong extensions).
381     * The perfect matches will be first in the array.
382     */
383    public String[] getAliases(String keyType, Principal[] issuers,
384            CheckType checkType, AlgorithmConstraints constraints) {
385        if (keyType == null) {
386            return null;
387        }
388
389        Set<Principal> issuerSet = getIssuerSet(issuers);
390        List<KeyType> keyTypeList = getKeyTypes(keyType);
391        List<EntryStatus> allResults = null;
392        for (int i = 0, n = builders.size(); i < n; i++) {
393            try {
394                List<EntryStatus> results = getAliases(i, keyTypeList,
395                                    issuerSet, true, checkType, constraints);
396                if (results != null) {
397                    if (allResults == null) {
398                        allResults = new ArrayList<EntryStatus>();
399                    }
400                    allResults.addAll(results);
401                }
402            } catch (Exception e) {
403                // ignore
404            }
405        }
406        if (allResults == null || allResults.size() == 0) {
407            if (useDebug) {
408                debug.println("KeyMgr: no matching alias found");
409            }
410            return null;
411        }
412        Collections.sort(allResults);
413        if (useDebug) {
414            debug.println("KeyMgr: getting aliases: " + allResults);
415        }
416        return toAliases(allResults);
417    }
418
419    // turn candidate entries into unique aliases we can return to JSSE
420    private String[] toAliases(List<EntryStatus> results) {
421        String[] s = new String[results.size()];
422        int i = 0;
423        for (EntryStatus result : results) {
424            s[i++] = makeAlias(result);
425        }
426        return s;
427    }
428
429    // make a Set out of the array
430    private Set<Principal> getIssuerSet(Principal[] issuers) {
431        if ((issuers != null) && (issuers.length != 0)) {
432            return new HashSet<>(Arrays.asList(issuers));
433        } else {
434            return null;
435        }
436    }
437
438    // a candidate match
439    // identifies the entry by builder and alias
440    // and includes the result of the certificate check
441    private static class EntryStatus implements Comparable<EntryStatus> {
442
443        final int builderIndex;
444        final int keyIndex;
445        final String alias;
446        final CheckResult checkResult;
447
448        EntryStatus(int builderIndex, int keyIndex, String alias,
449                Certificate[] chain, CheckResult checkResult) {
450            this.builderIndex = builderIndex;
451            this.keyIndex = keyIndex;
452            this.alias = alias;
453            this.checkResult = checkResult;
454        }
455
456        public int compareTo(EntryStatus other) {
457            int result = this.checkResult.compareTo(other.checkResult);
458            return (result == 0) ? (this.keyIndex - other.keyIndex) : result;
459        }
460
461        public String toString() {
462            String s = alias + " (verified: " + checkResult + ")";
463            if (builderIndex == 0) {
464                return s;
465            } else {
466                return "Builder #" + builderIndex + ", alias: " + s;
467            }
468        }
469    }
470
471    // enum for the type of certificate check we want to perform
472    // (client or server)
473    // also includes the check code itself
474    private static enum CheckType {
475
476        // enum constant for "no check" (currently not used)
477        NONE(Collections.<String>emptySet()),
478
479        // enum constant for "tls client" check
480        // valid EKU for TLS client: any, tls_client
481        CLIENT(new HashSet<String>(Arrays.asList(new String[] {
482            "2.5.29.37.0", "1.3.6.1.5.5.7.3.2" }))),
483
484        // enum constant for "tls server" check
485        // valid EKU for TLS server: any, tls_server, ns_sgc, ms_sgc
486        SERVER(new HashSet<String>(Arrays.asList(new String[] {
487            "2.5.29.37.0", "1.3.6.1.5.5.7.3.1", "2.16.840.1.113730.4.1",
488            "1.3.6.1.4.1.311.10.3.3" })));
489
490        // set of valid EKU values for this type
491        final Set<String> validEku;
492
493        CheckType(Set<String> validEku) {
494            this.validEku = validEku;
495        }
496
497        private static boolean getBit(boolean[] keyUsage, int bit) {
498            return (bit < keyUsage.length) && keyUsage[bit];
499        }
500
501        // check if this certificate is appropriate for this type of use
502        // first check extensions, if they match, check expiration
503        // note: we may want to move this code into the sun.security.validator
504        // package
505        CheckResult check(X509Certificate cert, Date date) {
506            if (this == NONE) {
507                return CheckResult.OK;
508            }
509
510            // check extensions
511            try {
512                // check extended key usage
513                List<String> certEku = cert.getExtendedKeyUsage();
514                if ((certEku != null) &&
515                        Collections.disjoint(validEku, certEku)) {
516                    // if extension present and it does not contain any of
517                    // the valid EKU OIDs, return extension_mismatch
518                    return CheckResult.EXTENSION_MISMATCH;
519                }
520
521                // check key usage
522                boolean[] ku = cert.getKeyUsage();
523                if (ku != null) {
524                    String algorithm = cert.getPublicKey().getAlgorithm();
525                    boolean kuSignature = getBit(ku, 0);
526                    if (algorithm.equals("RSA")) {
527                        // require either signature bit
528                        // or if server also allow key encipherment bit
529                        if (kuSignature == false) {
530                            if ((this == CLIENT) || (getBit(ku, 2) == false)) {
531                                return CheckResult.EXTENSION_MISMATCH;
532                            }
533                        }
534                    } else if (algorithm.equals("DSA")) {
535                        // require signature bit
536                        if (kuSignature == false) {
537                            return CheckResult.EXTENSION_MISMATCH;
538                        }
539                    } else if (algorithm.equals("DH")) {
540                        // require keyagreement bit
541                        if (getBit(ku, 4) == false) {
542                            return CheckResult.EXTENSION_MISMATCH;
543                        }
544                    } else if (algorithm.equals("EC")) {
545                        // require signature bit
546                        if (kuSignature == false) {
547                            return CheckResult.EXTENSION_MISMATCH;
548                        }
549                        // For servers, also require key agreement.
550                        // This is not totally accurate as the keyAgreement bit
551                        // is only necessary for static ECDH key exchange and
552                        // not ephemeral ECDH. We leave it in for now until
553                        // there are signs that this check causes problems
554                        // for real world EC certificates.
555                        if ((this == SERVER) && (getBit(ku, 4) == false)) {
556                            return CheckResult.EXTENSION_MISMATCH;
557                        }
558                    }
559                }
560            } catch (CertificateException e) {
561                // extensions unparseable, return failure
562                return CheckResult.EXTENSION_MISMATCH;
563            }
564
565            try {
566                cert.checkValidity(date);
567                return CheckResult.OK;
568            } catch (CertificateException e) {
569                return CheckResult.EXPIRED;
570            }
571        }
572    }
573
574    // enum for the result of the extension check
575    // NOTE: the order of the constants is important as they are used
576    // for sorting, i.e. OK is best, followed by EXPIRED and EXTENSION_MISMATCH
577    private static enum CheckResult {
578        OK,                     // ok or not checked
579        EXPIRED,                // extensions valid but cert expired
580        EXTENSION_MISMATCH,     // extensions invalid (expiration not checked)
581    }
582
583    /*
584     * Return a List of all candidate matches in the specified builder
585     * that fit the parameters.
586     * We exclude entries in the KeyStore if they are not:
587     *  . private key entries
588     *  . the certificates are not X509 certificates
589     *  . the algorithm of the key in the EE cert doesn't match one of keyTypes
590     *  . none of the certs is issued by a Principal in issuerSet
591     * Using those entries would not be possible or they would almost
592     * certainly be rejected by the peer.
593     *
594     * In addition to those checks, we also check the extensions in the EE
595     * cert and its expiration. Even if there is a mismatch, we include
596     * such certificates because they technically work and might be accepted
597     * by the peer. This leads to more graceful failure and better error
598     * messages if the cert expires from one day to the next.
599     *
600     * The return values are:
601     *   . null, if there are no matching entries at all
602     *   . if 'findAll' is 'false' and there is a perfect match, a List
603     *     with a single element (early return)
604     *   . if 'findAll' is 'false' and there is NO perfect match, a List
605     *     with all the imperfect matches (expired, wrong extensions)
606     *   . if 'findAll' is 'true', a List with all perfect and imperfect
607     *     matches
608     */
609    private List<EntryStatus> getAliases(int builderIndex,
610            List<KeyType> keyTypes, Set<Principal> issuerSet,
611            boolean findAll, CheckType checkType,
612            AlgorithmConstraints constraints) throws Exception {
613        Builder builder = builders.get(builderIndex);
614        KeyStore ks = builder.getKeyStore();
615        List<EntryStatus> results = null;
616        Date date = verificationDate;
617        boolean preferred = false;
618        for (Enumeration<String> e = ks.aliases(); e.hasMoreElements(); ) {
619            String alias = e.nextElement();
620            // check if it is a key entry (private key or secret key)
621            if (ks.isKeyEntry(alias) == false) {
622                continue;
623            }
624
625            Certificate[] chain = ks.getCertificateChain(alias);
626            if ((chain == null) || (chain.length == 0)) {
627                // must be secret key entry, ignore
628                continue;
629            }
630
631            boolean incompatible = false;
632            for (Certificate cert : chain) {
633                if (cert instanceof X509Certificate == false) {
634                    // not an X509Certificate, ignore this alias
635                    incompatible = true;
636                    break;
637                }
638            }
639            if (incompatible) {
640                continue;
641            }
642
643            // check keytype
644            int keyIndex = -1;
645            int j = 0;
646            for (KeyType keyType : keyTypes) {
647                if (keyType.matches(chain)) {
648                    keyIndex = j;
649                    break;
650                }
651                j++;
652            }
653            if (keyIndex == -1) {
654                if (useDebug) {
655                    debug.println("Ignoring alias " + alias
656                                + ": key algorithm does not match");
657                }
658                continue;
659            }
660            // check issuers
661            if (issuerSet != null) {
662                boolean found = false;
663                for (Certificate cert : chain) {
664                    X509Certificate xcert = (X509Certificate)cert;
665                    if (issuerSet.contains(xcert.getIssuerX500Principal())) {
666                        found = true;
667                        break;
668                    }
669                }
670                if (found == false) {
671                    if (useDebug) {
672                        debug.println("Ignoring alias " + alias
673                                    + ": issuers do not match");
674                    }
675                    continue;
676                }
677            }
678
679            // check the algorithm constraints
680            if (constraints != null &&
681                    !conformsToAlgorithmConstraints(constraints, chain)) {
682
683                if (useDebug) {
684                    debug.println("Ignoring alias " + alias +
685                            ": certificate list does not conform to " +
686                            "algorithm constraints");
687                }
688                continue;
689            }
690
691            if (date == null) {
692                date = new Date();
693            }
694            CheckResult checkResult =
695                    checkType.check((X509Certificate)chain[0], date);
696            EntryStatus status =
697                    new EntryStatus(builderIndex, keyIndex,
698                                        alias, chain, checkResult);
699            if (!preferred && checkResult == CheckResult.OK && keyIndex == 0) {
700                preferred = true;
701            }
702            if (preferred && (findAll == false)) {
703                // if we have a good match and do not need all matches,
704                // return immediately
705                return Collections.singletonList(status);
706            } else {
707                if (results == null) {
708                    results = new ArrayList<EntryStatus>();
709                }
710                results.add(status);
711            }
712        }
713        return results;
714    }
715
716    private static boolean conformsToAlgorithmConstraints(
717            AlgorithmConstraints constraints, Certificate[] chain) {
718
719        AlgorithmChecker checker = new AlgorithmChecker(constraints);
720        try {
721            checker.init(false);
722        } catch (CertPathValidatorException cpve) {
723            // unlikely to happen
724            return false;
725        }
726
727        // It is a forward checker, so we need to check from trust to target.
728        for (int i = chain.length - 1; i >= 0; i--) {
729            Certificate cert = chain[i];
730            try {
731                // We don't care about the unresolved critical extensions.
732                checker.check(cert, Collections.<String>emptySet());
733            } catch (CertPathValidatorException cpve) {
734                return false;
735            }
736        }
737
738        return true;
739    }
740
741}
742