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