1066 lines · 42.4 KB · Java
RawDownload
/* * Copyright (c) 2000, 2006, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation.  Oracle designates this * particular file as subject to the "Classpath" exception as provided * by Oracle in the LICENSE file that accompanied this code. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ package sun.security.provider.certpath.ldap; import java.io.ByteArrayInputStream;import java.io.IOException;import java.math.BigInteger;import java.net.URI;import java.util.*;import javax.naming.Context;import javax.naming.NamingEnumeration;import javax.naming.NamingException;import javax.naming.NameNotFoundException;import javax.naming.directory.Attribute;import javax.naming.directory.Attributes;import javax.naming.directory.BasicAttributes;import javax.naming.directory.DirContext;import javax.naming.directory.InitialDirContext; import java.security.*;import java.security.cert.Certificate;import java.security.cert.*;import javax.security.auth.x500.X500Principal; import sun.misc.HexDumpEncoder;import sun.security.provider.certpath.X509CertificatePair;import sun.security.util.Cache;import sun.security.util.Debug;import sun.security.x509.X500Name;import sun.security.action.GetPropertyAction; /** * A <code>CertStore</code> that retrieves <code>Certificates</code> and * <code>CRL</code>s from an LDAP directory, using the PKIX LDAP V2 Schema * (RFC 2587): * <a href="http://www.ietf.org/rfc/rfc2587.txt"> * http://www.ietf.org/rfc/rfc2587.txt</a>. * <p> * Before calling the {@link #engineGetCertificates engineGetCertificates} or * {@link #engineGetCRLs engineGetCRLs} methods, the * {@link #LDAPCertStore(CertStoreParameters) * LDAPCertStore(CertStoreParameters)} constructor is called to create the * <code>CertStore</code> and establish the DNS name and port of the LDAP * server from which <code>Certificate</code>s and <code>CRL</code>s will be * retrieved. * <p> * <b>Concurrent Access</b> * <p> * As described in the javadoc for <code>CertStoreSpi</code>, the * <code>engineGetCertificates</code> and <code>engineGetCRLs</code> methods * must be thread-safe. That is, multiple threads may concurrently * invoke these methods on a single <code>LDAPCertStore</code> object * (or more than one) with no ill effects. This allows a * <code>CertPathBuilder</code> to search for a CRL while simultaneously * searching for further certificates, for instance. * <p> * This is achieved by adding the <code>synchronized</code> keyword to the * <code>engineGetCertificates</code> and <code>engineGetCRLs</code> methods. * <p> * This classes uses caching and requests multiple attributes at once to * minimize LDAP round trips. The cache is associated with the CertStore * instance. It uses soft references to hold the values to minimize impact * on footprint and currently has a maximum size of 750 attributes and a * 30 second default lifetime. * <p> * We always request CA certificates, cross certificate pairs, and ARLs in * a single LDAP request when any one of them is needed. The reason is that * we typically need all of them anyway and requesting them in one go can * reduce the number of requests to a third. Even if we don't need them, * these attributes are typically small enough not to cause a noticeable * overhead. In addition, when the prefetchCRLs flag is true, we also request * the full CRLs. It is currently false initially but set to true once any * request for an ARL to the server returns an null value. The reason is * that CRLs could be rather large but are rarely used. This implementation * should improve performance in most cases. * * @see java.security.cert.CertStore * * @since       1.4 * @author      Steve Hanna * @author      Andreas Sterbenz */public class LDAPCertStore extends CertStoreSpi {     private static final Debug debug = Debug.getInstance("certpath");     private final static boolean DEBUG = false;     /**     * LDAP attribute identifiers.     */    private static final String USER_CERT = "userCertificate;binary";    private static final String CA_CERT = "cACertificate;binary";    private static final String CROSS_CERT = "crossCertificatePair;binary";    private static final String CRL = "certificateRevocationList;binary";    private static final String ARL = "authorityRevocationList;binary";    private static final String DELTA_CRL = "deltaRevocationList;binary";     // Constants for various empty values    private final static String[] STRING0 = new String[0];     private final static byte[][] BB0 = new byte[0][];     private final static Attributes EMPTY_ATTRIBUTES = new BasicAttributes();     // cache related constants    private final static int DEFAULT_CACHE_SIZE = 750;    private final static int DEFAULT_CACHE_LIFETIME = 30;     private final static int LIFETIME;     private final static String PROP_LIFETIME =                            "sun.security.certpath.ldap.cache.lifetime";     static {        String s = AccessController.doPrivileged(                                new GetPropertyAction(PROP_LIFETIME));        if (s != null) {            LIFETIME = Integer.parseInt(s); // throws NumberFormatException        } else {            LIFETIME = DEFAULT_CACHE_LIFETIME;        }    }     /**     * The CertificateFactory used to decode certificates from     * their binary stored form.     */    private CertificateFactory cf;    /**     * The JNDI directory context.     */    private DirContext ctx;     /**     * Flag indicating whether we should prefetch CRLs.     */    private boolean prefetchCRLs = false;     private final Cache valueCache;     private int cacheHits = 0;    private int cacheMisses = 0;    private int requests = 0;     /**     * Creates a <code>CertStore</code> with the specified parameters.     * For this class, the parameters object must be an instance of     * <code>LDAPCertStoreParameters</code>.     *     * @param params the algorithm parameters     * @exception InvalidAlgorithmParameterException if params is not an     *   instance of <code>LDAPCertStoreParameters</code>     */    public LDAPCertStore(CertStoreParameters params)            throws InvalidAlgorithmParameterException {        super(params);        if (!(params instanceof LDAPCertStoreParameters))          throw new InvalidAlgorithmParameterException(            "parameters must be LDAPCertStoreParameters");         LDAPCertStoreParameters lparams = (LDAPCertStoreParameters) params;         // Create InitialDirContext needed to communicate with the server        createInitialDirContext(lparams.getServerName(), lparams.getPort());         // Create CertificateFactory for use later on        try {            cf = CertificateFactory.getInstance("X.509");        } catch (CertificateException e) {            throw new InvalidAlgorithmParameterException(                "unable to create CertificateFactory for X.509");        }        if (LIFETIME == 0) {            valueCache = Cache.newNullCache();        } else if (LIFETIME < 0) {            valueCache = Cache.newSoftMemoryCache(DEFAULT_CACHE_SIZE);        } else {            valueCache = Cache.newSoftMemoryCache(DEFAULT_CACHE_SIZE, LIFETIME);        }    }     /**     * Returns an LDAP CertStore. This method consults a cache of     * CertStores (shared per JVM) using the LDAP server/port as a key.     */    private static final Cache certStoreCache = Cache.newSoftMemoryCache(185);    static synchronized CertStore getInstance(LDAPCertStoreParameters params)        throws NoSuchAlgorithmException, InvalidAlgorithmParameterException {        CertStore lcs = (CertStore) certStoreCache.get(params);        if (lcs == null) {            lcs = CertStore.getInstance("LDAP", params);            certStoreCache.put(params, lcs);        } else {            if (debug != null) {                debug.println("LDAPCertStore.getInstance: cache hit");            }        }        return lcs;    }     /**     * Create InitialDirContext.     *     * @param server Server DNS name hosting LDAP service     * @param port   Port at which server listens for requests     * @throws InvalidAlgorithmParameterException if creation fails     */    private void createInitialDirContext(String server, int port)            throws InvalidAlgorithmParameterException {        String url = "ldap://" + server + ":" + port;        Hashtable<String,Object> env = new Hashtable<String,Object>();        env.put(Context.INITIAL_CONTEXT_FACTORY,                "com.sun.jndi.ldap.LdapCtxFactory");        env.put(Context.PROVIDER_URL, url);        try {            ctx = new InitialDirContext(env);            /*             * By default, follow referrals unless application has             * overridden property in an application resource file.             */            Hashtable<?,?> currentEnv = ctx.getEnvironment();            if (currentEnv.get(Context.REFERRAL) == null) {                ctx.addToEnvironment(Context.REFERRAL, "follow");            }        } catch (NamingException e) {            if (debug != null) {                debug.println("LDAPCertStore.engineInit about to throw "                    + "InvalidAlgorithmParameterException");                e.printStackTrace();            }            Exception ee = new InvalidAlgorithmParameterException                ("unable to create InitialDirContext using supplied parameters");            ee.initCause(e);            throw (InvalidAlgorithmParameterException)ee;        }    }     /**     * Private class encapsulating the actual LDAP operations and cache     * handling. Use:     *     *   LDAPRequest request = new LDAPRequest(dn);     *   request.addRequestedAttribute(CROSS_CERT);     *   request.addRequestedAttribute(CA_CERT);     *   byte[][] crossValues = request.getValues(CROSS_CERT);     *   byte[][] caValues = request.getValues(CA_CERT);     *     * At most one LDAP request is sent for each instance created. If all     * getValues() calls can be satisfied from the cache, no request     * is sent at all. If a request is sent, all requested attributes     * are always added to the cache irrespective of whether the getValues()     * method is called.     */    private class LDAPRequest {         private final String name;        private Map<String, byte[][]> valueMap;        private final List<String> requestedAttributes;         LDAPRequest(String name) {            this.name = name;            requestedAttributes = new ArrayList<String>(5);        }         String getName() {            return name;        }         void addRequestedAttribute(String attrId) {            if (valueMap != null) {                throw new IllegalStateException("Request already sent");            }            requestedAttributes.add(attrId);        }         /**         * Gets one or more binary values from an attribute.         *         * @param name          the location holding the attribute         * @param attrId                the attribute identifier         * @return                      an array of binary values (byte arrays)         * @throws NamingException      if a naming exception occurs         */        byte[][] getValues(String attrId) throws NamingException {            if (DEBUG && ((cacheHits + cacheMisses) % 50 == 0)) {                System.out.println("Cache hits: " + cacheHits + "; misses: "                        + cacheMisses);            }            String cacheKey = name + "|" + attrId;            byte[][] values = (byte[][])valueCache.get(cacheKey);            if (values != null) {                cacheHits++;                return values;            }            cacheMisses++;            Map<String, byte[][]> attrs = getValueMap();            values = attrs.get(attrId);            return values;        }         /**         * Get a map containing the values for this request. The first time         * this method is called on an object, the LDAP request is sent,         * the results parsed and added to a private map and also to the         * cache of this LDAPCertStore. Subsequent calls return the private         * map immediately.         *         * The map contains an entry for each requested attribute. The         * attribute name is the key, values are byte[][]. If there are no         * values for that attribute, values are byte[0][].         *         * @return                      the value Map         * @throws NamingException      if a naming exception occurs         */        private Map<String, byte[][]> getValueMap() throws NamingException {            if (valueMap != null) {                return valueMap;            }            if (DEBUG) {                System.out.println("Request: " + name + ":" + requestedAttributes);                requests++;                if (requests % 5 == 0) {                    System.out.println("LDAP requests: " + requests);                }            }            valueMap = new HashMap<String, byte[][]>(8);            String[] attrIds = requestedAttributes.toArray(STRING0);            Attributes attrs;            try {                attrs = ctx.getAttributes(name, attrIds);            } catch (NameNotFoundException e) {                // name does not exist on this LDAP server                // treat same as not attributes found                attrs = EMPTY_ATTRIBUTES;            }            for (String attrId : requestedAttributes) {                Attribute attr = attrs.get(attrId);                byte[][] values = getAttributeValues(attr);                cacheAttribute(attrId, values);                valueMap.put(attrId, values);            }            return valueMap;        }         /**         * Add the values to the cache.         */        private void cacheAttribute(String attrId, byte[][] values) {            String cacheKey = name + "|" + attrId;            valueCache.put(cacheKey, values);        }         /**         * Get the values for the given attribute. If the attribute is null         * or does not contain any values, a zero length byte array is         * returned. NOTE that it is assumed that all values are byte arrays.         */        private byte[][] getAttributeValues(Attribute attr)                throws NamingException {            byte[][] values;            if (attr == null) {                values = BB0;            } else {                values = new byte[attr.size()][];                int i = 0;                NamingEnumeration<?> enum_ = attr.getAll();                while (enum_.hasMore()) {                    Object obj = enum_.next();                    if (debug != null) {                        if (obj instanceof String) {                            debug.println("LDAPCertStore.getAttrValues() "                                + "enum.next is a string!: " + obj);                        }                    }                    byte[] value = (byte[])obj;                    values[i++] = value;                }            }            return values;        }     }     /*     * Gets certificates from an attribute id and location in the LDAP     * directory. Returns a Collection containing only the Certificates that     * match the specified CertSelector.     *     * @param name the location holding the attribute     * @param id the attribute identifier     * @param sel a CertSelector that the Certificates must match     * @return a Collection of Certificates found     * @throws CertStoreException       if an exception occurs     */    private Collection<X509Certificate> getCertificates(LDAPRequest request,        String id, X509CertSelector sel) throws CertStoreException {         /* fetch encoded certs from storage */        byte[][] encodedCert;        try {            encodedCert = request.getValues(id);        } catch (NamingException namingEx) {            throw new CertStoreException(namingEx);        }         int n = encodedCert.length;        if (n == 0) {            return Collections.<X509Certificate>emptySet();        }         List<X509Certificate> certs = new ArrayList<X509Certificate>(n);        /* decode certs and check if they satisfy selector */        for (int i = 0; i < n; i++) {            ByteArrayInputStream bais = new ByteArrayInputStream(encodedCert[i]);            try {                Certificate cert = cf.generateCertificate(bais);                if (sel.match(cert)) {                  certs.add((X509Certificate)cert);                }            } catch (CertificateException e) {                if (debug != null) {                    debug.println("LDAPCertStore.getCertificates() encountered "                        + "exception while parsing cert, skipping the bad data: ");                    HexDumpEncoder encoder = new HexDumpEncoder();                    debug.println(                        "[ " + encoder.encodeBuffer(encodedCert[i]) + " ]");                }            }        }         return certs;    }     /*     * Gets certificate pairs from an attribute id and location in the LDAP     * directory.     *     * @param name the location holding the attribute     * @param id the attribute identifier     * @return a Collection of X509CertificatePairs found     * @throws CertStoreException       if an exception occurs     */    private Collection<X509CertificatePair> getCertPairs(        LDAPRequest request, String id) throws CertStoreException {         /* fetch the encoded cert pairs from storage */        byte[][] encodedCertPair;        try {            encodedCertPair = request.getValues(id);        } catch (NamingException namingEx) {            throw new CertStoreException(namingEx);        }         int n = encodedCertPair.length;        if (n == 0) {            return Collections.<X509CertificatePair>emptySet();        }         List<X509CertificatePair> certPairs =                                new ArrayList<X509CertificatePair>(n);        /* decode each cert pair and add it to the Collection */        for (int i = 0; i < n; i++) {            try {                X509CertificatePair certPair =                    X509CertificatePair.generateCertificatePair(encodedCertPair[i]);                certPairs.add(certPair);            } catch (CertificateException e) {                if (debug != null) {                    debug.println(                        "LDAPCertStore.getCertPairs() encountered exception "                        + "while parsing cert, skipping the bad data: ");                    HexDumpEncoder encoder = new HexDumpEncoder();                    debug.println(                        "[ " + encoder.encodeBuffer(encodedCertPair[i]) + " ]");                }            }

Lines 1–500 of 1066.