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