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 
  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 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  */
  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         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 }