|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  |  */ | 
|  |  | 
|  | package sun.security.provider.certpath; | 
|  |  | 
|  | import java.io.IOException; | 
|  | import java.math.BigInteger; | 
|  | import java.net.URI; | 
|  | import java.net.URISyntaxException; | 
|  | import java.security.AccessController; | 
|  | import java.security.InvalidAlgorithmParameterException; | 
|  | import java.security.NoSuchAlgorithmException; | 
|  | import java.security.PrivilegedAction; | 
|  | import java.security.PublicKey; | 
|  | import java.security.Security; | 
|  | import java.security.cert.CertPathValidatorException.BasicReason; | 
|  | import java.security.cert.Extension; | 
|  | import java.security.cert.*; | 
|  | import java.util.*; | 
|  | import javax.security.auth.x500.X500Principal; | 
|  |  | 
|  | import static sun.security.provider.certpath.OCSP.*; | 
|  | import static sun.security.provider.certpath.PKIX.*; | 
|  | import sun.security.x509.*; | 
|  | import static sun.security.x509.PKIXExtensions.*; | 
|  | import sun.security.util.Debug; | 
|  |  | 
|  | class RevocationChecker extends PKIXRevocationChecker { | 
|  |  | 
|  |     private static final Debug debug = Debug.getInstance("certpath"); | 
|  |  | 
|  |     private TrustAnchor anchor; | 
|  |     private ValidatorParams params; | 
|  |     private boolean onlyEE; | 
|  |     private boolean softFail; | 
|  |     private boolean crlDP; | 
|  |     private URI responderURI; | 
|  |     private X509Certificate responderCert; | 
|  |     private List<CertStore> certStores; | 
|  |     private Map<X509Certificate, byte[]> ocspResponses; | 
|  |     private List<Extension> ocspExtensions; | 
|  |     private final boolean legacy; | 
|  |     private LinkedList<CertPathValidatorException> softFailExceptions = | 
|  |         new LinkedList<>(); | 
|  |  | 
|  |      | 
|  |     private OCSPResponse.IssuerInfo issuerInfo; | 
|  |     private PublicKey prevPubKey; | 
|  |     private boolean crlSignFlag; | 
|  |     private int certIndex; | 
|  |  | 
|  |     private enum Mode { PREFER_OCSP, PREFER_CRLS, ONLY_CRLS, ONLY_OCSP }; | 
|  |     private Mode mode = Mode.PREFER_OCSP; | 
|  |  | 
|  |     private static class RevocationProperties { | 
|  |         boolean onlyEE; | 
|  |         boolean ocspEnabled; | 
|  |         boolean crlDPEnabled; | 
|  |         String ocspUrl; | 
|  |         String ocspSubject; | 
|  |         String ocspIssuer; | 
|  |         String ocspSerial; | 
|  |     } | 
|  |  | 
|  |     RevocationChecker() { | 
|  |         legacy = false; | 
|  |     } | 
|  |  | 
|  |     RevocationChecker(TrustAnchor anchor, ValidatorParams params) | 
|  |         throws CertPathValidatorException | 
|  |     { | 
|  |         legacy = true; | 
|  |         init(anchor, params); | 
|  |     } | 
|  |  | 
|  |     void init(TrustAnchor anchor, ValidatorParams params) | 
|  |         throws CertPathValidatorException | 
|  |     { | 
|  |         RevocationProperties rp = getRevocationProperties(); | 
|  |         URI uri = getOcspResponder(); | 
|  |         responderURI = (uri == null) ? toURI(rp.ocspUrl) : uri; | 
|  |         X509Certificate cert = getOcspResponderCert(); | 
|  |         responderCert = (cert == null) | 
|  |                         ? getResponderCert(rp, params.trustAnchors(), | 
|  |                                            params.certStores()) | 
|  |                         : cert; | 
|  |         Set<Option> options = getOptions(); | 
|  |         for (Option option : options) { | 
|  |             switch (option) { | 
|  |             case ONLY_END_ENTITY: | 
|  |             case PREFER_CRLS: | 
|  |             case SOFT_FAIL: | 
|  |             case NO_FALLBACK: | 
|  |                 break; | 
|  |             default: | 
|  |                 throw new CertPathValidatorException( | 
|  |                     "Unrecognized revocation parameter option: " + option); | 
|  |             } | 
|  |         } | 
|  |         softFail = options.contains(Option.SOFT_FAIL); | 
|  |  | 
|  |          | 
|  |         if (legacy) { | 
|  |             mode = (rp.ocspEnabled) ? Mode.PREFER_OCSP : Mode.ONLY_CRLS; | 
|  |             onlyEE = rp.onlyEE; | 
|  |         } else { | 
|  |             if (options.contains(Option.NO_FALLBACK)) { | 
|  |                 if (options.contains(Option.PREFER_CRLS)) { | 
|  |                     mode = Mode.ONLY_CRLS; | 
|  |                 } else { | 
|  |                     mode = Mode.ONLY_OCSP; | 
|  |                 } | 
|  |             } else if (options.contains(Option.PREFER_CRLS)) { | 
|  |                 mode = Mode.PREFER_CRLS; | 
|  |             } | 
|  |             onlyEE = options.contains(Option.ONLY_END_ENTITY); | 
|  |         } | 
|  |         if (legacy) { | 
|  |             crlDP = rp.crlDPEnabled; | 
|  |         } else { | 
|  |             crlDP = true; | 
|  |         } | 
|  |         ocspResponses = getOcspResponses(); | 
|  |         ocspExtensions = getOcspExtensions(); | 
|  |  | 
|  |         this.anchor = anchor; | 
|  |         this.params = params; | 
|  |         this.certStores = new ArrayList<>(params.certStores()); | 
|  |         try { | 
|  |             this.certStores.add(CertStore.getInstance("Collection", | 
|  |                 new CollectionCertStoreParameters(params.certificates()))); | 
|  |         } catch (InvalidAlgorithmParameterException | | 
|  |                  NoSuchAlgorithmException e) { | 
|  |             // should never occur but not necessarily fatal, so log it, | 
|  |              | 
|  |             if (debug != null) { | 
|  |                 debug.println("RevocationChecker: " + | 
|  |                               "error creating Collection CertStore: " + e); | 
|  |             } | 
|  |         } | 
|  |     } | 
|  |  | 
|  |     private static URI toURI(String uriString) | 
|  |         throws CertPathValidatorException | 
|  |     { | 
|  |         try { | 
|  |             if (uriString != null) { | 
|  |                 return new URI(uriString); | 
|  |             } | 
|  |             return null; | 
|  |         } catch (URISyntaxException e) { | 
|  |             throw new CertPathValidatorException( | 
|  |                 "cannot parse ocsp.responderURL property", e); | 
|  |         } | 
|  |     } | 
|  |  | 
|  |     private static RevocationProperties getRevocationProperties() { | 
|  |         return AccessController.doPrivileged( | 
|  |             new PrivilegedAction<RevocationProperties>() { | 
|  |                 public RevocationProperties run() { | 
|  |                     RevocationProperties rp = new RevocationProperties(); | 
|  |                     String onlyEE = Security.getProperty( | 
|  |                         "com.sun.security.onlyCheckRevocationOfEECert"); | 
|  |                     rp.onlyEE = onlyEE != null | 
|  |                                 && onlyEE.equalsIgnoreCase("true"); | 
|  |                     String ocspEnabled = Security.getProperty("ocsp.enable"); | 
|  |                     rp.ocspEnabled = ocspEnabled != null | 
|  |                                      && ocspEnabled.equalsIgnoreCase("true"); | 
|  |                     rp.ocspUrl = Security.getProperty("ocsp.responderURL"); | 
|  |                     rp.ocspSubject | 
|  |                         = Security.getProperty("ocsp.responderCertSubjectName"); | 
|  |                     rp.ocspIssuer | 
|  |                         = Security.getProperty("ocsp.responderCertIssuerName"); | 
|  |                     rp.ocspSerial | 
|  |                         = Security.getProperty("ocsp.responderCertSerialNumber"); | 
|  |                     rp.crlDPEnabled | 
|  |                         = Boolean.getBoolean("com.sun.security.enableCRLDP"); | 
|  |                     return rp; | 
|  |                 } | 
|  |             } | 
|  |         ); | 
|  |     } | 
|  |  | 
|  |     private static X509Certificate getResponderCert(RevocationProperties rp, | 
|  |                                                     Set<TrustAnchor> anchors, | 
|  |                                                     List<CertStore> stores) | 
|  |         throws CertPathValidatorException | 
|  |     { | 
|  |         if (rp.ocspSubject != null) { | 
|  |             return getResponderCert(rp.ocspSubject, anchors, stores); | 
|  |         } else if (rp.ocspIssuer != null && rp.ocspSerial != null) { | 
|  |             return getResponderCert(rp.ocspIssuer, rp.ocspSerial, | 
|  |                                     anchors, stores); | 
|  |         } else if (rp.ocspIssuer != null || rp.ocspSerial != null) { | 
|  |             throw new CertPathValidatorException( | 
|  |                 "Must specify both ocsp.responderCertIssuerName and " + | 
|  |                 "ocsp.responderCertSerialNumber properties"); | 
|  |         } | 
|  |         return null; | 
|  |     } | 
|  |  | 
|  |     private static X509Certificate getResponderCert(String subject, | 
|  |                                                     Set<TrustAnchor> anchors, | 
|  |                                                     List<CertStore> stores) | 
|  |         throws CertPathValidatorException | 
|  |     { | 
|  |         X509CertSelector sel = new X509CertSelector(); | 
|  |         try { | 
|  |             sel.setSubject(new X500Principal(subject)); | 
|  |         } catch (IllegalArgumentException e) { | 
|  |             throw new CertPathValidatorException( | 
|  |                 "cannot parse ocsp.responderCertSubjectName property", e); | 
|  |         } | 
|  |         return getResponderCert(sel, anchors, stores); | 
|  |     } | 
|  |  | 
|  |     private static X509Certificate getResponderCert(String issuer, | 
|  |                                                     String serial, | 
|  |                                                     Set<TrustAnchor> anchors, | 
|  |                                                     List<CertStore> stores) | 
|  |         throws CertPathValidatorException | 
|  |     { | 
|  |         X509CertSelector sel = new X509CertSelector(); | 
|  |         try { | 
|  |             sel.setIssuer(new X500Principal(issuer)); | 
|  |         } catch (IllegalArgumentException e) { | 
|  |             throw new CertPathValidatorException( | 
|  |                 "cannot parse ocsp.responderCertIssuerName property", e); | 
|  |         } | 
|  |         try { | 
|  |             sel.setSerialNumber(new BigInteger(stripOutSeparators(serial), 16)); | 
|  |         } catch (NumberFormatException e) { | 
|  |             throw new CertPathValidatorException( | 
|  |                 "cannot parse ocsp.responderCertSerialNumber property", e); | 
|  |         } | 
|  |         return getResponderCert(sel, anchors, stores); | 
|  |     } | 
|  |  | 
|  |     private static X509Certificate getResponderCert(X509CertSelector sel, | 
|  |                                                     Set<TrustAnchor> anchors, | 
|  |                                                     List<CertStore> stores) | 
|  |         throws CertPathValidatorException | 
|  |     { | 
|  |          | 
|  |         for (TrustAnchor anchor : anchors) { | 
|  |             X509Certificate cert = anchor.getTrustedCert(); | 
|  |             if (cert == null) { | 
|  |                 continue; | 
|  |             } | 
|  |             if (sel.match(cert)) { | 
|  |                 return cert; | 
|  |             } | 
|  |         } | 
|  |          | 
|  |         for (CertStore store : stores) { | 
|  |             try { | 
|  |                 Collection<? extends Certificate> certs = | 
|  |                     store.getCertificates(sel); | 
|  |                 if (!certs.isEmpty()) { | 
|  |                     return (X509Certificate)certs.iterator().next(); | 
|  |                 } | 
|  |             } catch (CertStoreException e) { | 
|  |                  | 
|  |                 if (debug != null) { | 
|  |                     debug.println("CertStore exception:" + e); | 
|  |                 } | 
|  |                 continue; | 
|  |             } | 
|  |         } | 
|  |         throw new CertPathValidatorException( | 
|  |             "Cannot find the responder's certificate " + | 
|  |             "(set using the OCSP security properties)."); | 
|  |     } | 
|  |  | 
|  |     @Override | 
|  |     public void init(boolean forward) throws CertPathValidatorException { | 
|  |         if (forward) { | 
|  |             throw new | 
|  |                 CertPathValidatorException("forward checking not supported"); | 
|  |         } | 
|  |         if (anchor != null) { | 
|  |             issuerInfo = new OCSPResponse.IssuerInfo(anchor); | 
|  |             prevPubKey = issuerInfo.getPublicKey(); | 
|  |  | 
|  |         } | 
|  |         crlSignFlag = true; | 
|  |         if (params != null && params.certPath() != null) { | 
|  |             certIndex = params.certPath().getCertificates().size() - 1; | 
|  |         } else { | 
|  |             certIndex = -1; | 
|  |         } | 
|  |         softFailExceptions.clear(); | 
|  |     } | 
|  |  | 
|  |     @Override | 
|  |     public boolean isForwardCheckingSupported() { | 
|  |         return false; | 
|  |     } | 
|  |  | 
|  |     @Override | 
|  |     public Set<String> getSupportedExtensions() { | 
|  |         return null; | 
|  |     } | 
|  |  | 
|  |     @Override | 
|  |     public List<CertPathValidatorException> getSoftFailExceptions() { | 
|  |         return Collections.unmodifiableList(softFailExceptions); | 
|  |     } | 
|  |  | 
|  |     @Override | 
|  |     public void check(Certificate cert, Collection<String> unresolvedCritExts) | 
|  |         throws CertPathValidatorException | 
|  |     { | 
|  |         check((X509Certificate)cert, unresolvedCritExts, | 
|  |               prevPubKey, crlSignFlag); | 
|  |     } | 
|  |  | 
|  |     private void check(X509Certificate xcert, | 
|  |                        Collection<String> unresolvedCritExts, | 
|  |                        PublicKey pubKey, boolean crlSignFlag) | 
|  |         throws CertPathValidatorException | 
|  |     { | 
|  |         if (debug != null) { | 
|  |             debug.println("RevocationChecker.check: checking cert" + | 
|  |                 "\n  SN: " + Debug.toHexString(xcert.getSerialNumber()) + | 
|  |                 "\n  Subject: " + xcert.getSubjectX500Principal() + | 
|  |                 "\n  Issuer: " + xcert.getIssuerX500Principal()); | 
|  |         } | 
|  |         try { | 
|  |             if (onlyEE && xcert.getBasicConstraints() != -1) { | 
|  |                 if (debug != null) { | 
|  |                     debug.println("Skipping revocation check; cert is not " + | 
|  |                                   "an end entity cert"); | 
|  |                 } | 
|  |                 return; | 
|  |             } | 
|  |             switch (mode) { | 
|  |                 case PREFER_OCSP: | 
|  |                 case ONLY_OCSP: | 
|  |                     checkOCSP(xcert, unresolvedCritExts); | 
|  |                     break; | 
|  |                 case PREFER_CRLS: | 
|  |                 case ONLY_CRLS: | 
|  |                     checkCRLs(xcert, unresolvedCritExts, null, | 
|  |                               pubKey, crlSignFlag); | 
|  |                     break; | 
|  |             } | 
|  |         } catch (CertPathValidatorException e) { | 
|  |             if (e.getReason() == BasicReason.REVOKED) { | 
|  |                 throw e; | 
|  |             } | 
|  |             boolean eSoftFail = isSoftFailException(e); | 
|  |             if (eSoftFail) { | 
|  |                 if (mode == Mode.ONLY_OCSP || mode == Mode.ONLY_CRLS) { | 
|  |                     return; | 
|  |                 } | 
|  |             } else { | 
|  |                 if (mode == Mode.ONLY_OCSP || mode == Mode.ONLY_CRLS) { | 
|  |                     throw e; | 
|  |                 } | 
|  |             } | 
|  |             CertPathValidatorException cause = e; | 
|  |              | 
|  |             if (debug != null) { | 
|  |                 debug.println("RevocationChecker.check() " + e.getMessage()); | 
|  |                 debug.println("RevocationChecker.check() preparing to failover"); | 
|  |             } | 
|  |             try { | 
|  |                 switch (mode) { | 
|  |                     case PREFER_OCSP: | 
|  |                         checkCRLs(xcert, unresolvedCritExts, null, | 
|  |                                   pubKey, crlSignFlag); | 
|  |                         break; | 
|  |                     case PREFER_CRLS: | 
|  |                         checkOCSP(xcert, unresolvedCritExts); | 
|  |                         break; | 
|  |                 } | 
|  |             } catch (CertPathValidatorException x) { | 
|  |                 if (debug != null) { | 
|  |                     debug.println("RevocationChecker.check() failover failed"); | 
|  |                     debug.println("RevocationChecker.check() " + x.getMessage()); | 
|  |                 } | 
|  |                 if (x.getReason() == BasicReason.REVOKED) { | 
|  |                     throw x; | 
|  |                 } | 
|  |                 if (!isSoftFailException(x)) { | 
|  |                     cause.addSuppressed(x); | 
|  |                     throw cause; | 
|  |                 } else { | 
|  |                      | 
|  |                     if (!eSoftFail) { | 
|  |                         throw cause; | 
|  |                     } | 
|  |                 } | 
|  |             } | 
|  |         } finally { | 
|  |             updateState(xcert); | 
|  |         } | 
|  |     } | 
|  |  | 
|  |     private boolean isSoftFailException(CertPathValidatorException e) { | 
|  |         if (softFail && | 
|  |             e.getReason() == BasicReason.UNDETERMINED_REVOCATION_STATUS) | 
|  |         { | 
|  |              | 
|  |             CertPathValidatorException e2 = new CertPathValidatorException( | 
|  |                 e.getMessage(), e.getCause(), params.certPath(), certIndex, | 
|  |                 e.getReason()); | 
|  |             softFailExceptions.addFirst(e2); | 
|  |             return true; | 
|  |         } | 
|  |         return false; | 
|  |     } | 
|  |  | 
|  |     private void updateState(X509Certificate cert) | 
|  |         throws CertPathValidatorException | 
|  |     { | 
|  |         issuerInfo = new OCSPResponse.IssuerInfo(anchor, cert); | 
|  |  | 
|  |          | 
|  |         PublicKey pubKey = cert.getPublicKey(); | 
|  |         if (PKIX.isDSAPublicKeyWithoutParams(pubKey)) { | 
|  |              | 
|  |             pubKey = BasicChecker.makeInheritedParamsKey(pubKey, prevPubKey); | 
|  |         } | 
|  |         prevPubKey = pubKey; | 
|  |         crlSignFlag = certCanSignCrl(cert); | 
|  |         if (certIndex > 0) { | 
|  |             certIndex--; | 
|  |         } | 
|  |     } | 
|  |  | 
|  |     // Maximum clock skew in milliseconds (15 minutes) allowed when checking | 
|  |      | 
|  |     private static final long MAX_CLOCK_SKEW = 900000; | 
|  |     private void checkCRLs(X509Certificate cert, | 
|  |                            Collection<String> unresolvedCritExts, | 
|  |                            Set<X509Certificate> stackedCerts, | 
|  |                            PublicKey pubKey, boolean signFlag) | 
|  |         throws CertPathValidatorException | 
|  |     { | 
|  |         checkCRLs(cert, pubKey, null, signFlag, true, | 
|  |                   stackedCerts, params.trustAnchors()); | 
|  |     } | 
|  |  | 
|  |     static boolean isCausedByNetworkIssue(String type, CertStoreException cse) { | 
|  |         boolean result; | 
|  |         Throwable t = cse.getCause(); | 
|  |  | 
|  |         switch (type) { | 
|  |             case "LDAP": | 
|  |                 if (t != null) { | 
|  |                      | 
|  |                     String cn = t.getClass().getName(); | 
|  |                     result = (cn.equals("javax.naming.ServiceUnavailableException") || | 
|  |                         cn.equals("javax.naming.CommunicationException")); | 
|  |                 } else { | 
|  |                     result = false; | 
|  |                 } | 
|  |                 break; | 
|  |             case "SSLServer": | 
|  |                 result = (t != null && t instanceof IOException); | 
|  |                 break; | 
|  |             case "URI": | 
|  |                 result = (t != null && t instanceof IOException); | 
|  |                 break; | 
|  |             default: | 
|  |                  | 
|  |                 return false; | 
|  |         } | 
|  |         return result; | 
|  |     } | 
|  |  | 
|  |     private void checkCRLs(X509Certificate cert, PublicKey prevKey, | 
|  |                            X509Certificate prevCert, boolean signFlag, | 
|  |                            boolean allowSeparateKey, | 
|  |                            Set<X509Certificate> stackedCerts, | 
|  |                            Set<TrustAnchor> anchors) | 
|  |         throws CertPathValidatorException | 
|  |     { | 
|  |         if (debug != null) { | 
|  |             debug.println("RevocationChecker.checkCRLs()" + | 
|  |                           " ---checking revocation status ..."); | 
|  |         } | 
|  |  | 
|  |         // Reject circular dependencies - RFC 5280 is not explicit on how | 
|  |         // to handle this, but does suggest that they can be a security | 
|  |          | 
|  |         if (stackedCerts != null && stackedCerts.contains(cert)) { | 
|  |             if (debug != null) { | 
|  |                 debug.println("RevocationChecker.checkCRLs()" + | 
|  |                               " circular dependency"); | 
|  |             } | 
|  |             throw new CertPathValidatorException | 
|  |                  ("Could not determine revocation status", null, null, -1, | 
|  |                   BasicReason.UNDETERMINED_REVOCATION_STATUS); | 
|  |         } | 
|  |  | 
|  |         Set<X509CRL> possibleCRLs = new HashSet<>(); | 
|  |         Set<X509CRL> approvedCRLs = new HashSet<>(); | 
|  |         X509CRLSelector sel = new X509CRLSelector(); | 
|  |         sel.setCertificateChecking(cert); | 
|  |         CertPathHelper.setDateAndTime(sel, params.date(), MAX_CLOCK_SKEW); | 
|  |  | 
|  |          | 
|  |         CertPathValidatorException networkFailureException = null; | 
|  |         for (CertStore store : certStores) { | 
|  |             try { | 
|  |                 for (CRL crl : store.getCRLs(sel)) { | 
|  |                     possibleCRLs.add((X509CRL)crl); | 
|  |                 } | 
|  |             } catch (CertStoreException e) { | 
|  |                 if (debug != null) { | 
|  |                     debug.println("RevocationChecker.checkCRLs() " + | 
|  |                                   "CertStoreException: " + e.getMessage()); | 
|  |                 } | 
|  |                 if (networkFailureException == null && | 
|  |                     isCausedByNetworkIssue(store.getType(),e)) { | 
|  |                      | 
|  |                     networkFailureException = new CertPathValidatorException( | 
|  |                         "Unable to determine revocation status due to " + | 
|  |                         "network error", e, null, -1, | 
|  |                         BasicReason.UNDETERMINED_REVOCATION_STATUS); | 
|  |                 } | 
|  |             } | 
|  |         } | 
|  |  | 
|  |         if (debug != null) { | 
|  |             debug.println("RevocationChecker.checkCRLs() " + | 
|  |                           "possible crls.size() = " + possibleCRLs.size()); | 
|  |         } | 
|  |         boolean[] reasonsMask = new boolean[9]; | 
|  |         if (!possibleCRLs.isEmpty()) { | 
|  |             // Now that we have a list of possible CRLs, see which ones can | 
|  |              | 
|  |             approvedCRLs.addAll(verifyPossibleCRLs(possibleCRLs, cert, prevKey, | 
|  |                                                    signFlag, reasonsMask, | 
|  |                                                    anchors)); | 
|  |         } | 
|  |  | 
|  |         if (debug != null) { | 
|  |             debug.println("RevocationChecker.checkCRLs() " + | 
|  |                           "approved crls.size() = " + approvedCRLs.size()); | 
|  |         } | 
|  |  | 
|  |         // make sure that we have at least one CRL that _could_ cover | 
|  |          | 
|  |         if (!approvedCRLs.isEmpty() && | 
|  |             Arrays.equals(reasonsMask, ALL_REASONS)) | 
|  |         { | 
|  |             checkApprovedCRLs(cert, approvedCRLs); | 
|  |         } else { | 
|  |             // Check Distribution Points | 
|  |              | 
|  |             try { | 
|  |                 if (crlDP) { | 
|  |                     approvedCRLs.addAll(DistributionPointFetcher.getCRLs( | 
|  |                             sel, signFlag, prevKey, prevCert, | 
|  |                             params.sigProvider(), certStores, reasonsMask, | 
|  |                             anchors, null, params.variant(), anchor)); | 
|  |                 } | 
|  |             } catch (CertStoreException e) { | 
|  |                 if (e instanceof CertStoreTypeException) { | 
|  |                     CertStoreTypeException cste = (CertStoreTypeException)e; | 
|  |                     if (isCausedByNetworkIssue(cste.getType(), e)) { | 
|  |                         throw new CertPathValidatorException( | 
|  |                             "Unable to determine revocation status due to " + | 
|  |                             "network error", e, null, -1, | 
|  |                             BasicReason.UNDETERMINED_REVOCATION_STATUS); | 
|  |                     } | 
|  |                 } | 
|  |                 throw new CertPathValidatorException(e); | 
|  |             } | 
|  |             if (!approvedCRLs.isEmpty() && | 
|  |                 Arrays.equals(reasonsMask, ALL_REASONS)) | 
|  |             { | 
|  |                 checkApprovedCRLs(cert, approvedCRLs); | 
|  |             } else { | 
|  |                 if (allowSeparateKey) { | 
|  |                     try { | 
|  |                         verifyWithSeparateSigningKey(cert, prevKey, signFlag, | 
|  |                                                      stackedCerts); | 
|  |                         return; | 
|  |                     } catch (CertPathValidatorException cpve) { | 
|  |                         if (networkFailureException != null) { | 
|  |                             // if a network issue previously prevented us from | 
|  |                             // retrieving a CRL from one of the user-specified | 
|  |                             // CertStores, throw it now so it can be handled | 
|  |                              | 
|  |                             throw networkFailureException; | 
|  |                         } | 
|  |                         throw cpve; | 
|  |                     } | 
|  |                 } else { | 
|  |                     if (networkFailureException != null) { | 
|  |                         // if a network issue previously prevented us from | 
|  |                         // retrieving a CRL from one of the user-specified | 
|  |                         // CertStores, throw it now so it can be handled | 
|  |                          | 
|  |                         throw networkFailureException; | 
|  |                     } | 
|  |                     throw new CertPathValidatorException( | 
|  |                         "Could not determine revocation status", null, null, -1, | 
|  |                         BasicReason.UNDETERMINED_REVOCATION_STATUS); | 
|  |                 } | 
|  |             } | 
|  |         } | 
|  |     } | 
|  |  | 
|  |     private void checkApprovedCRLs(X509Certificate cert, | 
|  |                                    Set<X509CRL> approvedCRLs) | 
|  |         throws CertPathValidatorException | 
|  |     { | 
|  |          | 
|  |         if (debug != null) { | 
|  |             BigInteger sn = cert.getSerialNumber(); | 
|  |             debug.println("RevocationChecker.checkApprovedCRLs() " + | 
|  |                           "starting the final sweep..."); | 
|  |             debug.println("RevocationChecker.checkApprovedCRLs()" + | 
|  |                           " cert SN: " + sn.toString()); | 
|  |         } | 
|  |  | 
|  |         CRLReason reasonCode = CRLReason.UNSPECIFIED; | 
|  |         X509CRLEntryImpl entry = null; | 
|  |         for (X509CRL crl : approvedCRLs) { | 
|  |             X509CRLEntry e = crl.getRevokedCertificate(cert); | 
|  |             if (e != null) { | 
|  |                 try { | 
|  |                     entry = X509CRLEntryImpl.toImpl(e); | 
|  |                 } catch (CRLException ce) { | 
|  |                     throw new CertPathValidatorException(ce); | 
|  |                 } | 
|  |                 if (debug != null) { | 
|  |                     debug.println("RevocationChecker.checkApprovedCRLs()" | 
|  |                         + " CRL entry: " + entry.toString()); | 
|  |                 } | 
|  |  | 
|  |                  | 
|  |  | 
|  |  | 
|  |  | 
|  |                  */ | 
|  |                 Set<String> unresCritExts = entry.getCriticalExtensionOIDs(); | 
|  |                 if (unresCritExts != null && !unresCritExts.isEmpty()) { | 
|  |                      | 
|  |                     unresCritExts.remove(ReasonCode_Id.toString()); | 
|  |                     unresCritExts.remove(CertificateIssuer_Id.toString()); | 
|  |                     if (!unresCritExts.isEmpty()) { | 
|  |                         throw new CertPathValidatorException( | 
|  |                             "Unrecognized critical extension(s) in revoked " + | 
|  |                             "CRL entry"); | 
|  |                     } | 
|  |                 } | 
|  |  | 
|  |                 reasonCode = entry.getRevocationReason(); | 
|  |                 if (reasonCode == null) { | 
|  |                     reasonCode = CRLReason.UNSPECIFIED; | 
|  |                 } | 
|  |                 Date revocationDate = entry.getRevocationDate(); | 
|  |                 if (revocationDate.before(params.date())) { | 
|  |                     Throwable t = new CertificateRevokedException( | 
|  |                         revocationDate, reasonCode, | 
|  |                         crl.getIssuerX500Principal(), entry.getExtensions()); | 
|  |                     throw new CertPathValidatorException( | 
|  |                         t.getMessage(), t, null, -1, BasicReason.REVOKED); | 
|  |                 } | 
|  |             } | 
|  |         } | 
|  |     } | 
|  |  | 
|  |     private void checkOCSP(X509Certificate cert, | 
|  |                            Collection<String> unresolvedCritExts) | 
|  |         throws CertPathValidatorException | 
|  |     { | 
|  |         X509CertImpl currCert = null; | 
|  |         try { | 
|  |             currCert = X509CertImpl.toImpl(cert); | 
|  |         } catch (CertificateException ce) { | 
|  |             throw new CertPathValidatorException(ce); | 
|  |         } | 
|  |  | 
|  |         // The algorithm constraints of the OCSP trusted responder certificate | 
|  |         // does not need to be checked in this code. The constraints will be | 
|  |         // checked when the responder's certificate is validated. | 
|  |  | 
|  |         OCSPResponse response = null; | 
|  |         CertId certId = null; | 
|  |         try { | 
|  |             certId = new CertId(issuerInfo.getName(), issuerInfo.getPublicKey(), | 
|  |                     currCert.getSerialNumberObject()); | 
|  |  | 
|  |              | 
|  |             byte[] responseBytes = ocspResponses.get(cert); | 
|  |             if (responseBytes != null) { | 
|  |                 if (debug != null) { | 
|  |                     debug.println("Found cached OCSP response"); | 
|  |                 } | 
|  |                 response = new OCSPResponse(responseBytes); | 
|  |  | 
|  |                  | 
|  |                 byte[] nonce = null; | 
|  |                 for (Extension ext : ocspExtensions) { | 
|  |                     if (ext.getId().equals("1.3.6.1.5.5.7.48.1.2")) { | 
|  |                         nonce = ext.getValue(); | 
|  |                     } | 
|  |                 } | 
|  |                 response.verify(Collections.singletonList(certId), issuerInfo, | 
|  |                         responderCert, params.date(), nonce, params.variant()); | 
|  |  | 
|  |             } else { | 
|  |                 URI responderURI = (this.responderURI != null) | 
|  |                                    ? this.responderURI | 
|  |                                    : OCSP.getResponderURI(currCert); | 
|  |                 if (responderURI == null) { | 
|  |                     throw new CertPathValidatorException( | 
|  |                         "Certificate does not specify OCSP responder", null, | 
|  |                         null, -1); | 
|  |                 } | 
|  |  | 
|  |                 response = OCSP.check(Collections.singletonList(certId), | 
|  |                         responderURI, issuerInfo, responderCert, null, | 
|  |                         ocspExtensions, params.variant()); | 
|  |             } | 
|  |         } catch (IOException e) { | 
|  |             throw new CertPathValidatorException( | 
|  |                 "Unable to determine revocation status due to network error", | 
|  |                 e, null, -1, BasicReason.UNDETERMINED_REVOCATION_STATUS); | 
|  |         } | 
|  |  | 
|  |         RevocationStatus rs = | 
|  |             (RevocationStatus)response.getSingleResponse(certId); | 
|  |         RevocationStatus.CertStatus certStatus = rs.getCertStatus(); | 
|  |         if (certStatus == RevocationStatus.CertStatus.REVOKED) { | 
|  |             Date revocationTime = rs.getRevocationTime(); | 
|  |             if (revocationTime.before(params.date())) { | 
|  |                 Throwable t = new CertificateRevokedException( | 
|  |                     revocationTime, rs.getRevocationReason(), | 
|  |                     response.getSignerCertificate().getSubjectX500Principal(), | 
|  |                     rs.getSingleExtensions()); | 
|  |                 throw new CertPathValidatorException(t.getMessage(), t, null, | 
|  |                                                      -1, BasicReason.REVOKED); | 
|  |             } | 
|  |         } else if (certStatus == RevocationStatus.CertStatus.UNKNOWN) { | 
|  |             throw new CertPathValidatorException( | 
|  |                 "Certificate's revocation status is unknown", null, | 
|  |                 params.certPath(), -1, | 
|  |                 BasicReason.UNDETERMINED_REVOCATION_STATUS); | 
|  |         } | 
|  |     } | 
|  |  | 
|  |      | 
|  |  | 
|  |      */ | 
|  |     private static final String HEX_DIGITS = "0123456789ABCDEFabcdef"; | 
|  |     private static String stripOutSeparators(String value) { | 
|  |         char[] chars = value.toCharArray(); | 
|  |         StringBuilder hexNumber = new StringBuilder(); | 
|  |         for (int i = 0; i < chars.length; i++) { | 
|  |             if (HEX_DIGITS.indexOf(chars[i]) != -1) { | 
|  |                 hexNumber.append(chars[i]); | 
|  |             } | 
|  |         } | 
|  |         return hexNumber.toString(); | 
|  |     } | 
|  |  | 
|  |      | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  |      */ | 
|  |     static boolean certCanSignCrl(X509Certificate cert) { | 
|  |         // if the cert doesn't include the key usage ext, or | 
|  |         // the key usage ext asserts cRLSigning, return true, | 
|  |          | 
|  |         boolean[] keyUsage = cert.getKeyUsage(); | 
|  |         if (keyUsage != null) { | 
|  |             return keyUsage[6]; | 
|  |         } | 
|  |         return false; | 
|  |     } | 
|  |  | 
|  |      | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  |      */ | 
|  |     private static final boolean[] ALL_REASONS = | 
|  |         {true, true, true, true, true, true, true, true, true}; | 
|  |     private Collection<X509CRL> verifyPossibleCRLs(Set<X509CRL> crls, | 
|  |                                                    X509Certificate cert, | 
|  |                                                    PublicKey prevKey, | 
|  |                                                    boolean signFlag, | 
|  |                                                    boolean[] reasonsMask, | 
|  |                                                    Set<TrustAnchor> anchors) | 
|  |         throws CertPathValidatorException | 
|  |     { | 
|  |         try { | 
|  |             X509CertImpl certImpl = X509CertImpl.toImpl(cert); | 
|  |             if (debug != null) { | 
|  |                 debug.println("RevocationChecker.verifyPossibleCRLs: " + | 
|  |                               "Checking CRLDPs for " | 
|  |                               + certImpl.getSubjectX500Principal()); | 
|  |             } | 
|  |             CRLDistributionPointsExtension ext = | 
|  |                 certImpl.getCRLDistributionPointsExtension(); | 
|  |             List<DistributionPoint> points = null; | 
|  |             if (ext == null) { | 
|  |                 // assume a DP with reasons and CRLIssuer fields omitted | 
|  |                 // and a DP name of the cert issuer. | 
|  |                  | 
|  |                 X500Name certIssuer = (X500Name)certImpl.getIssuerDN(); | 
|  |                 DistributionPoint point = new DistributionPoint( | 
|  |                      new GeneralNames().add(new GeneralName(certIssuer)), | 
|  |                      null, null); | 
|  |                 points = Collections.singletonList(point); | 
|  |             } else { | 
|  |                 points = ext.get(CRLDistributionPointsExtension.POINTS); | 
|  |             } | 
|  |             Set<X509CRL> results = new HashSet<>(); | 
|  |             for (DistributionPoint point : points) { | 
|  |                 for (X509CRL crl : crls) { | 
|  |                     if (DistributionPointFetcher.verifyCRL( | 
|  |                             certImpl, point, crl, reasonsMask, signFlag, | 
|  |                             prevKey, null, params.sigProvider(), anchors, | 
|  |                             certStores, params.date(), params.variant(), anchor)) | 
|  |                     { | 
|  |                         results.add(crl); | 
|  |                     } | 
|  |                 } | 
|  |                 if (Arrays.equals(reasonsMask, ALL_REASONS)) | 
|  |                     break; | 
|  |             } | 
|  |             return results; | 
|  |         } catch (CertificateException | CRLException | IOException e) { | 
|  |             if (debug != null) { | 
|  |                 debug.println("Exception while verifying CRL: "+e.getMessage()); | 
|  |                 e.printStackTrace(); | 
|  |             } | 
|  |             return Collections.emptySet(); | 
|  |         } | 
|  |     } | 
|  |  | 
|  |      | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  |      */ | 
|  |     private void verifyWithSeparateSigningKey(X509Certificate cert, | 
|  |                                               PublicKey prevKey, | 
|  |                                               boolean signFlag, | 
|  |                                               Set<X509Certificate> stackedCerts) | 
|  |         throws CertPathValidatorException | 
|  |     { | 
|  |         String msg = "revocation status"; | 
|  |         if (debug != null) { | 
|  |             debug.println( | 
|  |                 "RevocationChecker.verifyWithSeparateSigningKey()" + | 
|  |                 " ---checking " + msg + "..."); | 
|  |         } | 
|  |  | 
|  |         // Reject circular dependencies - RFC 5280 is not explicit on how | 
|  |         // to handle this, but does suggest that they can be a security | 
|  |          | 
|  |         if ((stackedCerts != null) && stackedCerts.contains(cert)) { | 
|  |             if (debug != null) { | 
|  |                 debug.println( | 
|  |                     "RevocationChecker.verifyWithSeparateSigningKey()" + | 
|  |                     " circular dependency"); | 
|  |             } | 
|  |             throw new CertPathValidatorException | 
|  |                 ("Could not determine revocation status", null, null, -1, | 
|  |                  BasicReason.UNDETERMINED_REVOCATION_STATUS); | 
|  |         } | 
|  |  | 
|  |         // Try to find another key that might be able to sign | 
|  |         // CRLs vouching for this cert. | 
|  |         // If prevKey wasn't trusted, maybe we just didn't have the right | 
|  |          | 
|  |         if (!signFlag) { | 
|  |             buildToNewKey(cert, null, stackedCerts); | 
|  |         } else { | 
|  |             buildToNewKey(cert, prevKey, stackedCerts); | 
|  |         } | 
|  |     } | 
|  |  | 
|  |      | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  |      */ | 
|  |     private static final boolean [] CRL_SIGN_USAGE = | 
|  |         { false, false, false, false, false, false, true }; | 
|  |     private void buildToNewKey(X509Certificate currCert, | 
|  |                                PublicKey prevKey, | 
|  |                                Set<X509Certificate> stackedCerts) | 
|  |         throws CertPathValidatorException | 
|  |     { | 
|  |  | 
|  |         if (debug != null) { | 
|  |             debug.println("RevocationChecker.buildToNewKey()" + | 
|  |                           " starting work"); | 
|  |         } | 
|  |         Set<PublicKey> badKeys = new HashSet<>(); | 
|  |         if (prevKey != null) { | 
|  |             badKeys.add(prevKey); | 
|  |         } | 
|  |         X509CertSelector certSel = new RejectKeySelector(badKeys); | 
|  |         certSel.setSubject(currCert.getIssuerX500Principal()); | 
|  |         certSel.setKeyUsage(CRL_SIGN_USAGE); | 
|  |  | 
|  |         Set<TrustAnchor> newAnchors = anchor == null ? | 
|  |                                       params.trustAnchors() : | 
|  |                                       Collections.singleton(anchor); | 
|  |  | 
|  |         PKIXBuilderParameters builderParams; | 
|  |         try { | 
|  |             builderParams = new PKIXBuilderParameters(newAnchors, certSel); | 
|  |         } catch (InvalidAlgorithmParameterException iape) { | 
|  |             throw new RuntimeException(iape);  | 
|  |         } | 
|  |         builderParams.setInitialPolicies(params.initialPolicies()); | 
|  |         builderParams.setCertStores(certStores); | 
|  |         builderParams.setExplicitPolicyRequired | 
|  |             (params.explicitPolicyRequired()); | 
|  |         builderParams.setPolicyMappingInhibited | 
|  |             (params.policyMappingInhibited()); | 
|  |         builderParams.setAnyPolicyInhibited(params.anyPolicyInhibited()); | 
|  |         // Policy qualifiers must be rejected, since we don't have | 
|  |         // any way to convey them back to the application. | 
|  |          | 
|  |         builderParams.setDate(params.date()); | 
|  |         builderParams.setCertPathCheckers(params.certPathCheckers()); | 
|  |         builderParams.setSigProvider(params.sigProvider()); | 
|  |  | 
|  |         // Skip revocation during this build to detect circular | 
|  |         // references. But check revocation afterwards, using the | 
|  |          | 
|  |         builderParams.setRevocationEnabled(false); | 
|  |  | 
|  |          | 
|  |         if (Builder.USE_AIA == true) { | 
|  |             X509CertImpl currCertImpl = null; | 
|  |             try { | 
|  |                 currCertImpl = X509CertImpl.toImpl(currCert); | 
|  |             } catch (CertificateException ce) { | 
|  |                  | 
|  |                 if (debug != null) { | 
|  |                     debug.println("RevocationChecker.buildToNewKey: " + | 
|  |                                   "error decoding cert: " + ce); | 
|  |                 } | 
|  |             } | 
|  |             AuthorityInfoAccessExtension aiaExt = null; | 
|  |             if (currCertImpl != null) { | 
|  |                 aiaExt = currCertImpl.getAuthorityInfoAccessExtension(); | 
|  |             } | 
|  |             if (aiaExt != null) { | 
|  |                 List<AccessDescription> adList = aiaExt.getAccessDescriptions(); | 
|  |                 if (adList != null) { | 
|  |                     for (AccessDescription ad : adList) { | 
|  |                         CertStore cs = URICertStore.getInstance(ad); | 
|  |                         if (cs != null) { | 
|  |                             if (debug != null) { | 
|  |                                 debug.println("adding AIAext CertStore"); | 
|  |                             } | 
|  |                             builderParams.addCertStore(cs); | 
|  |                         } | 
|  |                     } | 
|  |                 } | 
|  |             } | 
|  |         } | 
|  |  | 
|  |         CertPathBuilder builder = null; | 
|  |         try { | 
|  |             builder = CertPathBuilder.getInstance("PKIX"); | 
|  |         } catch (NoSuchAlgorithmException nsae) { | 
|  |             throw new CertPathValidatorException(nsae); | 
|  |         } | 
|  |         while (true) { | 
|  |             try { | 
|  |                 if (debug != null) { | 
|  |                     debug.println("RevocationChecker.buildToNewKey()" + | 
|  |                                   " about to try build ..."); | 
|  |                 } | 
|  |                 PKIXCertPathBuilderResult cpbr = | 
|  |                     (PKIXCertPathBuilderResult)builder.build(builderParams); | 
|  |  | 
|  |                 if (debug != null) { | 
|  |                     debug.println("RevocationChecker.buildToNewKey()" + | 
|  |                                   " about to check revocation ..."); | 
|  |                 } | 
|  |                 // Now check revocation of all certs in path, assuming that | 
|  |                  | 
|  |                 if (stackedCerts == null) { | 
|  |                     stackedCerts = new HashSet<X509Certificate>(); | 
|  |                 } | 
|  |                 stackedCerts.add(currCert); | 
|  |                 TrustAnchor ta = cpbr.getTrustAnchor(); | 
|  |                 PublicKey prevKey2 = ta.getCAPublicKey(); | 
|  |                 if (prevKey2 == null) { | 
|  |                     prevKey2 = ta.getTrustedCert().getPublicKey(); | 
|  |                 } | 
|  |                 boolean signFlag = true; | 
|  |                 List<? extends Certificate> cpList = | 
|  |                     cpbr.getCertPath().getCertificates(); | 
|  |                 try { | 
|  |                     for (int i = cpList.size() - 1; i >= 0; i--) { | 
|  |                         X509Certificate cert = (X509Certificate) cpList.get(i); | 
|  |  | 
|  |                         if (debug != null) { | 
|  |                             debug.println("RevocationChecker.buildToNewKey()" | 
|  |                                     + " index " + i + " checking " | 
|  |                                     + cert); | 
|  |                         } | 
|  |                         checkCRLs(cert, prevKey2, null, signFlag, true, | 
|  |                                 stackedCerts, newAnchors); | 
|  |                         signFlag = certCanSignCrl(cert); | 
|  |                         prevKey2 = cert.getPublicKey(); | 
|  |                     } | 
|  |                 } catch (CertPathValidatorException cpve) { | 
|  |                      | 
|  |                     badKeys.add(cpbr.getPublicKey()); | 
|  |                     continue; | 
|  |                 } | 
|  |  | 
|  |                 if (debug != null) { | 
|  |                     debug.println("RevocationChecker.buildToNewKey()" + | 
|  |                                   " got key " + cpbr.getPublicKey()); | 
|  |                 } | 
|  |                 // Now check revocation on the current cert using that key and | 
|  |                 // the corresponding certificate. | 
|  |                 // If it doesn't check out, try to find a different key. | 
|  |                  | 
|  |                 PublicKey newKey = cpbr.getPublicKey(); | 
|  |                 X509Certificate newCert = cpList.isEmpty() ? | 
|  |                     null : (X509Certificate) cpList.get(0); | 
|  |                 try { | 
|  |                     checkCRLs(currCert, newKey, newCert, | 
|  |                               true, false, null, params.trustAnchors()); | 
|  |                      | 
|  |                     return; | 
|  |                 } catch (CertPathValidatorException cpve) { | 
|  |                      | 
|  |                     if (cpve.getReason() == BasicReason.REVOKED) { | 
|  |                         throw cpve; | 
|  |                     } | 
|  |                     // Otherwise, ignore the exception and | 
|  |                     // try to get another key. | 
|  |                 } | 
|  |                 badKeys.add(newKey); | 
|  |             } catch (InvalidAlgorithmParameterException iape) { | 
|  |                 throw new CertPathValidatorException(iape); | 
|  |             } catch (CertPathBuilderException cpbe) { | 
|  |                 throw new CertPathValidatorException | 
|  |                     ("Could not determine revocation status", null, null, | 
|  |                      -1, BasicReason.UNDETERMINED_REVOCATION_STATUS); | 
|  |             } | 
|  |         } | 
|  |     } | 
|  |  | 
|  |      | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  |      */ | 
|  |     private static class RejectKeySelector extends X509CertSelector { | 
|  |         private final Set<PublicKey> badKeySet; | 
|  |  | 
|  |          | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  |          */ | 
|  |         RejectKeySelector(Set<PublicKey> badPublicKeys) { | 
|  |             this.badKeySet = badPublicKeys; | 
|  |         } | 
|  |  | 
|  |          | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  |          */ | 
|  |         @Override | 
|  |         public boolean match(Certificate cert) { | 
|  |             if (!super.match(cert)) | 
|  |                 return(false); | 
|  |  | 
|  |             if (badKeySet.contains(cert.getPublicKey())) { | 
|  |                 if (debug != null) | 
|  |                     debug.println("RejectKeySelector.match: bad key"); | 
|  |                 return false; | 
|  |             } | 
|  |  | 
|  |             if (debug != null) | 
|  |                 debug.println("RejectKeySelector.match: returning true"); | 
|  |             return true; | 
|  |         } | 
|  |  | 
|  |          | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  |          */ | 
|  |         @Override | 
|  |         public String toString() { | 
|  |             StringBuilder sb = new StringBuilder(); | 
|  |             sb.append("RejectKeySelector: [\n"); | 
|  |             sb.append(super.toString()); | 
|  |             sb.append(badKeySet); | 
|  |             sb.append("]"); | 
|  |             return sb.toString(); | 
|  |         } | 
|  |     } | 
|  | } |