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