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