1 /* 2 * Copyright (c) 2012, 2016, 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.provider.certpath; 27 28 import java.io.IOException; 29 import java.math.BigInteger; 30 import java.net.URI; 31 import java.net.URISyntaxException; 32 import java.security.AccessController; 33 import java.security.InvalidAlgorithmParameterException; 34 import java.security.NoSuchAlgorithmException; 35 import java.security.PrivilegedAction; 36 import java.security.PublicKey; 37 import java.security.Security; 38 import java.security.cert.CertPathValidatorException.BasicReason; 39 import java.security.cert.Extension; 40 import java.security.cert.*; 41 import java.util.*; 42 import javax.security.auth.x500.X500Principal; 43 44 import static sun.security.provider.certpath.OCSP.*; 45 import static sun.security.provider.certpath.PKIX.*; 46 import sun.security.x509.*; 47 import static sun.security.x509.PKIXExtensions.*; 48 import sun.security.util.Debug; 49 50 class RevocationChecker extends PKIXRevocationChecker { 51 52 private static final Debug debug = Debug.getInstance("certpath"); 53 54 private TrustAnchor anchor; 55 private ValidatorParams params; 56 private boolean onlyEE; 57 private boolean softFail; 58 private boolean crlDP; 59 private URI responderURI; 60 private X509Certificate responderCert; 61 private List<CertStore> certStores; 62 private Map<X509Certificate, byte[]> ocspResponses; 63 private List<Extension> ocspExtensions; 64 private final boolean legacy; 65 private LinkedList<CertPathValidatorException> softFailExceptions = 66 new LinkedList<>(); 67 68 // state variables 69 private OCSPResponse.IssuerInfo issuerInfo; 70 private PublicKey prevPubKey; 71 private boolean crlSignFlag; 72 private int certIndex; 73 74 private enum Mode { PREFER_OCSP, PREFER_CRLS, ONLY_CRLS, ONLY_OCSP }; 75 private Mode mode = Mode.PREFER_OCSP; 76 77 private static class RevocationProperties { 78 boolean onlyEE; 79 boolean ocspEnabled; 80 boolean crlDPEnabled; 81 String ocspUrl; 82 String ocspSubject; 83 String ocspIssuer; 84 String ocspSerial; 85 } 86 87 RevocationChecker() { 88 legacy = false; 89 } 90 91 RevocationChecker(TrustAnchor anchor, ValidatorParams params) 92 throws CertPathValidatorException 93 { 94 legacy = true; 95 init(anchor, params); 96 } 97 98 void init(TrustAnchor anchor, ValidatorParams params) 99 throws CertPathValidatorException 100 { 101 RevocationProperties rp = getRevocationProperties(); 102 URI uri = getOcspResponder(); 103 responderURI = (uri == null) ? toURI(rp.ocspUrl) : uri; 104 X509Certificate cert = getOcspResponderCert(); 105 responderCert = (cert == null) 106 ? getResponderCert(rp, params.trustAnchors(), 107 params.certStores()) 108 : cert; 109 Set<Option> options = getOptions(); 110 for (Option option : options) { 111 switch (option) { 112 case ONLY_END_ENTITY: 113 case PREFER_CRLS: 114 case SOFT_FAIL: 115 case NO_FALLBACK: 116 break; 117 default: 118 throw new CertPathValidatorException( 119 "Unrecognized revocation parameter option: " + option); 120 } 121 } 122 softFail = options.contains(Option.SOFT_FAIL); 123 124 // set mode, only end entity flag 125 if (legacy) { 126 mode = (rp.ocspEnabled) ? Mode.PREFER_OCSP : Mode.ONLY_CRLS; 127 onlyEE = rp.onlyEE; 128 } else { 129 if (options.contains(Option.NO_FALLBACK)) { 130 if (options.contains(Option.PREFER_CRLS)) { 131 mode = Mode.ONLY_CRLS; 132 } else { 133 mode = Mode.ONLY_OCSP; 134 } 135 } else if (options.contains(Option.PREFER_CRLS)) { 136 mode = Mode.PREFER_CRLS; 137 } 138 onlyEE = options.contains(Option.ONLY_END_ENTITY); 139 } 140 if (legacy) { 141 crlDP = rp.crlDPEnabled; 142 } else { 143 crlDP = true; 144 } 145 ocspResponses = getOcspResponses(); 146 ocspExtensions = getOcspExtensions(); 147 148 this.anchor = anchor; 149 this.params = params; 150 this.certStores = new ArrayList<>(params.certStores()); 151 try { 152 this.certStores.add(CertStore.getInstance("Collection", 153 new CollectionCertStoreParameters(params.certificates()))); 154 } catch (InvalidAlgorithmParameterException | 155 NoSuchAlgorithmException e) { 156 // should never occur but not necessarily fatal, so log it, 157 // ignore and continue 158 if (debug != null) { 159 debug.println("RevocationChecker: " + 160 "error creating Collection CertStore: " + e); 161 } 162 } 163 } 164 165 private static URI toURI(String uriString) 166 throws CertPathValidatorException 167 { 168 try { 169 if (uriString != null) { 170 return new URI(uriString); 171 } 172 return null; 173 } catch (URISyntaxException e) { 174 throw new CertPathValidatorException( 175 "cannot parse ocsp.responderURL property", e); 176 } 177 } 178 179 private static RevocationProperties getRevocationProperties() { 180 return AccessController.doPrivileged( 181 new PrivilegedAction<RevocationProperties>() { 182 public RevocationProperties run() { 183 RevocationProperties rp = new RevocationProperties(); 184 String onlyEE = Security.getProperty( 185 "com.sun.security.onlyCheckRevocationOfEECert"); 186 rp.onlyEE = onlyEE != null 187 && onlyEE.equalsIgnoreCase("true"); 188 String ocspEnabled = Security.getProperty("ocsp.enable"); 189 rp.ocspEnabled = ocspEnabled != null 190 && ocspEnabled.equalsIgnoreCase("true"); 191 rp.ocspUrl = Security.getProperty("ocsp.responderURL"); 192 rp.ocspSubject 193 = Security.getProperty("ocsp.responderCertSubjectName"); 194 rp.ocspIssuer 195 = Security.getProperty("ocsp.responderCertIssuerName"); 196 rp.ocspSerial 197 = Security.getProperty("ocsp.responderCertSerialNumber"); 198 rp.crlDPEnabled 199 = Boolean.getBoolean("com.sun.security.enableCRLDP"); 200 return rp; 201 } 202 } 203 ); 204 } 205 206 private static X509Certificate getResponderCert(RevocationProperties rp, 207 Set<TrustAnchor> anchors, 208 List<CertStore> stores) 209 throws CertPathValidatorException 210 { 211 if (rp.ocspSubject != null) { 212 return getResponderCert(rp.ocspSubject, anchors, stores); 213 } else if (rp.ocspIssuer != null && rp.ocspSerial != null) { 214 return getResponderCert(rp.ocspIssuer, rp.ocspSerial, 215 anchors, stores); 216 } else if (rp.ocspIssuer != null || rp.ocspSerial != null) { 217 throw new CertPathValidatorException( 218 "Must specify both ocsp.responderCertIssuerName and " + 219 "ocsp.responderCertSerialNumber properties"); 220 } 221 return null; 222 } 223 224 private static X509Certificate getResponderCert(String subject, 225 Set<TrustAnchor> anchors, 226 List<CertStore> stores) 227 throws CertPathValidatorException 228 { 229 X509CertSelector sel = new X509CertSelector(); 230 try { 231 sel.setSubject(new X500Principal(subject)); 232 } catch (IllegalArgumentException e) { 233 throw new CertPathValidatorException( 234 "cannot parse ocsp.responderCertSubjectName property", e); 235 } 236 return getResponderCert(sel, anchors, stores); 237 } 238 239 private static X509Certificate getResponderCert(String issuer, 240 String serial, 241 Set<TrustAnchor> anchors, 242 List<CertStore> stores) 243 throws CertPathValidatorException 244 { 245 X509CertSelector sel = new X509CertSelector(); 246 try { 247 sel.setIssuer(new X500Principal(issuer)); 248 } catch (IllegalArgumentException e) { 249 throw new CertPathValidatorException( 250 "cannot parse ocsp.responderCertIssuerName property", e); 251 } 252 try { 253 sel.setSerialNumber(new BigInteger(stripOutSeparators(serial), 16)); 254 } catch (NumberFormatException e) { 255 throw new CertPathValidatorException( 256 "cannot parse ocsp.responderCertSerialNumber property", e); 257 } 258 return getResponderCert(sel, anchors, stores); 259 } 260 261 private static X509Certificate getResponderCert(X509CertSelector sel, 262 Set<TrustAnchor> anchors, 263 List<CertStore> stores) 264 throws CertPathValidatorException 265 { 266 // first check TrustAnchors 267 for (TrustAnchor anchor : anchors) { 268 X509Certificate cert = anchor.getTrustedCert(); 269 if (cert == null) { 270 continue; 271 } 272 if (sel.match(cert)) { 273 return cert; 274 } 275 } 276 // now check CertStores 277 for (CertStore store : stores) { 278 try { 279 Collection<? extends Certificate> certs = 280 store.getCertificates(sel); 281 if (!certs.isEmpty()) { 282 return (X509Certificate)certs.iterator().next(); 283 } 284 } catch (CertStoreException e) { 285 // ignore and try next CertStore 286 if (debug != null) { 287 debug.println("CertStore exception:" + e); 288 } 289 continue; 290 } 291 } 292 throw new CertPathValidatorException( 293 "Cannot find the responder's certificate " + 294 "(set using the OCSP security properties)."); 295 } 296 297 @Override 298 public void init(boolean forward) throws CertPathValidatorException { 299 if (forward) { 300 throw new 301 CertPathValidatorException("forward checking not supported"); 302 } 303 if (anchor != null) { 304 issuerInfo = new OCSPResponse.IssuerInfo(anchor); 305 prevPubKey = issuerInfo.getPublicKey(); 306 307 } 308 crlSignFlag = true; 309 if (params != null && params.certPath() != null) { 310 certIndex = params.certPath().getCertificates().size() - 1; 311 } else { 312 certIndex = -1; 313 } 314 softFailExceptions.clear(); 315 } 316 317 @Override 318 public boolean isForwardCheckingSupported() { 319 return false; 320 } 321 322 @Override 323 public Set<String> getSupportedExtensions() { 324 return null; 325 } 326 327 @Override 328 public List<CertPathValidatorException> getSoftFailExceptions() { 329 return Collections.unmodifiableList(softFailExceptions); 330 } 331 332 @Override 333 public void check(Certificate cert, Collection<String> unresolvedCritExts) 334 throws CertPathValidatorException 335 { 336 check((X509Certificate)cert, unresolvedCritExts, 337 prevPubKey, crlSignFlag); 338 } 339 340 private void check(X509Certificate xcert, 341 Collection<String> unresolvedCritExts, 342 PublicKey pubKey, boolean crlSignFlag) 343 throws CertPathValidatorException 344 { 345 if (debug != null) { 346 debug.println("RevocationChecker.check: checking cert" + 347 "\n SN: " + Debug.toHexString(xcert.getSerialNumber()) + 348 "\n Subject: " + xcert.getSubjectX500Principal() + 349 "\n Issuer: " + xcert.getIssuerX500Principal()); 350 } 351 try { 352 if (onlyEE && xcert.getBasicConstraints() != -1) { 353 if (debug != null) { 354 debug.println("Skipping revocation check; cert is not " + 355 "an end entity cert"); 356 } 357 return; 358 } 359 switch (mode) { 360 case PREFER_OCSP: 361 case ONLY_OCSP: 362 checkOCSP(xcert, unresolvedCritExts); 363 break; 364 case PREFER_CRLS: 365 case ONLY_CRLS: 366 checkCRLs(xcert, unresolvedCritExts, null, 367 pubKey, crlSignFlag); 368 break; 369 } 370 } catch (CertPathValidatorException e) { 371 if (e.getReason() == BasicReason.REVOKED) { 372 throw e; 373 } 374 boolean eSoftFail = isSoftFailException(e); 375 if (eSoftFail) { 376 if (mode == Mode.ONLY_OCSP || mode == Mode.ONLY_CRLS) { 377 return; 378 } 379 } else { 380 if (mode == Mode.ONLY_OCSP || mode == Mode.ONLY_CRLS) { 381 throw e; 382 } 383 } 384 CertPathValidatorException cause = e; 385 // Otherwise, failover 386 if (debug != null) { 387 debug.println("RevocationChecker.check() " + e.getMessage()); 388 debug.println("RevocationChecker.check() preparing to failover"); 389 } 390 try { 391 switch (mode) { 392 case PREFER_OCSP: 393 checkCRLs(xcert, unresolvedCritExts, null, 394 pubKey, crlSignFlag); 395 break; 396 case PREFER_CRLS: 397 checkOCSP(xcert, unresolvedCritExts); 398 break; 399 } 400 } catch (CertPathValidatorException x) { 401 if (debug != null) { 402 debug.println("RevocationChecker.check() failover failed"); 403 debug.println("RevocationChecker.check() " + x.getMessage()); 404 } 405 if (x.getReason() == BasicReason.REVOKED) { 406 throw x; 407 } 408 if (!isSoftFailException(x)) { 409 cause.addSuppressed(x); 410 throw cause; 411 } else { 412 // only pass if both exceptions were soft failures 413 if (!eSoftFail) { 414 throw cause; 415 } 416 } 417 } 418 } finally { 419 updateState(xcert); 420 } 421 } 422 423 private boolean isSoftFailException(CertPathValidatorException e) { 424 if (softFail && 425 e.getReason() == BasicReason.UNDETERMINED_REVOCATION_STATUS) 426 { 427 // recreate exception with correct index 428 CertPathValidatorException e2 = new CertPathValidatorException( 429 e.getMessage(), e.getCause(), params.certPath(), certIndex, 430 e.getReason()); 431 softFailExceptions.addFirst(e2); 432 return true; 433 } 434 return false; 435 } 436 437 private void updateState(X509Certificate cert) 438 throws CertPathValidatorException 439 { 440 issuerInfo = new OCSPResponse.IssuerInfo(cert); 441 442 // Make new public key if parameters are missing 443 PublicKey pubKey = cert.getPublicKey(); 444 if (PKIX.isDSAPublicKeyWithoutParams(pubKey)) { 445 // pubKey needs to inherit DSA parameters from prev key 446 pubKey = BasicChecker.makeInheritedParamsKey(pubKey, prevPubKey); 447 } 448 prevPubKey = pubKey; 449 crlSignFlag = certCanSignCrl(cert); 450 if (certIndex > 0) { 451 certIndex--; 452 } 453 } 454 455 // Maximum clock skew in milliseconds (15 minutes) allowed when checking 456 // validity of CRLs 457 private static final long MAX_CLOCK_SKEW = 900000; 458 private void checkCRLs(X509Certificate cert, 459 Collection<String> unresolvedCritExts, 460 Set<X509Certificate> stackedCerts, 461 PublicKey pubKey, boolean signFlag) 462 throws CertPathValidatorException 463 { 464 checkCRLs(cert, pubKey, null, signFlag, true, 465 stackedCerts, params.trustAnchors()); 466 } 467 468 static boolean isCausedByNetworkIssue(String type, CertStoreException cse) { 469 boolean result; 470 Throwable t = cse.getCause(); 471 472 switch (type) { 473 case "LDAP": 474 if (t != null) { 475 // These two exception classes are inside java.naming module 476 String cn = t.getClass().getName(); 477 result = (cn.equals("javax.naming.ServiceUnavailableException") || 478 cn.equals("javax.naming.CommunicationException")); 479 } else { 480 result = false; 481 } 482 break; 483 case "SSLServer": 484 result = (t != null && t instanceof IOException); 485 break; 486 case "URI": 487 result = (t != null && t instanceof IOException); 488 break; 489 default: 490 // we don't know about any other remote CertStore types 491 return false; 492 } 493 return result; 494 } 495 496 private void checkCRLs(X509Certificate cert, PublicKey prevKey, 497 X509Certificate prevCert, boolean signFlag, 498 boolean allowSeparateKey, 499 Set<X509Certificate> stackedCerts, 500 Set<TrustAnchor> anchors) 501 throws CertPathValidatorException 502 { 503 if (debug != null) { 504 debug.println("RevocationChecker.checkCRLs()" + 505 " ---checking revocation status ..."); 506 } 507 508 // Reject circular dependencies - RFC 5280 is not explicit on how 509 // to handle this, but does suggest that they can be a security 510 // risk and can create unresolvable dependencies 511 if (stackedCerts != null && stackedCerts.contains(cert)) { 512 if (debug != null) { 513 debug.println("RevocationChecker.checkCRLs()" + 514 " circular dependency"); 515 } 516 throw new CertPathValidatorException 517 ("Could not determine revocation status", null, null, -1, 518 BasicReason.UNDETERMINED_REVOCATION_STATUS); 519 } 520 521 Set<X509CRL> possibleCRLs = new HashSet<>(); 522 Set<X509CRL> approvedCRLs = new HashSet<>(); 523 X509CRLSelector sel = new X509CRLSelector(); 524 sel.setCertificateChecking(cert); 525 CertPathHelper.setDateAndTime(sel, params.date(), MAX_CLOCK_SKEW); 526 527 // First, check user-specified CertStores 528 CertPathValidatorException networkFailureException = null; 529 for (CertStore store : certStores) { 530 try { 531 for (CRL crl : store.getCRLs(sel)) { 532 possibleCRLs.add((X509CRL)crl); 533 } 534 } catch (CertStoreException e) { 535 if (debug != null) { 536 debug.println("RevocationChecker.checkCRLs() " + 537 "CertStoreException: " + e.getMessage()); 538 } 539 if (networkFailureException == null && 540 isCausedByNetworkIssue(store.getType(),e)) { 541 // save this exception, we may need to throw it later 542 networkFailureException = new CertPathValidatorException( 543 "Unable to determine revocation status due to " + 544 "network error", e, null, -1, 545 BasicReason.UNDETERMINED_REVOCATION_STATUS); 546 } 547 } 548 } 549 550 if (debug != null) { 551 debug.println("RevocationChecker.checkCRLs() " + 552 "possible crls.size() = " + possibleCRLs.size()); 553 } 554 boolean[] reasonsMask = new boolean[9]; 555 if (!possibleCRLs.isEmpty()) { 556 // Now that we have a list of possible CRLs, see which ones can 557 // be approved 558 approvedCRLs.addAll(verifyPossibleCRLs(possibleCRLs, cert, prevKey, 559 signFlag, reasonsMask, 560 anchors)); 561 } 562 563 if (debug != null) { 564 debug.println("RevocationChecker.checkCRLs() " + 565 "approved crls.size() = " + approvedCRLs.size()); 566 } 567 568 // make sure that we have at least one CRL that _could_ cover 569 // the certificate in question and all reasons are covered 570 if (!approvedCRLs.isEmpty() && 571 Arrays.equals(reasonsMask, ALL_REASONS)) 572 { 573 checkApprovedCRLs(cert, approvedCRLs); 574 } else { 575 // Check Distribution Points 576 // all CRLs returned by the DP Fetcher have also been verified 577 try { 578 if (crlDP) { 579 approvedCRLs.addAll(DistributionPointFetcher.getCRLs( 580 sel, signFlag, prevKey, prevCert, 581 params.sigProvider(), certStores, 582 reasonsMask, anchors, null)); 583 } 584 } catch (CertStoreException e) { 585 if (e instanceof CertStoreTypeException) { 586 CertStoreTypeException cste = (CertStoreTypeException)e; 587 if (isCausedByNetworkIssue(cste.getType(), e)) { 588 throw new CertPathValidatorException( 589 "Unable to determine revocation status due to " + 590 "network error", e, null, -1, 591 BasicReason.UNDETERMINED_REVOCATION_STATUS); 592 } 593 } 594 throw new CertPathValidatorException(e); 595 } 596 if (!approvedCRLs.isEmpty() && 597 Arrays.equals(reasonsMask, ALL_REASONS)) 598 { 599 checkApprovedCRLs(cert, approvedCRLs); 600 } else { 601 if (allowSeparateKey) { 602 try { 603 verifyWithSeparateSigningKey(cert, prevKey, signFlag, 604 stackedCerts); 605 return; 606 } catch (CertPathValidatorException cpve) { 607 if (networkFailureException != null) { 608 // if a network issue previously prevented us from 609 // retrieving a CRL from one of the user-specified 610 // CertStores, throw it now so it can be handled 611 // appropriately 612 throw networkFailureException; 613 } 614 throw cpve; 615 } 616 } else { 617 if (networkFailureException != null) { 618 // if a network issue previously prevented us from 619 // retrieving a CRL from one of the user-specified 620 // CertStores, throw it now so it can be handled 621 // appropriately 622 throw networkFailureException; 623 } 624 throw new CertPathValidatorException( 625 "Could not determine revocation status", null, null, -1, 626 BasicReason.UNDETERMINED_REVOCATION_STATUS); 627 } 628 } 629 } 630 } 631 632 private void checkApprovedCRLs(X509Certificate cert, 633 Set<X509CRL> approvedCRLs) 634 throws CertPathValidatorException 635 { 636 // See if the cert is in the set of approved crls. 637 if (debug != null) { 638 BigInteger sn = cert.getSerialNumber(); 639 debug.println("RevocationChecker.checkApprovedCRLs() " + 640 "starting the final sweep..."); 641 debug.println("RevocationChecker.checkApprovedCRLs()" + 642 " cert SN: " + sn.toString()); 643 } 644 645 CRLReason reasonCode = CRLReason.UNSPECIFIED; 646 X509CRLEntryImpl entry = null; 647 for (X509CRL crl : approvedCRLs) { 648 X509CRLEntry e = crl.getRevokedCertificate(cert); 649 if (e != null) { 650 try { 651 entry = X509CRLEntryImpl.toImpl(e); 652 } catch (CRLException ce) { 653 throw new CertPathValidatorException(ce); 654 } 655 if (debug != null) { 656 debug.println("RevocationChecker.checkApprovedCRLs()" 657 + " CRL entry: " + entry.toString()); 658 } 659 660 /* 661 * Abort CRL validation and throw exception if there are any 662 * unrecognized critical CRL entry extensions (see section 663 * 5.3 of RFC 5280). 664 */ 665 Set<String> unresCritExts = entry.getCriticalExtensionOIDs(); 666 if (unresCritExts != null && !unresCritExts.isEmpty()) { 667 /* remove any that we will process */ 668 unresCritExts.remove(ReasonCode_Id.toString()); 669 unresCritExts.remove(CertificateIssuer_Id.toString()); 670 if (!unresCritExts.isEmpty()) { 671 throw new CertPathValidatorException( 672 "Unrecognized critical extension(s) in revoked " + 673 "CRL entry"); 674 } 675 } 676 677 reasonCode = entry.getRevocationReason(); 678 if (reasonCode == null) { 679 reasonCode = CRLReason.UNSPECIFIED; 680 } 681 Date revocationDate = entry.getRevocationDate(); 682 if (revocationDate.before(params.date())) { 683 Throwable t = new CertificateRevokedException( 684 revocationDate, reasonCode, 685 crl.getIssuerX500Principal(), entry.getExtensions()); 686 throw new CertPathValidatorException( 687 t.getMessage(), t, null, -1, BasicReason.REVOKED); 688 } 689 } 690 } 691 } 692 693 private void checkOCSP(X509Certificate cert, 694 Collection<String> unresolvedCritExts) 695 throws CertPathValidatorException 696 { 697 X509CertImpl currCert = null; 698 try { 699 currCert = X509CertImpl.toImpl(cert); 700 } catch (CertificateException ce) { 701 throw new CertPathValidatorException(ce); 702 } 703 704 // The algorithm constraints of the OCSP trusted responder certificate 705 // does not need to be checked in this code. The constraints will be 706 // checked when the responder's certificate is validated. 707 708 OCSPResponse response = null; 709 CertId certId = null; 710 try { 711 certId = new CertId(issuerInfo.getName(), issuerInfo.getPublicKey(), 712 currCert.getSerialNumberObject()); 713 714 // check if there is a cached OCSP response available 715 byte[] responseBytes = ocspResponses.get(cert); 716 if (responseBytes != null) { 717 if (debug != null) { 718 debug.println("Found cached OCSP response"); 719 } 720 response = new OCSPResponse(responseBytes); 721 722 // verify the response 723 byte[] nonce = null; 724 for (Extension ext : ocspExtensions) { 725 if (ext.getId().equals("1.3.6.1.5.5.7.48.1.2")) { 726 nonce = ext.getValue(); 727 } 728 } 729 response.verify(Collections.singletonList(certId), issuerInfo, 730 responderCert, params.date(), nonce); 731 732 } else { 733 URI responderURI = (this.responderURI != null) 734 ? this.responderURI 735 : OCSP.getResponderURI(currCert); 736 if (responderURI == null) { 737 throw new CertPathValidatorException( 738 "Certificate does not specify OCSP responder", null, 739 null, -1); 740 } 741 742 response = OCSP.check(Collections.singletonList(certId), 743 responderURI, issuerInfo, 744 responderCert, null, ocspExtensions); 745 } 746 } catch (IOException e) { 747 throw new CertPathValidatorException( 748 "Unable to determine revocation status due to network error", 749 e, null, -1, BasicReason.UNDETERMINED_REVOCATION_STATUS); 750 } 751 752 RevocationStatus rs = 753 (RevocationStatus)response.getSingleResponse(certId); 754 RevocationStatus.CertStatus certStatus = rs.getCertStatus(); 755 if (certStatus == RevocationStatus.CertStatus.REVOKED) { 756 Date revocationTime = rs.getRevocationTime(); 757 if (revocationTime.before(params.date())) { 758 Throwable t = new CertificateRevokedException( 759 revocationTime, rs.getRevocationReason(), 760 response.getSignerCertificate().getSubjectX500Principal(), 761 rs.getSingleExtensions()); 762 throw new CertPathValidatorException(t.getMessage(), t, null, 763 -1, BasicReason.REVOKED); 764 } 765 } else if (certStatus == RevocationStatus.CertStatus.UNKNOWN) { 766 throw new CertPathValidatorException( 767 "Certificate's revocation status is unknown", null, 768 params.certPath(), -1, 769 BasicReason.UNDETERMINED_REVOCATION_STATUS); 770 } 771 } 772 773 /* 774 * Removes any non-hexadecimal characters from a string. 775 */ 776 private static final String HEX_DIGITS = "0123456789ABCDEFabcdef"; 777 private static String stripOutSeparators(String value) { 778 char[] chars = value.toCharArray(); 779 StringBuilder hexNumber = new StringBuilder(); 780 for (int i = 0; i < chars.length; i++) { 781 if (HEX_DIGITS.indexOf(chars[i]) != -1) { 782 hexNumber.append(chars[i]); 783 } 784 } 785 return hexNumber.toString(); 786 } 787 788 /** 789 * Checks that a cert can be used to verify a CRL. 790 * 791 * @param cert an X509Certificate to check 792 * @return a boolean specifying if the cert is allowed to vouch for the 793 * validity of a CRL 794 */ 795 static boolean certCanSignCrl(X509Certificate cert) { 796 // if the cert doesn't include the key usage ext, or 797 // the key usage ext asserts cRLSigning, return true, 798 // otherwise return false. 799 boolean[] keyUsage = cert.getKeyUsage(); 800 if (keyUsage != null) { 801 return keyUsage[6]; 802 } 803 return false; 804 } 805 806 /** 807 * Internal method that verifies a set of possible_crls, 808 * and sees if each is approved, based on the cert. 809 * 810 * @param crls a set of possible CRLs to test for acceptability 811 * @param cert the certificate whose revocation status is being checked 812 * @param signFlag <code>true</code> if prevKey was trusted to sign CRLs 813 * @param prevKey the public key of the issuer of cert 814 * @param reasonsMask the reason code mask 815 * @param trustAnchors a <code>Set</code> of <code>TrustAnchor</code>s> 816 * @return a collection of approved crls (or an empty collection) 817 */ 818 private static final boolean[] ALL_REASONS = 819 {true, true, true, true, true, true, true, true, true}; 820 private Collection<X509CRL> verifyPossibleCRLs(Set<X509CRL> crls, 821 X509Certificate cert, 822 PublicKey prevKey, 823 boolean signFlag, 824 boolean[] reasonsMask, 825 Set<TrustAnchor> anchors) 826 throws CertPathValidatorException 827 { 828 try { 829 X509CertImpl certImpl = X509CertImpl.toImpl(cert); 830 if (debug != null) { 831 debug.println("RevocationChecker.verifyPossibleCRLs: " + 832 "Checking CRLDPs for " 833 + certImpl.getSubjectX500Principal()); 834 } 835 CRLDistributionPointsExtension ext = 836 certImpl.getCRLDistributionPointsExtension(); 837 List<DistributionPoint> points = null; 838 if (ext == null) { 839 // assume a DP with reasons and CRLIssuer fields omitted 840 // and a DP name of the cert issuer. 841 // TODO add issuerAltName too 842 X500Name certIssuer = (X500Name)certImpl.getIssuerDN(); 843 DistributionPoint point = new DistributionPoint( 844 new GeneralNames().add(new GeneralName(certIssuer)), 845 null, null); 846 points = Collections.singletonList(point); 847 } else { 848 points = ext.get(CRLDistributionPointsExtension.POINTS); 849 } 850 Set<X509CRL> results = new HashSet<>(); 851 for (DistributionPoint point : points) { 852 for (X509CRL crl : crls) { 853 if (DistributionPointFetcher.verifyCRL( 854 certImpl, point, crl, reasonsMask, signFlag, 855 prevKey, null, params.sigProvider(), anchors, 856 certStores, params.date())) 857 { 858 results.add(crl); 859 } 860 } 861 if (Arrays.equals(reasonsMask, ALL_REASONS)) 862 break; 863 } 864 return results; 865 } catch (CertificateException | CRLException | IOException e) { 866 if (debug != null) { 867 debug.println("Exception while verifying CRL: "+e.getMessage()); 868 e.printStackTrace(); 869 } 870 return Collections.emptySet(); 871 } 872 } 873 874 /** 875 * We have a cert whose revocation status couldn't be verified by 876 * a CRL issued by the cert that issued the CRL. See if we can 877 * find a valid CRL issued by a separate key that can verify the 878 * revocation status of this certificate. 879 * <p> 880 * Note that this does not provide support for indirect CRLs, 881 * only CRLs signed with a different key (but the same issuer 882 * name) as the certificate being checked. 883 * 884 * @param currCert the <code>X509Certificate</code> to be checked 885 * @param prevKey the <code>PublicKey</code> that failed 886 * @param signFlag <code>true</code> if that key was trusted to sign CRLs 887 * @param stackedCerts a <code>Set</code> of <code>X509Certificate</code>s> 888 * whose revocation status depends on the 889 * non-revoked status of this cert. To avoid 890 * circular dependencies, we assume they're 891 * revoked while checking the revocation 892 * status of this cert. 893 * @throws CertPathValidatorException if the cert's revocation status 894 * cannot be verified successfully with another key 895 */ 896 private void verifyWithSeparateSigningKey(X509Certificate cert, 897 PublicKey prevKey, 898 boolean signFlag, 899 Set<X509Certificate> stackedCerts) 900 throws CertPathValidatorException 901 { 902 String msg = "revocation status"; 903 if (debug != null) { 904 debug.println( 905 "RevocationChecker.verifyWithSeparateSigningKey()" + 906 " ---checking " + msg + "..."); 907 } 908 909 // Reject circular dependencies - RFC 5280 is not explicit on how 910 // to handle this, but does suggest that they can be a security 911 // risk and can create unresolvable dependencies 912 if ((stackedCerts != null) && stackedCerts.contains(cert)) { 913 if (debug != null) { 914 debug.println( 915 "RevocationChecker.verifyWithSeparateSigningKey()" + 916 " circular dependency"); 917 } 918 throw new CertPathValidatorException 919 ("Could not determine revocation status", null, null, -1, 920 BasicReason.UNDETERMINED_REVOCATION_STATUS); 921 } 922 923 // Try to find another key that might be able to sign 924 // CRLs vouching for this cert. 925 // If prevKey wasn't trusted, maybe we just didn't have the right 926 // path to it. Don't rule that key out. 927 if (!signFlag) { 928 buildToNewKey(cert, null, stackedCerts); 929 } else { 930 buildToNewKey(cert, prevKey, stackedCerts); 931 } 932 } 933 934 /** 935 * Tries to find a CertPath that establishes a key that can be 936 * used to verify the revocation status of a given certificate. 937 * Ignores keys that have previously been tried. Throws a 938 * CertPathValidatorException if no such key could be found. 939 * 940 * @param currCert the <code>X509Certificate</code> to be checked 941 * @param prevKey the <code>PublicKey</code> of the certificate whose key 942 * cannot be used to vouch for the CRL and should be ignored 943 * @param stackedCerts a <code>Set</code> of <code>X509Certificate</code>s> 944 * whose revocation status depends on the 945 * establishment of this path. 946 * @throws CertPathValidatorException on failure 947 */ 948 private static final boolean [] CRL_SIGN_USAGE = 949 { false, false, false, false, false, false, true }; 950 private void buildToNewKey(X509Certificate currCert, 951 PublicKey prevKey, 952 Set<X509Certificate> stackedCerts) 953 throws CertPathValidatorException 954 { 955 956 if (debug != null) { 957 debug.println("RevocationChecker.buildToNewKey()" + 958 " starting work"); 959 } 960 Set<PublicKey> badKeys = new HashSet<>(); 961 if (prevKey != null) { 962 badKeys.add(prevKey); 963 } 964 X509CertSelector certSel = new RejectKeySelector(badKeys); 965 certSel.setSubject(currCert.getIssuerX500Principal()); 966 certSel.setKeyUsage(CRL_SIGN_USAGE); 967 968 Set<TrustAnchor> newAnchors = anchor == null ? 969 params.trustAnchors() : 970 Collections.singleton(anchor); 971 972 PKIXBuilderParameters builderParams; 973 try { 974 builderParams = new PKIXBuilderParameters(newAnchors, certSel); 975 } catch (InvalidAlgorithmParameterException iape) { 976 throw new RuntimeException(iape); // should never occur 977 } 978 builderParams.setInitialPolicies(params.initialPolicies()); 979 builderParams.setCertStores(certStores); 980 builderParams.setExplicitPolicyRequired 981 (params.explicitPolicyRequired()); 982 builderParams.setPolicyMappingInhibited 983 (params.policyMappingInhibited()); 984 builderParams.setAnyPolicyInhibited(params.anyPolicyInhibited()); 985 // Policy qualifiers must be rejected, since we don't have 986 // any way to convey them back to the application. 987 // That's the default, so no need to write code. 988 builderParams.setDate(params.date()); 989 // CertPathCheckers need to be cloned to start from fresh state 990 builderParams.setCertPathCheckers( 991 params.getPKIXParameters().getCertPathCheckers()); 992 builderParams.setSigProvider(params.sigProvider()); 993 994 // Skip revocation during this build to detect circular 995 // references. But check revocation afterwards, using the 996 // key (or any other that works). 997 builderParams.setRevocationEnabled(false); 998 999 // check for AuthorityInformationAccess extension 1000 if (Builder.USE_AIA == true) { 1001 X509CertImpl currCertImpl = null; 1002 try { 1003 currCertImpl = X509CertImpl.toImpl(currCert); 1004 } catch (CertificateException ce) { 1005 // ignore but log it 1006 if (debug != null) { 1007 debug.println("RevocationChecker.buildToNewKey: " + 1008 "error decoding cert: " + ce); 1009 } 1010 } 1011 AuthorityInfoAccessExtension aiaExt = null; 1012 if (currCertImpl != null) { 1013 aiaExt = currCertImpl.getAuthorityInfoAccessExtension(); 1014 } 1015 if (aiaExt != null) { 1016 List<AccessDescription> adList = aiaExt.getAccessDescriptions(); 1017 if (adList != null) { 1018 for (AccessDescription ad : adList) { 1019 CertStore cs = URICertStore.getInstance(ad); 1020 if (cs != null) { 1021 if (debug != null) { 1022 debug.println("adding AIAext CertStore"); 1023 } 1024 builderParams.addCertStore(cs); 1025 } 1026 } 1027 } 1028 } 1029 } 1030 1031 CertPathBuilder builder = null; 1032 try { 1033 builder = CertPathBuilder.getInstance("PKIX"); 1034 } catch (NoSuchAlgorithmException nsae) { 1035 throw new CertPathValidatorException(nsae); 1036 } 1037 while (true) { 1038 try { 1039 if (debug != null) { 1040 debug.println("RevocationChecker.buildToNewKey()" + 1041 " about to try build ..."); 1042 } 1043 PKIXCertPathBuilderResult cpbr = 1044 (PKIXCertPathBuilderResult)builder.build(builderParams); 1045 1046 if (debug != null) { 1047 debug.println("RevocationChecker.buildToNewKey()" + 1048 " about to check revocation ..."); 1049 } 1050 // Now check revocation of all certs in path, assuming that 1051 // the stackedCerts are revoked. 1052 if (stackedCerts == null) { 1053 stackedCerts = new HashSet<X509Certificate>(); 1054 } 1055 stackedCerts.add(currCert); 1056 TrustAnchor ta = cpbr.getTrustAnchor(); 1057 PublicKey prevKey2 = ta.getCAPublicKey(); 1058 if (prevKey2 == null) { 1059 prevKey2 = ta.getTrustedCert().getPublicKey(); 1060 } 1061 boolean signFlag = true; 1062 List<? extends Certificate> cpList = 1063 cpbr.getCertPath().getCertificates(); 1064 try { 1065 for (int i = cpList.size() - 1; i >= 0; i--) { 1066 X509Certificate cert = (X509Certificate) cpList.get(i); 1067 1068 if (debug != null) { 1069 debug.println("RevocationChecker.buildToNewKey()" 1070 + " index " + i + " checking " 1071 + cert); 1072 } 1073 checkCRLs(cert, prevKey2, null, signFlag, true, 1074 stackedCerts, newAnchors); 1075 signFlag = certCanSignCrl(cert); 1076 prevKey2 = cert.getPublicKey(); 1077 } 1078 } catch (CertPathValidatorException cpve) { 1079 // ignore it and try to get another key 1080 badKeys.add(cpbr.getPublicKey()); 1081 continue; 1082 } 1083 1084 if (debug != null) { 1085 debug.println("RevocationChecker.buildToNewKey()" + 1086 " got key " + cpbr.getPublicKey()); 1087 } 1088 // Now check revocation on the current cert using that key and 1089 // the corresponding certificate. 1090 // If it doesn't check out, try to find a different key. 1091 // And if we can't find a key, then return false. 1092 PublicKey newKey = cpbr.getPublicKey(); 1093 X509Certificate newCert = cpList.isEmpty() ? 1094 null : (X509Certificate) cpList.get(0); 1095 try { 1096 checkCRLs(currCert, newKey, newCert, 1097 true, false, null, params.trustAnchors()); 1098 // If that passed, the cert is OK! 1099 return; 1100 } catch (CertPathValidatorException cpve) { 1101 // If it is revoked, rethrow exception 1102 if (cpve.getReason() == BasicReason.REVOKED) { 1103 throw cpve; 1104 } 1105 // Otherwise, ignore the exception and 1106 // try to get another key. 1107 } 1108 badKeys.add(newKey); 1109 } catch (InvalidAlgorithmParameterException iape) { 1110 throw new CertPathValidatorException(iape); 1111 } catch (CertPathBuilderException cpbe) { 1112 throw new CertPathValidatorException 1113 ("Could not determine revocation status", null, null, 1114 -1, BasicReason.UNDETERMINED_REVOCATION_STATUS); 1115 } 1116 } 1117 } 1118 1119 @Override 1120 public RevocationChecker clone() { 1121 RevocationChecker copy = (RevocationChecker)super.clone(); 1122 // we don't deep-copy the exceptions, but that is ok because they 1123 // are never modified after they are instantiated 1124 copy.softFailExceptions = new LinkedList<>(softFailExceptions); 1125 return copy; 1126 } 1127 1128 /* 1129 * This inner class extends the X509CertSelector to add an additional 1130 * check to make sure the subject public key isn't on a particular list. 1131 * This class is used by buildToNewKey() to make sure the builder doesn't 1132 * end up with a CertPath to a public key that has already been rejected. 1133 */ 1134 private static class RejectKeySelector extends X509CertSelector { 1135 private final Set<PublicKey> badKeySet; 1136 1137 /** 1138 * Creates a new <code>RejectKeySelector</code>. 1139 * 1140 * @param badPublicKeys a <code>Set</code> of 1141 * <code>PublicKey</code>s that 1142 * should be rejected (or <code>null</code> 1143 * if no such check should be done) 1144 */ 1145 RejectKeySelector(Set<PublicKey> badPublicKeys) { 1146 this.badKeySet = badPublicKeys; 1147 } 1148 1149 /** 1150 * Decides whether a <code>Certificate</code> should be selected. 1151 * 1152 * @param cert the <code>Certificate</code> to be checked 1153 * @return <code>true</code> if the <code>Certificate</code> should be 1154 * selected, <code>false</code> otherwise 1155 */ 1156 @Override 1157 public boolean match(Certificate cert) { 1158 if (!super.match(cert)) 1159 return(false); 1160 1161 if (badKeySet.contains(cert.getPublicKey())) { 1162 if (debug != null) 1163 debug.println("RejectKeySelector.match: bad key"); 1164 return false; 1165 } 1166 1167 if (debug != null) 1168 debug.println("RejectKeySelector.match: returning true"); 1169 return true; 1170 } 1171 1172 /** 1173 * Return a printable representation of the <code>CertSelector</code>. 1174 * 1175 * @return a <code>String</code> describing the contents of the 1176 * <code>CertSelector</code> 1177 */ 1178 @Override 1179 public String toString() { 1180 StringBuilder sb = new StringBuilder(); 1181 sb.append("RejectKeySelector: [\n"); 1182 sb.append(super.toString()); 1183 sb.append(badKeySet); 1184 sb.append("]"); 1185 return sb.toString(); 1186 } 1187 } 1188 }