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