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