Home | History | Annotate | Download | only in ldap
      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 
     26 package sun.security.provider.certpath.ldap;
     27 
     28 import java.io.ByteArrayInputStream;
     29 import java.io.IOException;
     30 import java.math.BigInteger;
     31 import java.net.URI;
     32 import java.util.*;
     33 import javax.naming.Context;
     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 import javax.naming.directory.DirContext;
     41 import javax.naming.directory.InitialDirContext;
     42 
     43 import java.security.*;
     44 import java.security.cert.Certificate;
     45 import java.security.cert.*;
     46 import javax.security.auth.x500.X500Principal;
     47 
     48 import sun.misc.HexDumpEncoder;
     49 import sun.security.provider.certpath.X509CertificatePair;
     50 import sun.security.util.Cache;
     51 import sun.security.util.Debug;
     52 import sun.security.x509.X500Name;
     53 import sun.security.action.GetBooleanAction;
     54 import 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  */
    107 public 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