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