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