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