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