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