1 /*
   2  * Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.  Oracle designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 
  26 package sun.security.provider.certpath.ldap;
  27 
  28 import java.io.ByteArrayInputStream;
  29 import java.io.IOException;
  30 import java.util.*;
  31 import javax.naming.Context;
  32 import javax.naming.NamingEnumeration;
  33 import javax.naming.NamingException;
  34 import javax.naming.NameNotFoundException;
  35 import javax.naming.directory.Attribute;
  36 import javax.naming.directory.Attributes;
  37 import javax.naming.directory.BasicAttributes;
  38 
  39 import java.security.*;
  40 import java.security.cert.Certificate;
  41 import java.security.cert.*;
  42 import javax.naming.CommunicationException;
  43 import javax.naming.ldap.InitialLdapContext;
  44 import javax.naming.ldap.LdapContext;
  45 import javax.security.auth.x500.X500Principal;
  46 
  47 import sun.security.util.HexDumpEncoder;
  48 import sun.security.provider.certpath.X509CertificatePair;
  49 import sun.security.util.Cache;
  50 import sun.security.util.Debug;
  51 import sun.security.x509.X500Name;
  52 
  53 /**
  54  * Core implementation of a LDAP Cert Store.
  55  * @see java.security.cert.CertStore
  56  *
  57  * @since       9
  58  */
  59 final class LDAPCertStoreImpl {
  60 
  61     private static final Debug debug = Debug.getInstance("certpath");
  62 
  63     private final static boolean DEBUG = false;
  64 
  65     /**
  66      * LDAP attribute identifiers.
  67      */
  68     private static final String USER_CERT = "userCertificate;binary";
  69     private static final String CA_CERT = "cACertificate;binary";
  70     private static final String CROSS_CERT = "crossCertificatePair;binary";
  71     private static final String CRL = "certificateRevocationList;binary";
  72     private static final String ARL = "authorityRevocationList;binary";
  73     private static final String DELTA_CRL = "deltaRevocationList;binary";
  74 
  75     // Constants for various empty values
  76     private final static String[] STRING0 = new String[0];
  77 
  78     private final static byte[][] BB0 = new byte[0][];
  79 
  80     private final static Attributes EMPTY_ATTRIBUTES = new BasicAttributes();
  81 
  82     // cache related constants
  83     private final static int DEFAULT_CACHE_SIZE = 750;
  84     private final static int DEFAULT_CACHE_LIFETIME = 30;
  85 
  86     private final static int LIFETIME;
  87 
  88     private final static String PROP_LIFETIME =
  89                             "sun.security.certpath.ldap.cache.lifetime";
  90 
  91     /*
  92      * Internal system property, that when set to "true", disables the
  93      * JNDI application resource files lookup to prevent recursion issues
  94      * when validating signed JARs with LDAP URLs in certificates.
  95      */
  96     private final static String PROP_DISABLE_APP_RESOURCE_FILES =
  97         "sun.security.certpath.ldap.disable.app.resource.files";
  98 
  99     static {
 100         String s = AccessController.doPrivileged(
 101             (PrivilegedAction<String>) () -> System.getProperty(PROP_LIFETIME));
 102         if (s != null) {
 103             LIFETIME = Integer.parseInt(s); // throws NumberFormatException
 104         } else {
 105             LIFETIME = DEFAULT_CACHE_LIFETIME;
 106         }
 107     }
 108 
 109     /**
 110      * The CertificateFactory used to decode certificates from
 111      * their binary stored form.
 112      */
 113     private CertificateFactory cf;
 114     /**
 115      * The JNDI directory context.
 116      */
 117     private LdapContext ctx;
 118 
 119     /**
 120      * Flag indicating that communication error occurred.
 121      */
 122     private boolean communicationError = false;
 123 
 124     /**
 125      * Flag indicating whether we should prefetch CRLs.
 126      */
 127     private boolean prefetchCRLs = false;
 128 
 129     private final Cache<String, byte[][]> valueCache;
 130 
 131     private int cacheHits = 0;
 132     private int cacheMisses = 0;
 133     private int requests = 0;
 134 
 135     /**
 136      * Creates a <code>CertStore</code> with the specified parameters.
 137      */
 138     LDAPCertStoreImpl(String serverName, int port)
 139         throws InvalidAlgorithmParameterException {
 140         createInitialDirContext(serverName, port);
 141         // Create CertificateFactory for use later on
 142         try {
 143             cf = CertificateFactory.getInstance("X.509");
 144         } catch (CertificateException e) {
 145             throw new InvalidAlgorithmParameterException(
 146                 "unable to create CertificateFactory for X.509");
 147         }
 148         if (LIFETIME == 0) {
 149             valueCache = Cache.newNullCache();
 150         } else if (LIFETIME < 0) {
 151             valueCache = Cache.newSoftMemoryCache(DEFAULT_CACHE_SIZE);
 152         } else {
 153             valueCache = Cache.newSoftMemoryCache(DEFAULT_CACHE_SIZE, LIFETIME);
 154         }
 155     }
 156 
 157     /**
 158      * Create InitialDirContext.
 159      *
 160      * @param server Server DNS name hosting LDAP service
 161      * @param port   Port at which server listens for requests
 162      * @throws InvalidAlgorithmParameterException if creation fails
 163      */
 164     private void createInitialDirContext(String server, int port)
 165             throws InvalidAlgorithmParameterException {
 166         String url = "ldap://" + server + ":" + port;
 167         Hashtable<String,Object> env = new Hashtable<>();
 168         env.put(Context.INITIAL_CONTEXT_FACTORY,
 169                 "com.sun.jndi.ldap.LdapCtxFactory");
 170         env.put(Context.PROVIDER_URL, url);
 171 
 172         // If property is set to true, disable application resource file lookup.
 173         boolean disableAppResourceFiles = AccessController.doPrivileged(
 174             (PrivilegedAction<Boolean>) () -> Boolean.getBoolean(PROP_DISABLE_APP_RESOURCE_FILES));
 175         if (disableAppResourceFiles) {
 176             if (debug != null) {
 177                 debug.println("LDAPCertStore disabling app resource files");
 178             }
 179             env.put("com.sun.naming.disable.app.resource.files", "true");
 180         }
 181 
 182         try {
 183             ctx = new InitialLdapContext(env, null);
 184             /*
 185              * By default, follow referrals unless application has
 186              * overridden property in an application resource file.
 187              */
 188             Hashtable<?,?> currentEnv = ctx.getEnvironment();
 189             if (currentEnv.get(Context.REFERRAL) == null) {
 190                 ctx.addToEnvironment(Context.REFERRAL, "follow");
 191             }
 192         } catch (NamingException e) {
 193             if (debug != null) {
 194                 debug.println("LDAPCertStore.engineInit about to throw "
 195                     + "InvalidAlgorithmParameterException");
 196                 e.printStackTrace();
 197             }
 198             Exception ee = new InvalidAlgorithmParameterException
 199                 ("unable to create InitialDirContext using supplied parameters");
 200             ee.initCause(e);
 201             throw (InvalidAlgorithmParameterException)ee;
 202         }
 203     }
 204 
 205     /**
 206      * Private class encapsulating the actual LDAP operations and cache
 207      * handling. Use:
 208      *
 209      *   LDAPRequest request = new LDAPRequest(dn);
 210      *   request.addRequestedAttribute(CROSS_CERT);
 211      *   request.addRequestedAttribute(CA_CERT);
 212      *   byte[][] crossValues = request.getValues(CROSS_CERT);
 213      *   byte[][] caValues = request.getValues(CA_CERT);
 214      *
 215      * At most one LDAP request is sent for each instance created. If all
 216      * getValues() calls can be satisfied from the cache, no request
 217      * is sent at all. If a request is sent, all requested attributes
 218      * are always added to the cache irrespective of whether the getValues()
 219      * method is called.
 220      */
 221     private class LDAPRequest {
 222 
 223         private final String name;
 224         private Map<String, byte[][]> valueMap;
 225         private final List<String> requestedAttributes;
 226 
 227         LDAPRequest(String name) {
 228             this.name = name;
 229             requestedAttributes = new ArrayList<>(5);
 230         }
 231 
 232         String getName() {
 233             return name;
 234         }
 235 
 236         void addRequestedAttribute(String attrId) {
 237             if (valueMap != null) {
 238                 throw new IllegalStateException("Request already sent");
 239             }
 240             requestedAttributes.add(attrId);
 241         }
 242 
 243         /**
 244          * Gets one or more binary values from an attribute.
 245          *
 246          * @param name          the location holding the attribute
 247          * @param attrId                the attribute identifier
 248          * @return                      an array of binary values (byte arrays)
 249          * @throws NamingException      if a naming exception occurs
 250          */
 251         byte[][] getValues(String attrId) throws NamingException {
 252             if (DEBUG && ((cacheHits + cacheMisses) % 50 == 0)) {
 253                 System.out.println("Cache hits: " + cacheHits + "; misses: "
 254                         + cacheMisses);
 255             }
 256             String cacheKey = name + "|" + attrId;
 257             byte[][] values = valueCache.get(cacheKey);
 258             if (values != null) {
 259                 cacheHits++;
 260                 return values;
 261             }
 262             cacheMisses++;
 263             Map<String, byte[][]> attrs = getValueMap();
 264             values = attrs.get(attrId);
 265             return values;
 266         }
 267 
 268         /**
 269          * Get a map containing the values for this request. The first time
 270          * this method is called on an object, the LDAP request is sent,
 271          * the results parsed and added to a private map and also to the
 272          * cache of this LDAPCertStore. Subsequent calls return the private
 273          * map immediately.
 274          *
 275          * The map contains an entry for each requested attribute. The
 276          * attribute name is the key, values are byte[][]. If there are no
 277          * values for that attribute, values are byte[0][].
 278          *
 279          * @return                      the value Map
 280          * @throws NamingException      if a naming exception occurs
 281          */
 282         private Map<String, byte[][]> getValueMap() throws NamingException {
 283             if (valueMap != null) {
 284                 return valueMap;
 285             }
 286             if (DEBUG) {
 287                 System.out.println("Request: " + name + ":" + requestedAttributes);
 288                 requests++;
 289                 if (requests % 5 == 0) {
 290                     System.out.println("LDAP requests: " + requests);
 291                 }
 292             }
 293             valueMap = new HashMap<>(8);
 294             String[] attrIds = requestedAttributes.toArray(STRING0);
 295             Attributes attrs;
 296 
 297             if (communicationError) {
 298                 ctx.reconnect(null);
 299                 communicationError = false;
 300             }
 301 
 302             try {
 303                 attrs = ctx.getAttributes(name, attrIds);
 304             } catch (CommunicationException ce) {
 305                 communicationError = true;
 306                 throw ce;
 307             } catch (NameNotFoundException e) {
 308                 // name does not exist on this LDAP server
 309                 // treat same as not attributes found
 310                 attrs = EMPTY_ATTRIBUTES;
 311             }
 312             for (String attrId : requestedAttributes) {
 313                 Attribute attr = attrs.get(attrId);
 314                 byte[][] values = getAttributeValues(attr);
 315                 cacheAttribute(attrId, values);
 316                 valueMap.put(attrId, values);
 317             }
 318             return valueMap;
 319         }
 320 
 321         /**
 322          * Add the values to the cache.
 323          */
 324         private void cacheAttribute(String attrId, byte[][] values) {
 325             String cacheKey = name + "|" + attrId;
 326             valueCache.put(cacheKey, values);
 327         }
 328 
 329         /**
 330          * Get the values for the given attribute. If the attribute is null
 331          * or does not contain any values, a zero length byte array is
 332          * returned. NOTE that it is assumed that all values are byte arrays.
 333          */
 334         private byte[][] getAttributeValues(Attribute attr)
 335                 throws NamingException {
 336             byte[][] values;
 337             if (attr == null) {
 338                 values = BB0;
 339             } else {
 340                 values = new byte[attr.size()][];
 341                 int i = 0;
 342                 NamingEnumeration<?> enum_ = attr.getAll();
 343                 while (enum_.hasMore()) {
 344                     Object obj = enum_.next();
 345                     if (debug != null) {
 346                         if (obj instanceof String) {
 347                             debug.println("LDAPCertStore.getAttrValues() "
 348                                 + "enum.next is a string!: " + obj);
 349                         }
 350                     }
 351                     byte[] value = (byte[])obj;
 352                     values[i++] = value;
 353                 }
 354             }
 355             return values;
 356         }
 357 
 358     }
 359 
 360     /*
 361      * Gets certificates from an attribute id and location in the LDAP
 362      * directory. Returns a Collection containing only the Certificates that
 363      * match the specified CertSelector.
 364      *
 365      * @param name the location holding the attribute
 366      * @param id the attribute identifier
 367      * @param sel a CertSelector that the Certificates must match
 368      * @return a Collection of Certificates found
 369      * @throws CertStoreException       if an exception occurs
 370      */
 371     private Collection<X509Certificate> getCertificates(LDAPRequest request,
 372         String id, X509CertSelector sel) throws CertStoreException {
 373 
 374         /* fetch encoded certs from storage */
 375         byte[][] encodedCert;
 376         try {
 377             encodedCert = request.getValues(id);
 378         } catch (NamingException namingEx) {
 379             throw new CertStoreException(namingEx);
 380         }
 381 
 382         int n = encodedCert.length;
 383         if (n == 0) {
 384             return Collections.emptySet();
 385         }
 386 
 387         List<X509Certificate> certs = new ArrayList<>(n);
 388         /* decode certs and check if they satisfy selector */
 389         for (int i = 0; i < n; i++) {
 390             ByteArrayInputStream bais = new ByteArrayInputStream(encodedCert[i]);
 391             try {
 392                 Certificate cert = cf.generateCertificate(bais);
 393                 if (sel.match(cert)) {
 394                   certs.add((X509Certificate)cert);
 395                 }
 396             } catch (CertificateException e) {
 397                 if (debug != null) {
 398                     debug.println("LDAPCertStore.getCertificates() encountered "
 399                         + "exception while parsing cert, skipping the bad data: ");
 400                     HexDumpEncoder encoder = new HexDumpEncoder();
 401                     debug.println(
 402                         "[ " + encoder.encodeBuffer(encodedCert[i]) + " ]");
 403                 }
 404             }
 405         }
 406 
 407         return certs;
 408     }
 409 
 410     /*
 411      * Gets certificate pairs from an attribute id and location in the LDAP
 412      * directory.
 413      *
 414      * @param name the location holding the attribute
 415      * @param id the attribute identifier
 416      * @return a Collection of X509CertificatePairs found
 417      * @throws CertStoreException       if an exception occurs
 418      */
 419     private Collection<X509CertificatePair> getCertPairs(
 420         LDAPRequest request, String id) throws CertStoreException {
 421 
 422         /* fetch the encoded cert pairs from storage */
 423         byte[][] encodedCertPair;
 424         try {
 425             encodedCertPair = request.getValues(id);
 426         } catch (NamingException namingEx) {
 427             throw new CertStoreException(namingEx);
 428         }
 429 
 430         int n = encodedCertPair.length;
 431         if (n == 0) {
 432             return Collections.emptySet();
 433         }
 434 
 435         List<X509CertificatePair> certPairs = new ArrayList<>(n);
 436         /* decode each cert pair and add it to the Collection */
 437         for (int i = 0; i < n; i++) {
 438             try {
 439                 X509CertificatePair certPair =
 440                     X509CertificatePair.generateCertificatePair(encodedCertPair[i]);
 441                 certPairs.add(certPair);
 442             } catch (CertificateException e) {
 443                 if (debug != null) {
 444                     debug.println(
 445                         "LDAPCertStore.getCertPairs() encountered exception "
 446                         + "while parsing cert, skipping the bad data: ");
 447                     HexDumpEncoder encoder = new HexDumpEncoder();
 448                     debug.println(
 449                         "[ " + encoder.encodeBuffer(encodedCertPair[i]) + " ]");
 450                 }
 451             }
 452         }
 453 
 454         return certPairs;
 455     }
 456 
 457     /*
 458      * Looks at certificate pairs stored in the crossCertificatePair attribute
 459      * at the specified location in the LDAP directory. Returns a Collection
 460      * containing all X509Certificates stored in the forward component that match
 461      * the forward X509CertSelector and all Certificates stored in the reverse
 462      * component that match the reverse X509CertSelector.
 463      * <p>
 464      * If either forward or reverse is null, all certificates from the
 465      * corresponding component will be rejected.
 466      *
 467      * @param name the location to look in
 468      * @param forward the forward X509CertSelector (or null)
 469      * @param reverse the reverse X509CertSelector (or null)
 470      * @return a Collection of X509Certificates found
 471      * @throws CertStoreException       if an exception occurs
 472      */
 473     private Collection<X509Certificate> getMatchingCrossCerts(
 474             LDAPRequest request, X509CertSelector forward,
 475             X509CertSelector reverse)
 476             throws CertStoreException {
 477         // Get the cert pairs
 478         Collection<X509CertificatePair> certPairs =
 479                                 getCertPairs(request, CROSS_CERT);
 480 
 481         // Find Certificates that match and put them in a list
 482         ArrayList<X509Certificate> matchingCerts = new ArrayList<>();
 483         for (X509CertificatePair certPair : certPairs) {
 484             X509Certificate cert;
 485             if (forward != null) {
 486                 cert = certPair.getForward();
 487                 if ((cert != null) && forward.match(cert)) {
 488                     matchingCerts.add(cert);
 489                 }
 490             }
 491             if (reverse != null) {
 492                 cert = certPair.getReverse();
 493                 if ((cert != null) && reverse.match(cert)) {
 494                     matchingCerts.add(cert);
 495                 }
 496             }
 497         }
 498         return matchingCerts;
 499     }
 500 
 501     /**
 502      * Returns a <code>Collection</code> of <code>X509Certificate</code>s that
 503      * match the specified selector. If no <code>X509Certificate</code>s
 504      * match the selector, an empty <code>Collection</code> will be returned.
 505      * <p>
 506      * It is not practical to search every entry in the LDAP database for
 507      * matching <code>X509Certificate</code>s. Instead, the
 508      * <code>X509CertSelector</code> is examined in order to determine where
 509      * matching <code>Certificate</code>s are likely to be found (according
 510      * to the PKIX LDAPv2 schema, RFC 2587).
 511      * If the subject is specified, its directory entry is searched. If the
 512      * issuer is specified, its directory entry is searched. If neither the
 513      * subject nor the issuer are specified (or the selector is not an
 514      * <code>X509CertSelector</code>), a <code>CertStoreException</code> is
 515      * thrown.
 516      *
 517      * @param selector a <code>X509CertSelector</code> used to select which
 518      *  <code>Certificate</code>s should be returned.
 519      * @return a <code>Collection</code> of <code>X509Certificate</code>s that
 520      *         match the specified selector
 521      * @throws CertStoreException if an exception occurs
 522      */
 523     synchronized Collection<X509Certificate> getCertificates
 524         (X509CertSelector xsel, String ldapDN) throws CertStoreException {
 525 
 526         if (ldapDN == null) {
 527             ldapDN = xsel.getSubjectAsString();
 528         }
 529         int basicConstraints = xsel.getBasicConstraints();
 530         String issuer = xsel.getIssuerAsString();
 531         HashSet<X509Certificate> certs = new HashSet<>();
 532         if (debug != null) {
 533             debug.println("LDAPCertStore.engineGetCertificates() basicConstraints: "
 534                 + basicConstraints);
 535         }
 536 
 537         // basicConstraints:
 538         // -2: only EE certs accepted
 539         // -1: no check is done
 540         //  0: any CA certificate accepted
 541         // >1: certificate's basicConstraints extension pathlen must match
 542         if (ldapDN != null) {
 543             if (debug != null) {
 544                 debug.println("LDAPCertStore.engineGetCertificates() "
 545                     + " subject is not null");
 546             }
 547             LDAPRequest request = new LDAPRequest(ldapDN);
 548             if (basicConstraints > -2) {
 549                 request.addRequestedAttribute(CROSS_CERT);
 550                 request.addRequestedAttribute(CA_CERT);
 551                 request.addRequestedAttribute(ARL);
 552                 if (prefetchCRLs) {
 553                     request.addRequestedAttribute(CRL);
 554                 }
 555             }
 556             if (basicConstraints < 0) {
 557                 request.addRequestedAttribute(USER_CERT);
 558             }
 559 
 560             if (basicConstraints > -2) {
 561                 certs.addAll(getMatchingCrossCerts(request, xsel, null));
 562                 if (debug != null) {
 563                     debug.println("LDAPCertStore.engineGetCertificates() after "
 564                         + "getMatchingCrossCerts(subject,xsel,null),certs.size(): "
 565                         + certs.size());
 566                 }
 567                 certs.addAll(getCertificates(request, CA_CERT, xsel));
 568                 if (debug != null) {
 569                     debug.println("LDAPCertStore.engineGetCertificates() after "
 570                         + "getCertificates(subject,CA_CERT,xsel),certs.size(): "
 571                         + certs.size());
 572                 }
 573             }
 574             if (basicConstraints < 0) {
 575                 certs.addAll(getCertificates(request, USER_CERT, xsel));
 576                 if (debug != null) {
 577                     debug.println("LDAPCertStore.engineGetCertificates() after "
 578                         + "getCertificates(subject,USER_CERT, xsel),certs.size(): "
 579                         + certs.size());
 580                 }
 581             }
 582         } else {
 583             if (debug != null) {
 584                 debug.println
 585                     ("LDAPCertStore.engineGetCertificates() subject is null");
 586             }
 587             if (basicConstraints == -2) {
 588                 throw new CertStoreException("need subject to find EE certs");
 589             }
 590             if (issuer == null) {
 591                 throw new CertStoreException("need subject or issuer to find certs");
 592             }
 593         }
 594         if (debug != null) {
 595             debug.println("LDAPCertStore.engineGetCertificates() about to "
 596                 + "getMatchingCrossCerts...");
 597         }
 598         if ((issuer != null) && (basicConstraints > -2)) {
 599             LDAPRequest request = new LDAPRequest(issuer);
 600             request.addRequestedAttribute(CROSS_CERT);
 601             request.addRequestedAttribute(CA_CERT);
 602             request.addRequestedAttribute(ARL);
 603             if (prefetchCRLs) {
 604                 request.addRequestedAttribute(CRL);
 605             }
 606 
 607             certs.addAll(getMatchingCrossCerts(request, null, xsel));
 608             if (debug != null) {
 609                 debug.println("LDAPCertStore.engineGetCertificates() after "
 610                     + "getMatchingCrossCerts(issuer,null,xsel),certs.size(): "
 611                     + certs.size());
 612             }
 613             certs.addAll(getCertificates(request, CA_CERT, xsel));
 614             if (debug != null) {
 615                 debug.println("LDAPCertStore.engineGetCertificates() after "
 616                     + "getCertificates(issuer,CA_CERT,xsel),certs.size(): "
 617                     + certs.size());
 618             }
 619         }
 620         if (debug != null) {
 621             debug.println("LDAPCertStore.engineGetCertificates() returning certs");
 622         }
 623         return certs;
 624     }
 625 
 626     /*
 627      * Gets CRLs from an attribute id and location in the LDAP directory.
 628      * Returns a Collection containing only the CRLs that match the
 629      * specified X509CRLSelector.
 630      *
 631      * @param name the location holding the attribute
 632      * @param id the attribute identifier
 633      * @param sel a X509CRLSelector that the CRLs must match
 634      * @return a Collection of CRLs found
 635      * @throws CertStoreException       if an exception occurs
 636      */
 637     private Collection<X509CRL> getCRLs(LDAPRequest request, String id,
 638             X509CRLSelector sel) throws CertStoreException {
 639 
 640         /* fetch the encoded crls from storage */
 641         byte[][] encodedCRL;
 642         try {
 643             encodedCRL = request.getValues(id);
 644         } catch (NamingException namingEx) {
 645             throw new CertStoreException(namingEx);
 646         }
 647 
 648         int n = encodedCRL.length;
 649         if (n == 0) {
 650             return Collections.emptySet();
 651         }
 652 
 653         List<X509CRL> crls = new ArrayList<>(n);
 654         /* decode each crl and check if it matches selector */
 655         for (int i = 0; i < n; i++) {
 656             try {
 657                 CRL crl = cf.generateCRL(new ByteArrayInputStream(encodedCRL[i]));
 658                 if (sel.match(crl)) {
 659                     crls.add((X509CRL)crl);
 660                 }
 661             } catch (CRLException e) {
 662                 if (debug != null) {
 663                     debug.println("LDAPCertStore.getCRLs() encountered exception"
 664                         + " while parsing CRL, skipping the bad data: ");
 665                     HexDumpEncoder encoder = new HexDumpEncoder();
 666                     debug.println("[ " + encoder.encodeBuffer(encodedCRL[i]) + " ]");
 667                 }
 668             }
 669         }
 670 
 671         return crls;
 672     }
 673 
 674     /**
 675      * Returns a <code>Collection</code> of <code>X509CRL</code>s that
 676      * match the specified selector. If no <code>X509CRL</code>s
 677      * match the selector, an empty <code>Collection</code> will be returned.
 678      * <p>
 679      * It is not practical to search every entry in the LDAP database for
 680      * matching <code>X509CRL</code>s. Instead, the <code>X509CRLSelector</code>
 681      * is examined in order to determine where matching <code>X509CRL</code>s
 682      * are likely to be found (according to the PKIX LDAPv2 schema, RFC 2587).
 683      * If issuerNames or certChecking are specified, the issuer's directory
 684      * entry is searched. If neither issuerNames or certChecking are specified
 685      * (or the selector is not an <code>X509CRLSelector</code>), a
 686      * <code>CertStoreException</code> is thrown.
 687      *
 688      * @param selector A <code>X509CRLSelector</code> used to select which
 689      *  <code>CRL</code>s should be returned. Specify <code>null</code>
 690      *  to return all <code>CRL</code>s.
 691      * @return A <code>Collection</code> of <code>X509CRL</code>s that
 692      *         match the specified selector
 693      * @throws CertStoreException if an exception occurs
 694      */
 695     synchronized Collection<X509CRL> getCRLs(X509CRLSelector xsel,
 696          String ldapDN) throws CertStoreException {
 697 
 698         HashSet<X509CRL> crls = new HashSet<>();
 699 
 700         // Look in directory entry for issuer of cert we're checking.
 701         Collection<Object> issuerNames;
 702         X509Certificate certChecking = xsel.getCertificateChecking();
 703         if (certChecking != null) {
 704             issuerNames = new HashSet<>();
 705             X500Principal issuer = certChecking.getIssuerX500Principal();
 706             issuerNames.add(issuer.getName(X500Principal.RFC2253));
 707         } else {
 708             // But if we don't know which cert we're checking, try the directory
 709             // entries of all acceptable CRL issuers
 710             if (ldapDN != null) {
 711                 issuerNames = new HashSet<>();
 712                 issuerNames.add(ldapDN);
 713             } else {
 714                 issuerNames = xsel.getIssuerNames();
 715                 if (issuerNames == null) {
 716                     throw new CertStoreException("need issuerNames or"
 717                        + " certChecking to find CRLs");
 718                 }
 719             }
 720         }
 721         for (Object nameObject : issuerNames) {
 722             String issuerName;
 723             if (nameObject instanceof byte[]) {
 724                 try {
 725                     X500Principal issuer = new X500Principal((byte[])nameObject);
 726                     issuerName = issuer.getName(X500Principal.RFC2253);
 727                 } catch (IllegalArgumentException e) {
 728                     continue;
 729                 }
 730             } else {
 731                 issuerName = (String)nameObject;
 732             }
 733             // If all we want is CA certs, try to get the (probably shorter) ARL
 734             Collection<X509CRL> entryCRLs = Collections.emptySet();
 735             if (certChecking == null || certChecking.getBasicConstraints() != -1) {
 736                 LDAPRequest request = new LDAPRequest(issuerName);
 737                 request.addRequestedAttribute(CROSS_CERT);
 738                 request.addRequestedAttribute(CA_CERT);
 739                 request.addRequestedAttribute(ARL);
 740                 if (prefetchCRLs) {
 741                     request.addRequestedAttribute(CRL);
 742                 }
 743                 try {
 744                     entryCRLs = getCRLs(request, ARL, xsel);
 745                     if (entryCRLs.isEmpty()) {
 746                         // no ARLs found. We assume that means that there are
 747                         // no ARLs on this server at all and prefetch the CRLs.
 748                         prefetchCRLs = true;
 749                     } else {
 750                         crls.addAll(entryCRLs);
 751                     }
 752                 } catch (CertStoreException e) {
 753                     if (debug != null) {
 754                         debug.println("LDAPCertStore.engineGetCRLs non-fatal error "
 755                             + "retrieving ARLs:" + e);
 756                         e.printStackTrace();
 757                     }
 758                 }
 759             }
 760             // Otherwise, get the CRL
 761             // if certChecking is null, we don't know if we should look in ARL or CRL
 762             // attribute, so check both for matching CRLs.
 763             if (entryCRLs.isEmpty() || certChecking == null) {
 764                 LDAPRequest request = new LDAPRequest(issuerName);
 765                 request.addRequestedAttribute(CRL);
 766                 entryCRLs = getCRLs(request, CRL, xsel);
 767                 crls.addAll(entryCRLs);
 768             }
 769         }
 770         return crls;
 771     }
 772 }