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