1 /* 2 * Copyright (c) 2012, 2013, 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 88 RevocationChecker() { 89 legacy = false; 90 } 91 92 RevocationChecker(TrustAnchor anchor, ValidatorParams params) 93 throws CertPathValidatorException 94 { 95 legacy = true; 96 init(anchor, params); 97 } 98 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 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 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 try { 347 if (onlyEE && xcert.getBasicConstraints() != -1) { 348 if (debug != null) { 349 debug.println("Skipping revocation check, not end " + 350 "entity cert"); 351 } 352 return; 353 } 354 switch (mode) { 355 case PREFER_OCSP: 356 case ONLY_OCSP: 357 checkOCSP(xcert, unresolvedCritExts); 358 break; 359 case PREFER_CRLS: 360 case ONLY_CRLS: 361 checkCRLs(xcert, unresolvedCritExts, null, 362 pubKey, crlSignFlag); 363 break; 364 } 365 } catch (CertPathValidatorException e) { 366 if (e.getReason() == BasicReason.REVOKED) { 367 throw e; 368 } 369 boolean eSoftFail = isSoftFailException(e); 370 if (eSoftFail) { 371 if (mode == Mode.ONLY_OCSP || mode == Mode.ONLY_CRLS) { 372 return; 373 } 374 } else { 375 if (mode == Mode.ONLY_OCSP || mode == Mode.ONLY_CRLS) { 376 throw e; 377 } 378 } 379 CertPathValidatorException cause = e; 380 // Otherwise, failover 381 if (debug != null) { 382 debug.println("RevocationChecker.check() " + e.getMessage()); 383 debug.println("RevocationChecker.check() preparing to failover"); 384 } 385 try { 386 switch (mode) { 387 case PREFER_OCSP: 388 checkCRLs(xcert, unresolvedCritExts, null, 389 pubKey, crlSignFlag); 390 break; 391 case PREFER_CRLS: 392 checkOCSP(xcert, unresolvedCritExts); 393 break; 394 } 395 } catch (CertPathValidatorException x) { 396 if (debug != null) { 397 debug.println("RevocationChecker.check() failover failed"); 398 debug.println("RevocationChecker.check() " + x.getMessage()); 399 } 400 if (x.getReason() == BasicReason.REVOKED) { 401 throw x; 402 } 403 if (!isSoftFailException(x)) { 404 cause.addSuppressed(x); 405 throw cause; 406 } else { 407 // only pass if both exceptions were soft failures 408 if (!eSoftFail) { 409 throw cause; 410 } 411 } 412 } 413 } finally { 414 updateState(xcert); 415 } 416 } 417 418 private boolean isSoftFailException(CertPathValidatorException e) { 419 if (softFail && 420 e.getReason() == BasicReason.UNDETERMINED_REVOCATION_STATUS) 421 { 422 // recreate exception with correct index 423 CertPathValidatorException e2 = new CertPathValidatorException( 424 e.getMessage(), e.getCause(), params.certPath(), certIndex, 425 e.getReason()); 426 softFailExceptions.addFirst(e2); 427 return true; 428 } 429 return false; 430 } 431 432 private void updateState(X509Certificate cert) 433 throws CertPathValidatorException 434 { 435 issuerCert = cert; 436 437 // Make new public key if parameters are missing 438 PublicKey pubKey = cert.getPublicKey(); 439 if (PKIX.isDSAPublicKeyWithoutParams(pubKey)) { 440 // pubKey needs to inherit DSA parameters from prev key 441 pubKey = BasicChecker.makeInheritedParamsKey(pubKey, prevPubKey); 442 } 443 prevPubKey = pubKey; 444 crlSignFlag = certCanSignCrl(cert); 445 if (certIndex > 0) { 446 certIndex--; 447 } 448 } 449 450 // Maximum clock skew in milliseconds (15 minutes) allowed when checking 451 // validity of CRLs 452 private static final long MAX_CLOCK_SKEW = 900000; 453 private void checkCRLs(X509Certificate cert, 454 Collection<String> unresolvedCritExts, 455 Set<X509Certificate> stackedCerts, 456 PublicKey pubKey, boolean signFlag) 457 throws CertPathValidatorException 458 { 459 checkCRLs(cert, pubKey, null, signFlag, true, 460 stackedCerts, params.trustAnchors()); 461 } 462 463 private void checkCRLs(X509Certificate cert, PublicKey prevKey, 464 X509Certificate prevCert, boolean signFlag, 465 boolean allowSeparateKey, 466 Set<X509Certificate> stackedCerts, 467 Set<TrustAnchor> anchors) 468 throws CertPathValidatorException 469 { 470 if (debug != null) { 471 debug.println("RevocationChecker.checkCRLs()" + 472 " ---checking revocation status ..."); 473 } 474 475 // reject circular dependencies - RFC 3280 is not explicit on how 476 // to handle this, so we feel it is safest to reject them until 477 // the issue is resolved in the PKIX WG. 478 if (stackedCerts != null && stackedCerts.contains(cert)) { 479 if (debug != null) { 480 debug.println("RevocationChecker.checkCRLs()" + 481 " circular dependency"); 482 } 483 throw new CertPathValidatorException 484 ("Could not determine revocation status", null, null, -1, 485 BasicReason.UNDETERMINED_REVOCATION_STATUS); 486 } 487 488 Set<X509CRL> possibleCRLs = new HashSet<>(); 489 Set<X509CRL> approvedCRLs = new HashSet<>(); 490 X509CRLSelector sel = new X509CRLSelector(); 491 sel.setCertificateChecking(cert); 492 CertPathHelper.setDateAndTime(sel, params.date(), MAX_CLOCK_SKEW); 493 494 // First, check user-specified CertStores 495 CertPathValidatorException networkFailureException = null; 496 for (CertStore store : certStores) { 497 try { 498 for (CRL crl : store.getCRLs(sel)) { 499 possibleCRLs.add((X509CRL)crl); 500 } 501 } catch (CertStoreException e) { 502 if (debug != null) { 503 debug.println("RevocationChecker.checkCRLs() " + 504 "CertStoreException: " + e.getMessage()); 505 } 506 if (networkFailureException == null && 507 CertStoreHelper.isCausedByNetworkIssue(store.getType(),e)) { 508 // save this exception, we may need to throw it later 509 networkFailureException = new CertPathValidatorException( 510 "Unable to determine revocation status due to " + 511 "network error", e, null, -1, 512 BasicReason.UNDETERMINED_REVOCATION_STATUS); 513 } 514 } 515 } 516 517 if (debug != null) { 518 debug.println("RevocationChecker.checkCRLs() " + 519 "possible crls.size() = " + possibleCRLs.size()); 520 } 521 boolean[] reasonsMask = new boolean[9]; 522 if (!possibleCRLs.isEmpty()) { 523 // Now that we have a list of possible CRLs, see which ones can 524 // be approved 525 approvedCRLs.addAll(verifyPossibleCRLs(possibleCRLs, cert, prevKey, 526 signFlag, reasonsMask, 527 anchors)); 528 } 529 530 if (debug != null) { 531 debug.println("RevocationChecker.checkCRLs() " + 532 "approved crls.size() = " + approvedCRLs.size()); 533 } 534 535 // make sure that we have at least one CRL that _could_ cover 536 // the certificate in question and all reasons are covered 537 if (!approvedCRLs.isEmpty() && 538 Arrays.equals(reasonsMask, ALL_REASONS)) 539 { 540 checkApprovedCRLs(cert, approvedCRLs); 541 } else { 542 // Check Distribution Points 543 // all CRLs returned by the DP Fetcher have also been verified 544 try { 545 if (crlDP) { 546 approvedCRLs.addAll(DistributionPointFetcher.getCRLs( 547 sel, signFlag, prevKey, prevCert, 548 params.sigProvider(), certStores, 549 reasonsMask, anchors, null)); 550 } 551 } catch (CertStoreException e) { 552 if (e instanceof CertStoreTypeException) { 553 CertStoreTypeException cste = (CertStoreTypeException)e; 554 if (CertStoreHelper.isCausedByNetworkIssue(cste.getType(), 555 e)) { 556 throw new CertPathValidatorException( 557 "Unable to determine revocation status due to " + 558 "network error", e, null, -1, 559 BasicReason.UNDETERMINED_REVOCATION_STATUS); 560 } 561 } 562 throw new CertPathValidatorException(e); 563 } 564 if (!approvedCRLs.isEmpty() && 565 Arrays.equals(reasonsMask, ALL_REASONS)) 566 { 567 checkApprovedCRLs(cert, approvedCRLs); 568 } else { 569 if (allowSeparateKey) { 570 try { 571 verifyWithSeparateSigningKey(cert, prevKey, signFlag, 572 stackedCerts); 573 return; 574 } catch (CertPathValidatorException cpve) { 575 if (networkFailureException != null) { 576 // if a network issue previously prevented us from 577 // retrieving a CRL from one of the user-specified 578 // CertStores, throw it now so it can be handled 579 // appropriately 580 throw networkFailureException; 581 } 582 throw cpve; 583 } 584 } else { 585 if (networkFailureException != null) { 586 // if a network issue previously prevented us from 587 // retrieving a CRL from one of the user-specified 588 // CertStores, throw it now so it can be handled 589 // appropriately 590 throw networkFailureException; 591 } 592 throw new CertPathValidatorException( 593 "Could not determine revocation status", null, null, -1, 594 BasicReason.UNDETERMINED_REVOCATION_STATUS); 595 } 596 } 597 } 598 } 599 600 private void checkApprovedCRLs(X509Certificate cert, 601 Set<X509CRL> approvedCRLs) 602 throws CertPathValidatorException 603 { 604 // See if the cert is in the set of approved crls. 605 if (debug != null) { 606 BigInteger sn = cert.getSerialNumber(); 607 debug.println("RevocationChecker.checkApprovedCRLs() " + 608 "starting the final sweep..."); 609 debug.println("RevocationChecker.checkApprovedCRLs()" + 610 " cert SN: " + sn.toString()); 611 } 612 613 CRLReason reasonCode = CRLReason.UNSPECIFIED; 614 X509CRLEntryImpl entry = null; 615 for (X509CRL crl : approvedCRLs) { 616 X509CRLEntry e = crl.getRevokedCertificate(cert); 617 if (e != null) { 618 try { 619 entry = X509CRLEntryImpl.toImpl(e); 620 } catch (CRLException ce) { 621 throw new CertPathValidatorException(ce); 622 } 623 if (debug != null) { 624 debug.println("RevocationChecker.checkApprovedCRLs()" 625 + " CRL entry: " + entry.toString()); 626 } 627 628 /* 629 * Abort CRL validation and throw exception if there are any 630 * unrecognized critical CRL entry extensions (see section 631 * 5.3 of RFC 3280). 632 */ 633 Set<String> unresCritExts = entry.getCriticalExtensionOIDs(); 634 if (unresCritExts != null && !unresCritExts.isEmpty()) { 635 /* remove any that we will process */ 636 unresCritExts.remove(ReasonCode_Id.toString()); 637 unresCritExts.remove(CertificateIssuer_Id.toString()); 638 if (!unresCritExts.isEmpty()) { 639 throw new CertPathValidatorException( 640 "Unrecognized critical extension(s) in revoked " + 641 "CRL entry"); 642 } 643 } 644 645 reasonCode = entry.getRevocationReason(); 646 if (reasonCode == null) { 647 reasonCode = CRLReason.UNSPECIFIED; 648 } 649 Date revocationDate = entry.getRevocationDate(); 650 if (revocationDate.before(params.date())) { 651 Throwable t = new CertificateRevokedException( 652 revocationDate, reasonCode, 653 crl.getIssuerX500Principal(), entry.getExtensions()); 654 throw new CertPathValidatorException( 655 t.getMessage(), t, null, -1, BasicReason.REVOKED); 656 } 657 } 658 } 659 } 660 661 private void checkOCSP(X509Certificate cert, 662 Collection<String> unresolvedCritExts) 663 throws CertPathValidatorException 664 { 665 X509CertImpl currCert = null; 666 try { 667 currCert = X509CertImpl.toImpl(cert); 668 } catch (CertificateException ce) { 669 throw new CertPathValidatorException(ce); 670 } 671 672 // The algorithm constraints of the OCSP trusted responder certificate 673 // does not need to be checked in this code. The constraints will be 674 // checked when the responder's certificate is validated. 675 676 OCSPResponse response = null; 677 CertId certId = null; 678 try { 679 if (issuerCert != null) { 680 certId = new CertId(issuerCert, 681 currCert.getSerialNumberObject()); 682 } else { 683 // must be an anchor name and key 684 certId = new CertId(anchor.getCA(), anchor.getCAPublicKey(), 685 currCert.getSerialNumberObject()); 686 } 687 688 // check if there is a cached OCSP response available 689 byte[] responseBytes = ocspResponses.get(cert); 690 if (responseBytes != null) { 691 if (debug != null) { 692 debug.println("Found cached OCSP response"); 693 } 694 response = new OCSPResponse(responseBytes); 695 696 // verify the response 697 byte[] nonce = null; 698 for (Extension ext : ocspExtensions) { 699 if (ext.getId().equals("1.3.6.1.5.5.7.48.1.2")) { 700 nonce = ext.getValue(); 701 } 702 } 703 response.verify(Collections.singletonList(certId), issuerCert, 704 responderCert, params.date(), nonce); 705 706 } else { 707 URI responderURI = (this.responderURI != null) 708 ? this.responderURI 709 : OCSP.getResponderURI(currCert); 710 if (responderURI == null) { 711 throw new CertPathValidatorException( 712 "Certificate does not specify OCSP responder", null, 713 null, -1); 714 } 715 716 response = OCSP.check(Collections.singletonList(certId), 717 responderURI, issuerCert, responderCert, 718 null, ocspExtensions); 719 } 720 } catch (IOException e) { 721 throw new CertPathValidatorException( 722 "Unable to determine revocation status due to network error", 723 e, null, -1, BasicReason.UNDETERMINED_REVOCATION_STATUS); 724 } 725 726 RevocationStatus rs = 727 (RevocationStatus)response.getSingleResponse(certId); 728 RevocationStatus.CertStatus certStatus = rs.getCertStatus(); 729 if (certStatus == RevocationStatus.CertStatus.REVOKED) { 730 Date revocationTime = rs.getRevocationTime(); 731 if (revocationTime.before(params.date())) { 732 Throwable t = new CertificateRevokedException( 733 revocationTime, rs.getRevocationReason(), 734 response.getSignerCertificate().getSubjectX500Principal(), 735 rs.getSingleExtensions()); 736 throw new CertPathValidatorException(t.getMessage(), t, null, 737 -1, BasicReason.REVOKED); 738 } 739 } else if (certStatus == RevocationStatus.CertStatus.UNKNOWN) { 740 throw new CertPathValidatorException( 741 "Certificate's revocation status is unknown", null, 742 params.certPath(), -1, 743 BasicReason.UNDETERMINED_REVOCATION_STATUS); 744 } 745 } 746 747 /* 748 * Removes any non-hexadecimal characters from a string. 749 */ 750 private static final String HEX_DIGITS = "0123456789ABCDEFabcdef"; 751 private static String stripOutSeparators(String value) { 752 char[] chars = value.toCharArray(); 753 StringBuilder hexNumber = new StringBuilder(); 754 for (int i = 0; i < chars.length; i++) { 755 if (HEX_DIGITS.indexOf(chars[i]) != -1) { 756 hexNumber.append(chars[i]); 757 } 758 } 759 return hexNumber.toString(); 760 } 761 762 /** 763 * Checks that a cert can be used to verify a CRL. 764 * 765 * @param cert an X509Certificate to check 766 * @return a boolean specifying if the cert is allowed to vouch for the 767 * validity of a CRL 768 */ 769 static boolean certCanSignCrl(X509Certificate cert) { 770 // if the cert doesn't include the key usage ext, or 771 // the key usage ext asserts cRLSigning, return true, 772 // otherwise return false. 773 boolean[] keyUsage = cert.getKeyUsage(); 774 if (keyUsage != null) { 775 return keyUsage[6]; 776 } 777 return false; 778 } 779 780 /** 781 * Internal method that verifies a set of possible_crls, 782 * and sees if each is approved, based on the cert. 783 * 784 * @param crls a set of possible CRLs to test for acceptability 785 * @param cert the certificate whose revocation status is being checked 786 * @param signFlag <code>true</code> if prevKey was trusted to sign CRLs 787 * @param prevKey the public key of the issuer of cert 788 * @param reasonsMask the reason code mask 789 * @param trustAnchors a <code>Set</code> of <code>TrustAnchor</code>s> 790 * @return a collection of approved crls (or an empty collection) 791 */ 792 private static final boolean[] ALL_REASONS = 793 {true, true, true, true, true, true, true, true, true}; 794 private Collection<X509CRL> verifyPossibleCRLs(Set<X509CRL> crls, 795 X509Certificate cert, 796 PublicKey prevKey, 797 boolean signFlag, 798 boolean[] reasonsMask, 799 Set<TrustAnchor> anchors) 800 throws CertPathValidatorException 801 { 802 try { 803 X509CertImpl certImpl = X509CertImpl.toImpl(cert); 804 if (debug != null) { 805 debug.println("RevocationChecker.verifyPossibleCRLs: " + 806 "Checking CRLDPs for " 807 + certImpl.getSubjectX500Principal()); 808 } 809 CRLDistributionPointsExtension ext = 810 certImpl.getCRLDistributionPointsExtension(); 811 List<DistributionPoint> points = null; 812 if (ext == null) { 813 // assume a DP with reasons and CRLIssuer fields omitted 814 // and a DP name of the cert issuer. 815 // TODO add issuerAltName too 816 X500Name certIssuer = (X500Name)certImpl.getIssuerDN(); 817 DistributionPoint point = new DistributionPoint( 818 new GeneralNames().add(new GeneralName(certIssuer)), 819 null, null); 820 points = Collections.singletonList(point); 821 } else { 822 points = ext.get(CRLDistributionPointsExtension.POINTS); 823 } 824 Set<X509CRL> results = new HashSet<>(); 825 for (DistributionPoint point : points) { 826 for (X509CRL crl : crls) { 827 if (DistributionPointFetcher.verifyCRL( 828 certImpl, point, crl, reasonsMask, signFlag, 829 prevKey, null, params.sigProvider(), anchors, 830 certStores, params.date())) 831 { 832 results.add(crl); 833 } 834 } 835 if (Arrays.equals(reasonsMask, ALL_REASONS)) 836 break; 837 } 838 return results; 839 } catch (CertificateException | CRLException | IOException e) { 840 if (debug != null) { 841 debug.println("Exception while verifying CRL: "+e.getMessage()); 842 e.printStackTrace(); 843 } 844 return Collections.emptySet(); 845 } 846 } 847 848 /** 849 * We have a cert whose revocation status couldn't be verified by 850 * a CRL issued by the cert that issued the CRL. See if we can 851 * find a valid CRL issued by a separate key that can verify the 852 * revocation status of this certificate. 853 * <p> 854 * Note that this does not provide support for indirect CRLs, 855 * only CRLs signed with a different key (but the same issuer 856 * name) as the certificate being checked. 857 * 858 * @param currCert the <code>X509Certificate</code> to be checked 859 * @param prevKey the <code>PublicKey</code> that failed 860 * @param signFlag <code>true</code> if that key was trusted to sign CRLs 861 * @param stackedCerts a <code>Set</code> of <code>X509Certificate</code>s> 862 * whose revocation status depends on the 863 * non-revoked status of this cert. To avoid 864 * circular dependencies, we assume they're 865 * revoked while checking the revocation 866 * status of this cert. 867 * @throws CertPathValidatorException if the cert's revocation status 868 * cannot be verified successfully with another key 869 */ 870 private void verifyWithSeparateSigningKey(X509Certificate cert, 871 PublicKey prevKey, 872 boolean signFlag, 873 Set<X509Certificate> stackedCerts) 874 throws CertPathValidatorException 875 { 876 String msg = "revocation status"; 877 if (debug != null) { 878 debug.println( 879 "RevocationChecker.verifyWithSeparateSigningKey()" + 880 " ---checking " + msg + "..."); 881 } 882 883 // reject circular dependencies - RFC 3280 is not explicit on how 884 // to handle this, so we feel it is safest to reject them until 885 // the issue is resolved in the PKIX WG. 886 if ((stackedCerts != null) && stackedCerts.contains(cert)) { 887 if (debug != null) { 888 debug.println( 889 "RevocationChecker.verifyWithSeparateSigningKey()" + 890 " circular dependency"); 891 } 892 throw new CertPathValidatorException 893 ("Could not determine revocation status", null, null, -1, 894 BasicReason.UNDETERMINED_REVOCATION_STATUS); 895 } 896 897 // Try to find another key that might be able to sign 898 // CRLs vouching for this cert. 899 // If prevKey wasn't trusted, maybe we just didn't have the right 900 // path to it. Don't rule that key out. 901 if (!signFlag) { 902 buildToNewKey(cert, null, stackedCerts); 903 } else { 904 buildToNewKey(cert, prevKey, stackedCerts); 905 } 906 } 907 908 /** 909 * Tries to find a CertPath that establishes a key that can be 910 * used to verify the revocation status of a given certificate. 911 * Ignores keys that have previously been tried. Throws a 912 * CertPathValidatorException if no such key could be found. 913 * 914 * @param currCert the <code>X509Certificate</code> to be checked 915 * @param prevKey the <code>PublicKey</code> of the certificate whose key 916 * cannot be used to vouch for the CRL and should be ignored 917 * @param stackedCerts a <code>Set</code> of <code>X509Certificate</code>s> 918 * whose revocation status depends on the 919 * establishment of this path. 920 * @throws CertPathValidatorException on failure 921 */ 922 private static final boolean [] CRL_SIGN_USAGE = 923 { false, false, false, false, false, false, true }; 924 private void buildToNewKey(X509Certificate currCert, 925 PublicKey prevKey, 926 Set<X509Certificate> stackedCerts) 927 throws CertPathValidatorException 928 { 929 930 if (debug != null) { 931 debug.println("RevocationChecker.buildToNewKey()" + 932 " starting work"); 933 } 934 Set<PublicKey> badKeys = new HashSet<>(); 935 if (prevKey != null) { 936 badKeys.add(prevKey); 937 } 938 X509CertSelector certSel = new RejectKeySelector(badKeys); 939 certSel.setSubject(currCert.getIssuerX500Principal()); 940 certSel.setKeyUsage(CRL_SIGN_USAGE); 941 942 Set<TrustAnchor> newAnchors = anchor == null ? 943 params.trustAnchors() : 944 Collections.singleton(anchor); 945 946 PKIXBuilderParameters builderParams; 947 try { 948 builderParams = new PKIXBuilderParameters(newAnchors, certSel); 949 } catch (InvalidAlgorithmParameterException iape) { 950 throw new RuntimeException(iape); // should never occur 951 } 952 builderParams.setInitialPolicies(params.initialPolicies()); 953 builderParams.setCertStores(certStores); 954 builderParams.setExplicitPolicyRequired 955 (params.explicitPolicyRequired()); 956 builderParams.setPolicyMappingInhibited 957 (params.policyMappingInhibited()); 958 builderParams.setAnyPolicyInhibited(params.anyPolicyInhibited()); 959 // Policy qualifiers must be rejected, since we don't have 960 // any way to convey them back to the application. 961 // That's the default, so no need to write code. 962 builderParams.setDate(params.date()); 963 // CertPathCheckers need to be cloned to start from fresh state 964 builderParams.setCertPathCheckers( 965 params.getPKIXParameters().getCertPathCheckers()); 966 builderParams.setSigProvider(params.sigProvider()); 967 968 // Skip revocation during this build to detect circular 969 // references. But check revocation afterwards, using the 970 // key (or any other that works). 971 builderParams.setRevocationEnabled(false); 972 973 // check for AuthorityInformationAccess extension 974 if (Builder.USE_AIA == true) { 975 X509CertImpl currCertImpl = null; 976 try { 977 currCertImpl = X509CertImpl.toImpl(currCert); 978 } catch (CertificateException ce) { 979 // ignore but log it 980 if (debug != null) { 981 debug.println("RevocationChecker.buildToNewKey: " + 982 "error decoding cert: " + ce); 983 } 984 } 985 AuthorityInfoAccessExtension aiaExt = null; 986 if (currCertImpl != null) { 987 aiaExt = currCertImpl.getAuthorityInfoAccessExtension(); 988 } 989 if (aiaExt != null) { 990 List<AccessDescription> adList = aiaExt.getAccessDescriptions(); 991 if (adList != null) { 992 for (AccessDescription ad : adList) { 993 CertStore cs = URICertStore.getInstance(ad); 994 if (cs != null) { 995 if (debug != null) { 996 debug.println("adding AIAext CertStore"); 997 } 998 builderParams.addCertStore(cs); 999 } 1000 } 1001 } 1002 } 1003 } 1004 1005 CertPathBuilder builder = null; 1006 try { 1007 builder = CertPathBuilder.getInstance("PKIX"); 1008 } catch (NoSuchAlgorithmException nsae) { 1009 throw new CertPathValidatorException(nsae); 1010 } 1011 while (true) { 1012 try { 1013 if (debug != null) { 1014 debug.println("RevocationChecker.buildToNewKey()" + 1015 " about to try build ..."); 1016 } 1017 PKIXCertPathBuilderResult cpbr = 1018 (PKIXCertPathBuilderResult)builder.build(builderParams); 1019 1020 if (debug != null) { 1021 debug.println("RevocationChecker.buildToNewKey()" + 1022 " about to check revocation ..."); 1023 } 1024 // Now check revocation of all certs in path, assuming that 1025 // the stackedCerts are revoked. 1026 if (stackedCerts == null) { 1027 stackedCerts = new HashSet<X509Certificate>(); 1028 } 1029 stackedCerts.add(currCert); 1030 TrustAnchor ta = cpbr.getTrustAnchor(); 1031 PublicKey prevKey2 = ta.getCAPublicKey(); 1032 if (prevKey2 == null) { 1033 prevKey2 = ta.getTrustedCert().getPublicKey(); 1034 } 1035 boolean signFlag = true; 1036 List<? extends Certificate> cpList = 1037 cpbr.getCertPath().getCertificates(); 1038 try { 1039 for (int i = cpList.size()-1; i >= 0; i-- ) { 1040 X509Certificate cert = (X509Certificate)cpList.get(i); 1041 1042 if (debug != null) { 1043 debug.println("RevocationChecker.buildToNewKey()" 1044 + " index " + i + " checking " 1045 + cert); 1046 } 1047 checkCRLs(cert, prevKey2, null, signFlag, true, 1048 stackedCerts, newAnchors); 1049 signFlag = certCanSignCrl(cert); 1050 prevKey2 = cert.getPublicKey(); 1051 } 1052 } catch (CertPathValidatorException cpve) { 1053 // ignore it and try to get another key 1054 badKeys.add(cpbr.getPublicKey()); 1055 continue; 1056 } 1057 1058 if (debug != null) { 1059 debug.println("RevocationChecker.buildToNewKey()" + 1060 " got key " + cpbr.getPublicKey()); 1061 } 1062 // Now check revocation on the current cert using that key and 1063 // the corresponding certificate. 1064 // If it doesn't check out, try to find a different key. 1065 // And if we can't find a key, then return false. 1066 PublicKey newKey = cpbr.getPublicKey(); 1067 try { 1068 checkCRLs(currCert, newKey, (X509Certificate) cpList.get(0), 1069 true, false, null, params.trustAnchors()); 1070 // If that passed, the cert is OK! 1071 return; 1072 } catch (CertPathValidatorException cpve) { 1073 // If it is revoked, rethrow exception 1074 if (cpve.getReason() == BasicReason.REVOKED) { 1075 throw cpve; 1076 } 1077 // Otherwise, ignore the exception and 1078 // try to get another key. 1079 } 1080 badKeys.add(newKey); 1081 } catch (InvalidAlgorithmParameterException iape) { 1082 throw new CertPathValidatorException(iape); 1083 } catch (CertPathBuilderException cpbe) { 1084 throw new CertPathValidatorException 1085 ("Could not determine revocation status", null, null, 1086 -1, BasicReason.UNDETERMINED_REVOCATION_STATUS); 1087 } 1088 } 1089 } 1090 1091 @Override 1092 public RevocationChecker clone() { 1093 RevocationChecker copy = (RevocationChecker)super.clone(); 1094 // we don't deep-copy the exceptions, but that is ok because they 1095 // are never modified after they are instantiated 1096 copy.softFailExceptions = new LinkedList<>(softFailExceptions); 1097 return copy; 1098 } 1099 1100 /* 1101 * This inner class extends the X509CertSelector to add an additional 1102 * check to make sure the subject public key isn't on a particular list. 1103 * This class is used by buildToNewKey() to make sure the builder doesn't 1104 * end up with a CertPath to a public key that has already been rejected. 1105 */ 1106 private static class RejectKeySelector extends X509CertSelector { 1107 private final Set<PublicKey> badKeySet; 1108 1109 /** 1110 * Creates a new <code>RejectKeySelector</code>. 1111 * 1112 * @param badPublicKeys a <code>Set</code> of 1113 * <code>PublicKey</code>s that 1114 * should be rejected (or <code>null</code> 1115 * if no such check should be done) 1116 */ 1117 RejectKeySelector(Set<PublicKey> badPublicKeys) { 1118 this.badKeySet = badPublicKeys; 1119 } 1120 1121 /** 1122 * Decides whether a <code>Certificate</code> should be selected. 1123 * 1124 * @param cert the <code>Certificate</code> to be checked 1125 * @return <code>true</code> if the <code>Certificate</code> should be 1126 * selected, <code>false</code> otherwise 1127 */ 1128 @Override 1129 public boolean match(Certificate cert) { 1130 if (!super.match(cert)) 1131 return(false); 1132 1133 if (badKeySet.contains(cert.getPublicKey())) { 1134 if (debug != null) 1135 debug.println("RejectKeySelector.match: bad key"); 1136 return false; 1137 } 1138 1139 if (debug != null) 1140 debug.println("RejectKeySelector.match: returning true"); 1141 return true; 1142 } 1143 1144 /** 1145 * Return a printable representation of the <code>CertSelector</code>. 1146 * 1147 * @return a <code>String</code> describing the contents of the 1148 * <code>CertSelector</code> 1149 */ 1150 @Override 1151 public String toString() { 1152 StringBuilder sb = new StringBuilder(); 1153 sb.append("RejectKeySelector: [\n"); 1154 sb.append(super.toString()); 1155 sb.append(badKeySet); 1156 sb.append("]"); 1157 return sb.toString(); 1158 } 1159 } 1160 } 1161