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