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 }