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