1 /*
   2  * Copyright (c) 2004, 2010, 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 
  26 package sun.security.ssl;
  27 
  28 import java.lang.ref.*;
  29 import java.util.*;
  30 import static java.util.Locale.ENGLISH;
  31 import java.util.concurrent.atomic.AtomicLong;
  32 import java.net.Socket;
  33 
  34 import java.security.*;
  35 import java.security.KeyStore.*;
  36 import java.security.cert.*;
  37 import java.security.cert.Certificate;
  38 
  39 import javax.net.ssl.*;
  40 
  41 import 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  */
  60 final 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 }