1/*
2 * Copyright (c) 2000, 2015, Oracle and/or its affiliates. All rights reserved.
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4 *
5 * This code is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License version 2 only, as
7 * published by the Free Software Foundation.  Oracle designates this
8 * particular file as subject to the "Classpath" exception as provided
9 * by Oracle in the LICENSE file that accompanied this code.
10 *
11 * This code is distributed in the hope that it will be useful, but WITHOUT
12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
14 * version 2 for more details (a copy is included in the LICENSE file that
15 * accompanied this code).
16 *
17 * You should have received a copy of the GNU General Public License version
18 * 2 along with this work; if not, write to the Free Software Foundation,
19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20 *
21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22 * or visit www.oracle.com if you need additional information or have any
23 * questions.
24 */
25
26package sun.security.provider.certpath.ldap;
27
28import java.net.URI;
29import java.security.*;
30import java.security.cert.*;
31import java.util.*;
32import sun.security.util.Cache;
33import sun.security.util.Debug;
34
35/**
36 * A <code>CertStore</code> that retrieves <code>Certificates</code> and
37 * <code>CRL</code>s from an LDAP directory, using the PKIX LDAP V2 Schema
38 * (RFC 2587):
39 * <a href="http://www.ietf.org/rfc/rfc2587.txt">
40 * http://www.ietf.org/rfc/rfc2587.txt</a>.
41 * <p>
42 * Before calling the {@link #engineGetCertificates engineGetCertificates} or
43 * {@link #engineGetCRLs engineGetCRLs} methods, the
44 * {@link #LDAPCertStore(CertStoreParameters)
45 * LDAPCertStore(CertStoreParameters)} constructor is called to create the
46 * <code>CertStore</code> and establish the DNS name and port of the LDAP
47 * server from which <code>Certificate</code>s and <code>CRL</code>s will be
48 * retrieved.
49 * <p>
50 * <b>Concurrent Access</b>
51 * <p>
52 * As described in the javadoc for <code>CertStoreSpi</code>, the
53 * <code>engineGetCertificates</code> and <code>engineGetCRLs</code> methods
54 * must be thread-safe. That is, multiple threads may concurrently
55 * invoke these methods on a single <code>LDAPCertStore</code> object
56 * (or more than one) with no ill effects. This allows a
57 * <code>CertPathBuilder</code> to search for a CRL while simultaneously
58 * searching for further certificates, for instance.
59 * <p>
60 * This is achieved by adding the <code>synchronized</code> keyword to the
61 * <code>engineGetCertificates</code> and <code>engineGetCRLs</code> methods.
62 * <p>
63 * This classes uses caching and requests multiple attributes at once to
64 * minimize LDAP round trips. The cache is associated with the CertStore
65 * instance. It uses soft references to hold the values to minimize impact
66 * on footprint and currently has a maximum size of 750 attributes and a
67 * 30 second default lifetime.
68 * <p>
69 * We always request CA certificates, cross certificate pairs, and ARLs in
70 * a single LDAP request when any one of them is needed. The reason is that
71 * we typically need all of them anyway and requesting them in one go can
72 * reduce the number of requests to a third. Even if we don't need them,
73 * these attributes are typically small enough not to cause a noticeable
74 * overhead. In addition, when the prefetchCRLs flag is true, we also request
75 * the full CRLs. It is currently false initially but set to true once any
76 * request for an ARL to the server returns an null value. The reason is
77 * that CRLs could be rather large but are rarely used. This implementation
78 * should improve performance in most cases.
79 *
80 * @see java.security.cert.CertStore
81 *
82 * @since       1.4
83 * @author      Steve Hanna
84 * @author      Andreas Sterbenz
85 */
86public final class LDAPCertStore extends CertStoreSpi {
87
88    private static final Debug debug = Debug.getInstance("certpath");
89
90    private String ldapDN;
91
92    private LDAPCertStoreImpl impl;
93
94    public LDAPCertStore(CertStoreParameters params)
95        throws InvalidAlgorithmParameterException {
96        super(params);
97
98        String serverName;
99        int port;
100        String dn = null;
101        if (params == null) {
102            throw new InvalidAlgorithmParameterException(
103                    "Parameters required for LDAP certstore");
104        }
105        if (params instanceof LDAPCertStoreParameters) {
106            LDAPCertStoreParameters p = (LDAPCertStoreParameters) params;
107            serverName = p.getServerName();
108            port = p.getPort();
109        } else if (params instanceof URICertStoreParameters) {
110            URICertStoreParameters p = (URICertStoreParameters) params;
111            URI u = p.getURI();
112            if (!u.getScheme().equalsIgnoreCase("ldap")) {
113                throw new InvalidAlgorithmParameterException(
114                        "Unsupported scheme '" + u.getScheme()
115                                + "', only LDAP URIs are supported "
116                                + "for LDAP certstore");
117            }
118            // Use the same default values as in LDAPCertStoreParameters
119            // if unspecified in URI
120            serverName = u.getHost();
121            if (serverName == null) {
122                serverName = "localhost";
123            }
124            port = u.getPort();
125            if (port == -1) {
126                port = 389;
127            }
128            dn = u.getPath();
129            if (dn != null && dn.charAt(0) == '/') {
130                dn = dn.substring(1);
131            }
132        } else {
133            throw new InvalidAlgorithmParameterException(
134                "Parameters must be either LDAPCertStoreParameters or "
135                        + "URICertStoreParameters, but instance of "
136                        + params.getClass().getName() + " passed");
137        }
138
139        Key k = new Key(serverName, port);
140        LDAPCertStoreImpl lci = certStoreCache.get(k);
141        if (lci == null) {
142            this.impl = new LDAPCertStoreImpl(serverName, port);
143            certStoreCache.put(k, impl);
144        } else {
145            this.impl = lci;
146            if (debug != null) {
147                debug.println("LDAPCertStore.getInstance: cache hit");
148            }
149        }
150        this.ldapDN = dn;
151    }
152
153    private static class Key {
154        volatile int hashCode;
155
156        String serverName;
157        int port;
158
159        Key(String serverName, int port) {
160            this.serverName = serverName;
161            this.port = port;
162        }
163
164        @Override
165        public boolean equals(Object obj) {
166            if (!(obj instanceof Key)) {
167                return false;
168            }
169            Key key = (Key) obj;
170            return (port == key.port &&
171                serverName.equalsIgnoreCase(key.serverName));
172        }
173
174        @Override
175        public int hashCode() {
176            if (hashCode == 0) {
177                int result = 17;
178                result = 37*result + port;
179                result = 37*result +
180                    serverName.toLowerCase(Locale.ENGLISH).hashCode();
181                hashCode = result;
182            }
183            return hashCode;
184        }
185    }
186
187    /**
188     * Returns an LDAPCertStoreImpl object. This method consults a cache of
189     * LDAPCertStoreImpl objects (shared per JVM) using the corresponding
190     * LDAP server name and port info as a key.
191     */
192    private static final Cache<Key, LDAPCertStoreImpl>
193        certStoreCache = Cache.newSoftMemoryCache(185);
194
195    // Exist solely for regression test for ensuring that caching is done
196    static synchronized LDAPCertStoreImpl getInstance(LDAPCertStoreParameters params)
197        throws NoSuchAlgorithmException, InvalidAlgorithmParameterException {
198        String serverName = params.getServerName();
199        int port = params.getPort();
200        Key k = new Key(serverName, port);
201        LDAPCertStoreImpl lci = certStoreCache.get(k);
202        if (lci == null) {
203            lci = new LDAPCertStoreImpl(serverName, port);
204            certStoreCache.put(k, lci);
205        } else {
206            if (debug != null) {
207                debug.println("LDAPCertStore.getInstance: cache hit");
208            }
209        }
210        return lci;
211    }
212
213    /**
214     * Returns a <code>Collection</code> of <code>Certificate</code>s that
215     * match the specified selector. If no <code>Certificate</code>s
216     * match the selector, an empty <code>Collection</code> will be returned.
217     * <p>
218     * It is not practical to search every entry in the LDAP database for
219     * matching <code>Certificate</code>s. Instead, the <code>CertSelector</code>
220     * is examined in order to determine where matching <code>Certificate</code>s
221     * are likely to be found (according to the PKIX LDAPv2 schema, RFC 2587).
222     * If the subject is specified, its directory entry is searched. If the
223     * issuer is specified, its directory entry is searched. If neither the
224     * subject nor the issuer are specified (or the selector is not an
225     * <code>X509CertSelector</code>), a <code>CertStoreException</code> is
226     * thrown.
227     *
228     * @param selector a <code>CertSelector</code> used to select which
229     *  <code>Certificate</code>s should be returned.
230     * @return a <code>Collection</code> of <code>Certificate</code>s that
231     *         match the specified selector
232     * @throws CertStoreException if an exception occurs
233     */
234    @Override
235    public synchronized Collection<X509Certificate> engineGetCertificates
236            (CertSelector selector) throws CertStoreException {
237        if (debug != null) {
238            debug.println("LDAPCertStore.engineGetCertificates() selector: "
239                + String.valueOf(selector));
240        }
241        if (selector == null) {
242            selector = new X509CertSelector();
243        } else if (!(selector instanceof X509CertSelector)) {
244            throw new CertStoreException("Need X509CertSelector to find certs, "
245                    + "but instance of " + selector.getClass().getName()
246                    + " passed");
247        }
248        return impl.getCertificates((X509CertSelector) selector, ldapDN);
249    }
250
251    /**
252     * Returns a <code>Collection</code> of <code>CRL</code>s that
253     * match the specified selector. If no <code>CRL</code>s
254     * match the selector, an empty <code>Collection</code> will be returned.
255     * <p>
256     * It is not practical to search every entry in the LDAP database for
257     * matching <code>CRL</code>s. Instead, the <code>CRLSelector</code>
258     * is examined in order to determine where matching <code>CRL</code>s
259     * are likely to be found (according to the PKIX LDAPv2 schema, RFC 2587).
260     * If issuerNames or certChecking are specified, the issuer's directory
261     * entry is searched. If neither issuerNames or certChecking are specified
262     * (or the selector is not an <code>X509CRLSelector</code>), a
263     * <code>CertStoreException</code> is thrown.
264     *
265     * @param selector A <code>CRLSelector</code> used to select which
266     *  <code>CRL</code>s should be returned. Specify <code>null</code>
267     *  to return all <code>CRL</code>s.
268     * @return A <code>Collection</code> of <code>CRL</code>s that
269     *         match the specified selector
270     * @throws CertStoreException if an exception occurs
271     */
272    @Override
273    public synchronized Collection<X509CRL> engineGetCRLs(CRLSelector selector)
274            throws CertStoreException {
275        if (debug != null) {
276            debug.println("LDAPCertStore.engineGetCRLs() selector: "
277                + selector);
278        }
279        // Set up selector and collection to hold CRLs
280        if (selector == null) {
281            selector = new X509CRLSelector();
282        } else if (!(selector instanceof X509CRLSelector)) {
283            throw new CertStoreException("Need X509CRLSelector to find CRLs, "
284                    + "but instance of " + selector.getClass().getName()
285                    + " passed");
286        }
287        return impl.getCRLs((X509CRLSelector) selector, ldapDN);
288    }
289}
290