1/*
2 * Copyright (c) 2000, 2013, Oracle and/or its affiliates. All rights reserved.
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4 *
5 * This code is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License version 2 only, as
7 * published by the Free Software Foundation.  Oracle designates this
8 * particular file as subject to the "Classpath" exception as provided
9 * by Oracle in the LICENSE file that accompanied this code.
10 *
11 * This code is distributed in the hope that it will be useful, but WITHOUT
12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
14 * version 2 for more details (a copy is included in the LICENSE file that
15 * accompanied this code).
16 *
17 * You should have received a copy of the GNU General Public License version
18 * 2 along with this work; if not, write to the Free Software Foundation,
19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20 *
21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22 * or visit www.oracle.com if you need additional information or have any
23 * questions.
24 */
25
26package sun.security.provider.certpath.ldap;
27
28import java.io.ByteArrayInputStream;
29import java.io.IOException;
30import java.math.BigInteger;
31import java.net.URI;
32import java.util.*;
33import javax.naming.Context;
34import javax.naming.NamingEnumeration;
35import javax.naming.NamingException;
36import javax.naming.NameNotFoundException;
37import javax.naming.directory.Attribute;
38import javax.naming.directory.Attributes;
39import javax.naming.directory.BasicAttributes;
40import javax.naming.directory.DirContext;
41import javax.naming.directory.InitialDirContext;
42
43import java.security.*;
44import java.security.cert.Certificate;
45import java.security.cert.*;
46import javax.security.auth.x500.X500Principal;
47
48import sun.misc.HexDumpEncoder;
49import sun.security.provider.certpath.X509CertificatePair;
50import sun.security.util.Cache;
51import sun.security.util.Debug;
52import sun.security.x509.X500Name;
53import sun.security.action.GetBooleanAction;
54import sun.security.action.GetPropertyAction;
55
56/**
57 * A <code>CertStore</code> that retrieves <code>Certificates</code> and
58 * <code>CRL</code>s from an LDAP directory, using the PKIX LDAP V2 Schema
59 * (RFC 2587):
60 * <a href="http://www.ietf.org/rfc/rfc2587.txt">
61 * http://www.ietf.org/rfc/rfc2587.txt</a>.
62 * <p>
63 * Before calling the {@link #engineGetCertificates engineGetCertificates} or
64 * {@link #engineGetCRLs engineGetCRLs} methods, the
65 * {@link #LDAPCertStore(CertStoreParameters)
66 * LDAPCertStore(CertStoreParameters)} constructor is called to create the
67 * <code>CertStore</code> and establish the DNS name and port of the LDAP
68 * server from which <code>Certificate</code>s and <code>CRL</code>s will be
69 * retrieved.
70 * <p>
71 * <b>Concurrent Access</b>
72 * <p>
73 * As described in the javadoc for <code>CertStoreSpi</code>, the
74 * <code>engineGetCertificates</code> and <code>engineGetCRLs</code> methods
75 * must be thread-safe. That is, multiple threads may concurrently
76 * invoke these methods on a single <code>LDAPCertStore</code> object
77 * (or more than one) with no ill effects. This allows a
78 * <code>CertPathBuilder</code> to search for a CRL while simultaneously
79 * searching for further certificates, for instance.
80 * <p>
81 * This is achieved by adding the <code>synchronized</code> keyword to the
82 * <code>engineGetCertificates</code> and <code>engineGetCRLs</code> methods.
83 * <p>
84 * This classes uses caching and requests multiple attributes at once to
85 * minimize LDAP round trips. The cache is associated with the CertStore
86 * instance. It uses soft references to hold the values to minimize impact
87 * on footprint and currently has a maximum size of 750 attributes and a
88 * 30 second default lifetime.
89 * <p>
90 * We always request CA certificates, cross certificate pairs, and ARLs in
91 * a single LDAP request when any one of them is needed. The reason is that
92 * we typically need all of them anyway and requesting them in one go can
93 * reduce the number of requests to a third. Even if we don't need them,
94 * these attributes are typically small enough not to cause a noticeable
95 * overhead. In addition, when the prefetchCRLs flag is true, we also request
96 * the full CRLs. It is currently false initially but set to true once any
97 * request for an ARL to the server returns an null value. The reason is
98 * that CRLs could be rather large but are rarely used. This implementation
99 * should improve performance in most cases.
100 *
101 * @see java.security.cert.CertStore
102 *
103 * @since       1.4
104 * @author      Steve Hanna
105 * @author      Andreas Sterbenz
106 */
107public final class LDAPCertStore extends CertStoreSpi {
108
109    private static final Debug debug = Debug.getInstance("certpath");
110
111    private final static boolean DEBUG = false;
112
113    /**
114     * LDAP attribute identifiers.
115     */
116    private static final String USER_CERT = "userCertificate;binary";
117    private static final String CA_CERT = "cACertificate;binary";
118    private static final String CROSS_CERT = "crossCertificatePair;binary";
119    private static final String CRL = "certificateRevocationList;binary";
120    private static final String ARL = "authorityRevocationList;binary";
121    private static final String DELTA_CRL = "deltaRevocationList;binary";
122
123    // Constants for various empty values
124    private final static String[] STRING0 = new String[0];
125
126    private final static byte[][] BB0 = new byte[0][];
127
128    private final static Attributes EMPTY_ATTRIBUTES = new BasicAttributes();
129
130    // cache related constants
131    private final static int DEFAULT_CACHE_SIZE = 750;
132    private final static int DEFAULT_CACHE_LIFETIME = 30;
133
134    private final static int LIFETIME;
135
136    private final static String PROP_LIFETIME =
137                            "sun.security.certpath.ldap.cache.lifetime";
138
139    /*
140     * Internal system property, that when set to "true", disables the
141     * JNDI application resource files lookup to prevent recursion issues
142     * when validating signed JARs with LDAP URLs in certificates.
143     */
144    private final static String PROP_DISABLE_APP_RESOURCE_FILES =
145        "sun.security.certpath.ldap.disable.app.resource.files";
146
147    static {
148        String s = AccessController.doPrivileged(
149                                new GetPropertyAction(PROP_LIFETIME));
150        if (s != null) {
151            LIFETIME = Integer.parseInt(s); // throws NumberFormatException
152        } else {
153            LIFETIME = DEFAULT_CACHE_LIFETIME;
154        }
155    }
156
157    /**
158     * The CertificateFactory used to decode certificates from
159     * their binary stored form.
160     */
161    private CertificateFactory cf;
162    /**
163     * The JNDI directory context.
164     */
165    private DirContext ctx;
166
167    /**
168     * Flag indicating whether we should prefetch CRLs.
169     */
170    private boolean prefetchCRLs = false;
171
172    private final Cache<String, byte[][]> valueCache;
173
174    private int cacheHits = 0;
175    private int cacheMisses = 0;
176    private int requests = 0;
177
178    /**
179     * Creates a <code>CertStore</code> with the specified parameters.
180     * For this class, the parameters object must be an instance of
181     * <code>LDAPCertStoreParameters</code>.
182     *
183     * @param params the algorithm parameters
184     * @exception InvalidAlgorithmParameterException if params is not an
185     *   instance of <code>LDAPCertStoreParameters</code>
186     */
187    public LDAPCertStore(CertStoreParameters params)
188            throws InvalidAlgorithmParameterException {
189        super(params);
190        if (!(params instanceof LDAPCertStoreParameters))
191          throw new InvalidAlgorithmParameterException(
192            "parameters must be LDAPCertStoreParameters");
193
194        LDAPCertStoreParameters lparams = (LDAPCertStoreParameters) params;
195
196        // Create InitialDirContext needed to communicate with the server
197        createInitialDirContext(lparams.getServerName(), lparams.getPort());
198
199        // Create CertificateFactory for use later on
200        try {
201            cf = CertificateFactory.getInstance("X.509");
202        } catch (CertificateException e) {
203            throw new InvalidAlgorithmParameterException(
204                "unable to create CertificateFactory for X.509");
205        }
206        if (LIFETIME == 0) {
207            valueCache = Cache.newNullCache();
208        } else if (LIFETIME < 0) {
209            valueCache = Cache.newSoftMemoryCache(DEFAULT_CACHE_SIZE);
210        } else {
211            valueCache = Cache.newSoftMemoryCache(DEFAULT_CACHE_SIZE, LIFETIME);
212        }
213    }
214
215    /**
216     * Returns an LDAP CertStore. This method consults a cache of
217     * CertStores (shared per JVM) using the LDAP server/port as a key.
218     */
219    private static final Cache<LDAPCertStoreParameters, CertStore>
220        certStoreCache = Cache.newSoftMemoryCache(185);
221    static synchronized CertStore getInstance(LDAPCertStoreParameters params)
222        throws NoSuchAlgorithmException, InvalidAlgorithmParameterException {
223        CertStore lcs = certStoreCache.get(params);
224        if (lcs == null) {
225            lcs = CertStore.getInstance("LDAP", params);
226            certStoreCache.put(params, lcs);
227        } else {
228            if (debug != null) {
229                debug.println("LDAPCertStore.getInstance: cache hit");
230            }
231        }
232        return lcs;
233    }
234
235    /**
236     * Create InitialDirContext.
237     *
238     * @param server Server DNS name hosting LDAP service
239     * @param port   Port at which server listens for requests
240     * @throws InvalidAlgorithmParameterException if creation fails
241     */
242    private void createInitialDirContext(String server, int port)
243            throws InvalidAlgorithmParameterException {
244        String url = "ldap://" + server + ":" + port;
245        Hashtable<String,Object> env = new Hashtable<>();
246        env.put(Context.INITIAL_CONTEXT_FACTORY,
247                "com.sun.jndi.ldap.LdapCtxFactory");
248        env.put(Context.PROVIDER_URL, url);
249
250        // If property is set to true, disable application resource file lookup.
251        boolean disableAppResourceFiles = AccessController.doPrivileged(
252            new GetBooleanAction(PROP_DISABLE_APP_RESOURCE_FILES));
253        if (disableAppResourceFiles) {
254            if (debug != null) {
255                debug.println("LDAPCertStore disabling app resource files");
256            }
257            env.put("com.sun.naming.disable.app.resource.files", "true");
258        }
259
260        try {
261            ctx = new InitialDirContext(env);
262            /*
263             * By default, follow referrals unless application has
264             * overridden property in an application resource file.
265             */
266            Hashtable<?,?> currentEnv = ctx.getEnvironment();
267            if (currentEnv.get(Context.REFERRAL) == null) {
268                ctx.addToEnvironment(Context.REFERRAL, "follow");
269            }
270        } catch (NamingException e) {
271            if (debug != null) {
272                debug.println("LDAPCertStore.engineInit about to throw "
273                    + "InvalidAlgorithmParameterException");
274                e.printStackTrace();
275            }
276            Exception ee = new InvalidAlgorithmParameterException
277                ("unable to create InitialDirContext using supplied parameters");
278            ee.initCause(e);
279            throw (InvalidAlgorithmParameterException)ee;
280        }
281    }
282
283    /**
284     * Private class encapsulating the actual LDAP operations and cache
285     * handling. Use:
286     *
287     *   LDAPRequest request = new LDAPRequest(dn);
288     *   request.addRequestedAttribute(CROSS_CERT);
289     *   request.addRequestedAttribute(CA_CERT);
290     *   byte[][] crossValues = request.getValues(CROSS_CERT);
291     *   byte[][] caValues = request.getValues(CA_CERT);
292     *
293     * At most one LDAP request is sent for each instance created. If all
294     * getValues() calls can be satisfied from the cache, no request
295     * is sent at all. If a request is sent, all requested attributes
296     * are always added to the cache irrespective of whether the getValues()
297     * method is called.
298     */
299    private class LDAPRequest {
300
301        private final String name;
302        private Map<String, byte[][]> valueMap;
303        private final List<String> requestedAttributes;
304
305        LDAPRequest(String name) {
306            this.name = name;
307            requestedAttributes = new ArrayList<>(5);
308        }
309
310        String getName() {
311            return name;
312        }
313
314        void addRequestedAttribute(String attrId) {
315            if (valueMap != null) {
316                throw new IllegalStateException("Request already sent");
317            }
318            requestedAttributes.add(attrId);
319        }
320
321        /**
322         * Gets one or more binary values from an attribute.
323         *
324         * @param name          the location holding the attribute
325         * @param attrId                the attribute identifier
326         * @return                      an array of binary values (byte arrays)
327         * @throws NamingException      if a naming exception occurs
328         */
329        byte[][] getValues(String attrId) throws NamingException {
330            if (DEBUG && ((cacheHits + cacheMisses) % 50 == 0)) {
331                System.out.println("Cache hits: " + cacheHits + "; misses: "
332                        + cacheMisses);
333            }
334            String cacheKey = name + "|" + attrId;
335            byte[][] values = valueCache.get(cacheKey);
336            if (values != null) {
337                cacheHits++;
338                return values;
339            }
340            cacheMisses++;
341            Map<String, byte[][]> attrs = getValueMap();
342            values = attrs.get(attrId);
343            return values;
344        }
345
346        /**
347         * Get a map containing the values for this request. The first time
348         * this method is called on an object, the LDAP request is sent,
349         * the results parsed and added to a private map and also to the
350         * cache of this LDAPCertStore. Subsequent calls return the private
351         * map immediately.
352         *
353         * The map contains an entry for each requested attribute. The
354         * attribute name is the key, values are byte[][]. If there are no
355         * values for that attribute, values are byte[0][].
356         *
357         * @return                      the value Map
358         * @throws NamingException      if a naming exception occurs
359         */
360        private Map<String, byte[][]> getValueMap() throws NamingException {
361            if (valueMap != null) {
362                return valueMap;
363            }
364            if (DEBUG) {
365                System.out.println("Request: " + name + ":" + requestedAttributes);
366                requests++;
367                if (requests % 5 == 0) {
368                    System.out.println("LDAP requests: " + requests);
369                }
370            }
371            valueMap = new HashMap<>(8);
372            String[] attrIds = requestedAttributes.toArray(STRING0);
373            Attributes attrs;
374            try {
375                attrs = ctx.getAttributes(name, attrIds);
376            } catch (NameNotFoundException e) {
377                // name does not exist on this LDAP server
378                // treat same as not attributes found
379                attrs = EMPTY_ATTRIBUTES;
380            }
381            for (String attrId : requestedAttributes) {
382                Attribute attr = attrs.get(attrId);
383                byte[][] values = getAttributeValues(attr);
384                cacheAttribute(attrId, values);
385                valueMap.put(attrId, values);
386            }
387            return valueMap;
388        }
389
390        /**
391         * Add the values to the cache.
392         */
393        private void cacheAttribute(String attrId, byte[][] values) {
394            String cacheKey = name + "|" + attrId;
395            valueCache.put(cacheKey, values);
396        }
397
398        /**
399         * Get the values for the given attribute. If the attribute is null
400         * or does not contain any values, a zero length byte array is
401         * returned. NOTE that it is assumed that all values are byte arrays.
402         */
403        private byte[][] getAttributeValues(Attribute attr)
404                throws NamingException {
405            byte[][] values;
406            if (attr == null) {
407                values = BB0;
408            } else {
409                values = new byte[attr.size()][];
410                int i = 0;
411                NamingEnumeration<?> enum_ = attr.getAll();
412                while (enum_.hasMore()) {
413                    Object obj = enum_.next();
414                    if (debug != null) {
415                        if (obj instanceof String) {
416                            debug.println("LDAPCertStore.getAttrValues() "
417                                + "enum.next is a string!: " + obj);
418                        }
419                    }
420                    byte[] value = (byte[])obj;
421                    values[i++] = value;
422                }
423            }
424            return values;
425        }
426
427    }
428
429    /*
430     * Gets certificates from an attribute id and location in the LDAP
431     * directory. Returns a Collection containing only the Certificates that
432     * match the specified CertSelector.
433     *
434     * @param name the location holding the attribute
435     * @param id the attribute identifier
436     * @param sel a CertSelector that the Certificates must match
437     * @return a Collection of Certificates found
438     * @throws CertStoreException       if an exception occurs
439     */
440    private Collection<X509Certificate> getCertificates(LDAPRequest request,
441        String id, X509CertSelector sel) throws CertStoreException {
442
443        /* fetch encoded certs from storage */
444        byte[][] encodedCert;
445        try {
446            encodedCert = request.getValues(id);
447        } catch (NamingException namingEx) {
448            throw new CertStoreException(namingEx);
449        }
450
451        int n = encodedCert.length;
452        if (n == 0) {
453            return Collections.emptySet();
454        }
455
456        List<X509Certificate> certs = new ArrayList<>(n);
457        /* decode certs and check if they satisfy selector */
458        for (int i = 0; i < n; i++) {
459            ByteArrayInputStream bais = new ByteArrayInputStream(encodedCert[i]);
460            try {
461                Certificate cert = cf.generateCertificate(bais);
462                if (sel.match(cert)) {
463                  certs.add((X509Certificate)cert);
464                }
465            } catch (CertificateException e) {
466                if (debug != null) {
467                    debug.println("LDAPCertStore.getCertificates() encountered "
468                        + "exception while parsing cert, skipping the bad data: ");
469                    HexDumpEncoder encoder = new HexDumpEncoder();
470                    debug.println(
471                        "[ " + encoder.encodeBuffer(encodedCert[i]) + " ]");
472                }
473            }
474        }
475
476        return certs;
477    }
478
479    /*
480     * Gets certificate pairs from an attribute id and location in the LDAP
481     * directory.
482     *
483     * @param name the location holding the attribute
484     * @param id the attribute identifier
485     * @return a Collection of X509CertificatePairs found
486     * @throws CertStoreException       if an exception occurs
487     */
488    private Collection<X509CertificatePair> getCertPairs(
489        LDAPRequest request, String id) throws CertStoreException {
490
491        /* fetch the encoded cert pairs from storage */
492        byte[][] encodedCertPair;
493        try {
494            encodedCertPair = request.getValues(id);
495        } catch (NamingException namingEx) {
496            throw new CertStoreException(namingEx);
497        }
498
499        int n = encodedCertPair.length;
500        if (n == 0) {
501            return Collections.emptySet();
502        }
503
504        List<X509CertificatePair> certPairs = new ArrayList<>(n);
505        /* decode each cert pair and add it to the Collection */
506        for (int i = 0; i < n; i++) {
507            try {
508                X509CertificatePair certPair =
509                    X509CertificatePair.generateCertificatePair(encodedCertPair[i]);
510                certPairs.add(certPair);
511            } catch (CertificateException e) {
512                if (debug != null) {
513                    debug.println(
514                        "LDAPCertStore.getCertPairs() encountered exception "
515                        + "while parsing cert, skipping the bad data: ");
516                    HexDumpEncoder encoder = new HexDumpEncoder();
517                    debug.println(
518                        "[ " + encoder.encodeBuffer(encodedCertPair[i]) + " ]");
519                }
520            }
521        }
522
523        return certPairs;
524    }
525
526    /*
527     * Looks at certificate pairs stored in the crossCertificatePair attribute
528     * at the specified location in the LDAP directory. Returns a Collection
529     * containing all Certificates stored in the forward component that match
530     * the forward CertSelector and all Certificates stored in the reverse
531     * component that match the reverse CertSelector.
532     * <p>
533     * If either forward or reverse is null, all certificates from the
534     * corresponding component will be rejected.
535     *
536     * @param name the location to look in
537     * @param forward the forward CertSelector (or null)
538     * @param reverse the reverse CertSelector (or null)
539     * @return a Collection of Certificates found
540     * @throws CertStoreException       if an exception occurs
541     */
542    private Collection<X509Certificate> getMatchingCrossCerts(
543            LDAPRequest request, X509CertSelector forward,
544            X509CertSelector reverse)
545            throws CertStoreException {
546        // Get the cert pairs
547        Collection<X509CertificatePair> certPairs =
548                                getCertPairs(request, CROSS_CERT);
549
550        // Find Certificates that match and put them in a list
551        ArrayList<X509Certificate> matchingCerts = new ArrayList<>();
552        for (X509CertificatePair certPair : certPairs) {
553            X509Certificate cert;
554            if (forward != null) {
555                cert = certPair.getForward();
556                if ((cert != null) && forward.match(cert)) {
557                    matchingCerts.add(cert);
558                }
559            }
560            if (reverse != null) {
561                cert = certPair.getReverse();
562                if ((cert != null) && reverse.match(cert)) {
563                    matchingCerts.add(cert);
564                }
565            }
566        }
567        return matchingCerts;
568    }
569
570    /**
571     * Returns a <code>Collection</code> of <code>Certificate</code>s that
572     * match the specified selector. If no <code>Certificate</code>s
573     * match the selector, an empty <code>Collection</code> will be returned.
574     * <p>
575     * It is not practical to search every entry in the LDAP database for
576     * matching <code>Certificate</code>s. Instead, the <code>CertSelector</code>
577     * is examined in order to determine where matching <code>Certificate</code>s
578     * are likely to be found (according to the PKIX LDAPv2 schema, RFC 2587).
579     * If the subject is specified, its directory entry is searched. If the
580     * issuer is specified, its directory entry is searched. If neither the
581     * subject nor the issuer are specified (or the selector is not an
582     * <code>X509CertSelector</code>), a <code>CertStoreException</code> is
583     * thrown.
584     *
585     * @param selector a <code>CertSelector</code> used to select which
586     *  <code>Certificate</code>s should be returned.
587     * @return a <code>Collection</code> of <code>Certificate</code>s that
588     *         match the specified selector
589     * @throws CertStoreException if an exception occurs
590     */
591    public synchronized Collection<X509Certificate> engineGetCertificates
592            (CertSelector selector) throws CertStoreException {
593        if (debug != null) {
594            debug.println("LDAPCertStore.engineGetCertificates() selector: "
595                + String.valueOf(selector));
596        }
597
598        if (selector == null) {
599            selector = new X509CertSelector();
600        }
601        if (!(selector instanceof X509CertSelector)) {
602            throw new CertStoreException("LDAPCertStore needs an X509CertSelector " +
603                                         "to find certs");
604        }
605        X509CertSelector xsel = (X509CertSelector) selector;
606        int basicConstraints = xsel.getBasicConstraints();
607        String subject = xsel.getSubjectAsString();
608        String issuer = xsel.getIssuerAsString();
609        HashSet<X509Certificate> certs = new HashSet<>();
610        if (debug != null) {
611            debug.println("LDAPCertStore.engineGetCertificates() basicConstraints: "
612                + basicConstraints);
613        }
614
615        // basicConstraints:
616        // -2: only EE certs accepted
617        // -1: no check is done
618        //  0: any CA certificate accepted
619        // >1: certificate's basicConstraints extension pathlen must match
620        if (subject != null) {
621            if (debug != null) {
622                debug.println("LDAPCertStore.engineGetCertificates() "
623                    + "subject is not null");
624            }
625            LDAPRequest request = new LDAPRequest(subject);
626            if (basicConstraints > -2) {
627                request.addRequestedAttribute(CROSS_CERT);
628                request.addRequestedAttribute(CA_CERT);
629                request.addRequestedAttribute(ARL);
630                if (prefetchCRLs) {
631                    request.addRequestedAttribute(CRL);
632                }
633            }
634            if (basicConstraints < 0) {
635                request.addRequestedAttribute(USER_CERT);
636            }
637
638            if (basicConstraints > -2) {
639                certs.addAll(getMatchingCrossCerts(request, xsel, null));
640                if (debug != null) {
641                    debug.println("LDAPCertStore.engineGetCertificates() after "
642                        + "getMatchingCrossCerts(subject,xsel,null),certs.size(): "
643                        + certs.size());
644                }
645                certs.addAll(getCertificates(request, CA_CERT, xsel));
646                if (debug != null) {
647                    debug.println("LDAPCertStore.engineGetCertificates() after "
648                        + "getCertificates(subject,CA_CERT,xsel),certs.size(): "
649                        + certs.size());
650                }
651            }
652            if (basicConstraints < 0) {
653                certs.addAll(getCertificates(request, USER_CERT, xsel));
654                if (debug != null) {
655                    debug.println("LDAPCertStore.engineGetCertificates() after "
656                        + "getCertificates(subject,USER_CERT, xsel),certs.size(): "
657                        + certs.size());
658                }
659            }
660        } else {
661            if (debug != null) {
662                debug.println
663                    ("LDAPCertStore.engineGetCertificates() subject is null");
664            }
665            if (basicConstraints == -2) {
666                throw new CertStoreException("need subject to find EE certs");
667            }
668            if (issuer == null) {
669                throw new CertStoreException("need subject or issuer to find certs");
670            }
671        }
672        if (debug != null) {
673            debug.println("LDAPCertStore.engineGetCertificates() about to "
674                + "getMatchingCrossCerts...");
675        }
676        if ((issuer != null) && (basicConstraints > -2)) {
677            LDAPRequest request = new LDAPRequest(issuer);
678            request.addRequestedAttribute(CROSS_CERT);
679            request.addRequestedAttribute(CA_CERT);
680            request.addRequestedAttribute(ARL);
681            if (prefetchCRLs) {
682                request.addRequestedAttribute(CRL);
683            }
684
685            certs.addAll(getMatchingCrossCerts(request, null, xsel));
686            if (debug != null) {
687                debug.println("LDAPCertStore.engineGetCertificates() after "
688                    + "getMatchingCrossCerts(issuer,null,xsel),certs.size(): "
689                    + certs.size());
690            }
691            certs.addAll(getCertificates(request, CA_CERT, xsel));
692            if (debug != null) {
693                debug.println("LDAPCertStore.engineGetCertificates() after "
694                    + "getCertificates(issuer,CA_CERT,xsel),certs.size(): "
695                    + certs.size());
696            }
697        }
698        if (debug != null) {
699            debug.println("LDAPCertStore.engineGetCertificates() returning certs");
700        }
701        return certs;
702    }
703
704    /*
705     * Gets CRLs from an attribute id and location in the LDAP directory.
706     * Returns a Collection containing only the CRLs that match the
707     * specified CRLSelector.
708     *
709     * @param name the location holding the attribute
710     * @param id the attribute identifier
711     * @param sel a CRLSelector that the CRLs must match
712     * @return a Collection of CRLs found
713     * @throws CertStoreException       if an exception occurs
714     */
715    private Collection<X509CRL> getCRLs(LDAPRequest request, String id,
716            X509CRLSelector sel) throws CertStoreException {
717
718        /* fetch the encoded crls from storage */
719        byte[][] encodedCRL;
720        try {
721            encodedCRL = request.getValues(id);
722        } catch (NamingException namingEx) {
723            throw new CertStoreException(namingEx);
724        }
725
726        int n = encodedCRL.length;
727        if (n == 0) {
728            return Collections.emptySet();
729        }
730
731        List<X509CRL> crls = new ArrayList<>(n);
732        /* decode each crl and check if it matches selector */
733        for (int i = 0; i < n; i++) {
734            try {
735                CRL crl = cf.generateCRL(new ByteArrayInputStream(encodedCRL[i]));
736                if (sel.match(crl)) {
737                    crls.add((X509CRL)crl);
738                }
739            } catch (CRLException e) {
740                if (debug != null) {
741                    debug.println("LDAPCertStore.getCRLs() encountered exception"
742                        + " while parsing CRL, skipping the bad data: ");
743                    HexDumpEncoder encoder = new HexDumpEncoder();
744                    debug.println("[ " + encoder.encodeBuffer(encodedCRL[i]) + " ]");
745                }
746            }
747        }
748
749        return crls;
750    }
751
752    /**
753     * Returns a <code>Collection</code> of <code>CRL</code>s that
754     * match the specified selector. If no <code>CRL</code>s
755     * match the selector, an empty <code>Collection</code> will be returned.
756     * <p>
757     * It is not practical to search every entry in the LDAP database for
758     * matching <code>CRL</code>s. Instead, the <code>CRLSelector</code>
759     * is examined in order to determine where matching <code>CRL</code>s
760     * are likely to be found (according to the PKIX LDAPv2 schema, RFC 2587).
761     * If issuerNames or certChecking are specified, the issuer's directory
762     * entry is searched. If neither issuerNames or certChecking are specified
763     * (or the selector is not an <code>X509CRLSelector</code>), a
764     * <code>CertStoreException</code> is thrown.
765     *
766     * @param selector A <code>CRLSelector</code> used to select which
767     *  <code>CRL</code>s should be returned. Specify <code>null</code>
768     *  to return all <code>CRL</code>s.
769     * @return A <code>Collection</code> of <code>CRL</code>s that
770     *         match the specified selector
771     * @throws CertStoreException if an exception occurs
772     */
773    public synchronized Collection<X509CRL> engineGetCRLs(CRLSelector selector)
774            throws CertStoreException {
775        if (debug != null) {
776            debug.println("LDAPCertStore.engineGetCRLs() selector: "
777                + selector);
778        }
779        // Set up selector and collection to hold CRLs
780        if (selector == null) {
781            selector = new X509CRLSelector();
782        }
783        if (!(selector instanceof X509CRLSelector)) {
784            throw new CertStoreException("need X509CRLSelector to find CRLs");
785        }
786        X509CRLSelector xsel = (X509CRLSelector) selector;
787        HashSet<X509CRL> crls = new HashSet<>();
788
789        // Look in directory entry for issuer of cert we're checking.
790        Collection<Object> issuerNames;
791        X509Certificate certChecking = xsel.getCertificateChecking();
792        if (certChecking != null) {
793            issuerNames = new HashSet<>();
794            X500Principal issuer = certChecking.getIssuerX500Principal();
795            issuerNames.add(issuer.getName(X500Principal.RFC2253));
796        } else {
797            // But if we don't know which cert we're checking, try the directory
798            // entries of all acceptable CRL issuers
799            issuerNames = xsel.getIssuerNames();
800            if (issuerNames == null) {
801                throw new CertStoreException("need issuerNames or certChecking to "
802                    + "find CRLs");
803            }
804        }
805        for (Object nameObject : issuerNames) {
806            String issuerName;
807            if (nameObject instanceof byte[]) {
808                try {
809                    X500Principal issuer = new X500Principal((byte[])nameObject);
810                    issuerName = issuer.getName(X500Principal.RFC2253);
811                } catch (IllegalArgumentException e) {
812                    continue;
813                }
814            } else {
815                issuerName = (String)nameObject;
816            }
817            // If all we want is CA certs, try to get the (probably shorter) ARL
818            Collection<X509CRL> entryCRLs = Collections.emptySet();
819            if (certChecking == null || certChecking.getBasicConstraints() != -1) {
820                LDAPRequest request = new LDAPRequest(issuerName);
821                request.addRequestedAttribute(CROSS_CERT);
822                request.addRequestedAttribute(CA_CERT);
823                request.addRequestedAttribute(ARL);
824                if (prefetchCRLs) {
825                    request.addRequestedAttribute(CRL);
826                }
827                try {
828                    entryCRLs = getCRLs(request, ARL, xsel);
829                    if (entryCRLs.isEmpty()) {
830                        // no ARLs found. We assume that means that there are
831                        // no ARLs on this server at all and prefetch the CRLs.
832                        prefetchCRLs = true;
833                    } else {
834                        crls.addAll(entryCRLs);
835                    }
836                } catch (CertStoreException e) {
837                    if (debug != null) {
838                        debug.println("LDAPCertStore.engineGetCRLs non-fatal error "
839                            + "retrieving ARLs:" + e);
840                        e.printStackTrace();
841                    }
842                }
843            }
844            // Otherwise, get the CRL
845            // if certChecking is null, we don't know if we should look in ARL or CRL
846            // attribute, so check both for matching CRLs.
847            if (entryCRLs.isEmpty() || certChecking == null) {
848                LDAPRequest request = new LDAPRequest(issuerName);
849                request.addRequestedAttribute(CRL);
850                entryCRLs = getCRLs(request, CRL, xsel);
851                crls.addAll(entryCRLs);
852            }
853        }
854        return crls;
855    }
856
857    // converts an LDAP URI into LDAPCertStoreParameters
858    static LDAPCertStoreParameters getParameters(URI uri) {
859        String host = uri.getHost();
860        if (host == null) {
861            return new SunLDAPCertStoreParameters();
862        } else {
863            int port = uri.getPort();
864            return (port == -1
865                    ? new SunLDAPCertStoreParameters(host)
866                    : new SunLDAPCertStoreParameters(host, port));
867        }
868    }
869
870    /*
871     * Subclass of LDAPCertStoreParameters with overridden equals/hashCode
872     * methods. This is necessary because the parameters are used as
873     * keys in the LDAPCertStore cache.
874     */
875    private static class SunLDAPCertStoreParameters
876        extends LDAPCertStoreParameters {
877
878        private volatile int hashCode = 0;
879
880        SunLDAPCertStoreParameters(String serverName, int port) {
881            super(serverName, port);
882        }
883        SunLDAPCertStoreParameters(String serverName) {
884            super(serverName);
885        }
886        SunLDAPCertStoreParameters() {
887            super();
888        }
889        public boolean equals(Object obj) {
890            if (!(obj instanceof LDAPCertStoreParameters)) {
891                return false;
892            }
893            LDAPCertStoreParameters params = (LDAPCertStoreParameters) obj;
894            return (getPort() == params.getPort() &&
895                    getServerName().equalsIgnoreCase(params.getServerName()));
896        }
897        public int hashCode() {
898            if (hashCode == 0) {
899                int result = 17;
900                result = 37*result + getPort();
901                result = 37*result +
902                    getServerName().toLowerCase(Locale.ENGLISH).hashCode();
903                hashCode = result;
904            }
905            return hashCode;
906        }
907    }
908
909    /*
910     * This inner class wraps an existing X509CertSelector and adds
911     * additional criteria to match on when the certificate's subject is
912     * different than the LDAP Distinguished Name entry. The LDAPCertStore
913     * implementation uses the subject DN as the directory entry for
914     * looking up certificates. This can be problematic if the certificates
915     * that you want to fetch have a different subject DN than the entry
916     * where they are stored. You could set the selector's subject to the
917     * LDAP DN entry, but then the resulting match would fail to find the
918     * desired certificates because the subject DNs would not match. This
919     * class avoids that problem by introducing a certSubject which should
920     * be set to the certificate's subject DN when it is different than
921     * the LDAP DN.
922     */
923    static class LDAPCertSelector extends X509CertSelector {
924
925        private X500Principal certSubject;
926        private X509CertSelector selector;
927        private X500Principal subject;
928
929        /**
930         * Creates an LDAPCertSelector.
931         *
932         * @param selector the X509CertSelector to wrap
933         * @param certSubject the subject DN of the certificate that you want
934         *      to retrieve via LDAP
935         * @param ldapDN the LDAP DN where the certificate is stored
936         */
937        LDAPCertSelector(X509CertSelector selector, X500Principal certSubject,
938            String ldapDN) throws IOException {
939            this.selector = selector == null ? new X509CertSelector() : selector;
940            this.certSubject = certSubject;
941            this.subject = new X500Name(ldapDN).asX500Principal();
942        }
943
944        // we only override the get (accessor methods) since the set methods
945        // will not be invoked by the code that uses this LDAPCertSelector.
946        public X509Certificate getCertificate() {
947            return selector.getCertificate();
948        }
949        public BigInteger getSerialNumber() {
950            return selector.getSerialNumber();
951        }
952        public X500Principal getIssuer() {
953            return selector.getIssuer();
954        }
955        public String getIssuerAsString() {
956            return selector.getIssuerAsString();
957        }
958        public byte[] getIssuerAsBytes() throws IOException {
959            return selector.getIssuerAsBytes();
960        }
961        public X500Principal getSubject() {
962            // return the ldap DN
963            return subject;
964        }
965        public String getSubjectAsString() {
966            // return the ldap DN
967            return subject.getName();
968        }
969        public byte[] getSubjectAsBytes() throws IOException {
970            // return the encoded ldap DN
971            return subject.getEncoded();
972        }
973        public byte[] getSubjectKeyIdentifier() {
974            return selector.getSubjectKeyIdentifier();
975        }
976        public byte[] getAuthorityKeyIdentifier() {
977            return selector.getAuthorityKeyIdentifier();
978        }
979        public Date getCertificateValid() {
980            return selector.getCertificateValid();
981        }
982        public Date getPrivateKeyValid() {
983            return selector.getPrivateKeyValid();
984        }
985        public String getSubjectPublicKeyAlgID() {
986            return selector.getSubjectPublicKeyAlgID();
987        }
988        public PublicKey getSubjectPublicKey() {
989            return selector.getSubjectPublicKey();
990        }
991        public boolean[] getKeyUsage() {
992            return selector.getKeyUsage();
993        }
994        public Set<String> getExtendedKeyUsage() {
995            return selector.getExtendedKeyUsage();
996        }
997        public boolean getMatchAllSubjectAltNames() {
998            return selector.getMatchAllSubjectAltNames();
999        }
1000        public Collection<List<?>> getSubjectAlternativeNames() {
1001            return selector.getSubjectAlternativeNames();
1002        }
1003        public byte[] getNameConstraints() {
1004            return selector.getNameConstraints();
1005        }
1006        public int getBasicConstraints() {
1007            return selector.getBasicConstraints();
1008        }
1009        public Set<String> getPolicy() {
1010            return selector.getPolicy();
1011        }
1012        public Collection<List<?>> getPathToNames() {
1013            return selector.getPathToNames();
1014        }
1015
1016        public boolean match(Certificate cert) {
1017            // temporarily set the subject criterion to the certSubject
1018            // so that match will not reject the desired certificates
1019            selector.setSubject(certSubject);
1020            boolean match = selector.match(cert);
1021            selector.setSubject(subject);
1022            return match;
1023        }
1024    }
1025
1026    /**
1027     * This class has the same purpose as LDAPCertSelector except it is for
1028     * X.509 CRLs.
1029     */
1030    static class LDAPCRLSelector extends X509CRLSelector {
1031
1032        private X509CRLSelector selector;
1033        private Collection<X500Principal> certIssuers;
1034        private Collection<X500Principal> issuers;
1035        private HashSet<Object> issuerNames;
1036
1037        /**
1038         * Creates an LDAPCRLSelector.
1039         *
1040         * @param selector the X509CRLSelector to wrap
1041         * @param certIssuers the issuer DNs of the CRLs that you want
1042         *      to retrieve via LDAP
1043         * @param ldapDN the LDAP DN where the CRL is stored
1044         */
1045        LDAPCRLSelector(X509CRLSelector selector,
1046            Collection<X500Principal> certIssuers, String ldapDN)
1047            throws IOException {
1048            this.selector = selector == null ? new X509CRLSelector() : selector;
1049            this.certIssuers = certIssuers;
1050            issuerNames = new HashSet<>();
1051            issuerNames.add(ldapDN);
1052            issuers = new HashSet<>();
1053            issuers.add(new X500Name(ldapDN).asX500Principal());
1054        }
1055        // we only override the get (accessor methods) since the set methods
1056        // will not be invoked by the code that uses this LDAPCRLSelector.
1057        public Collection<X500Principal> getIssuers() {
1058            // return the ldap DN
1059            return Collections.unmodifiableCollection(issuers);
1060        }
1061        public Collection<Object> getIssuerNames() {
1062            // return the ldap DN
1063            return Collections.unmodifiableCollection(issuerNames);
1064        }
1065        public BigInteger getMinCRL() {
1066            return selector.getMinCRL();
1067        }
1068        public BigInteger getMaxCRL() {
1069            return selector.getMaxCRL();
1070        }
1071        public Date getDateAndTime() {
1072            return selector.getDateAndTime();
1073        }
1074        public X509Certificate getCertificateChecking() {
1075            return selector.getCertificateChecking();
1076        }
1077        public boolean match(CRL crl) {
1078            // temporarily set the issuer criterion to the certIssuers
1079            // so that match will not reject the desired CRL
1080            selector.setIssuers(certIssuers);
1081            boolean match = selector.match(crl);
1082            selector.setIssuers(issuers);
1083            return match;
1084        }
1085    }
1086}
1087