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 }