/* * Copyright (c) 2000, 2015, 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.net.URI; import java.security.*; import java.security.cert.*; import java.util.*; import sun.security.util.Cache; import sun.security.util.Debug; /** * 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 final class LDAPCertStore extends CertStoreSpi { private static final Debug debug = Debug.getInstance("certpath"); private String ldapDN; private LDAPCertStoreImpl impl; public LDAPCertStore(CertStoreParameters params) throws InvalidAlgorithmParameterException { super(params); String serverName; int port; String dn = null; if (params == null) { throw new InvalidAlgorithmParameterException( "Parameters required for LDAP certstore"); } if (params instanceof LDAPCertStoreParameters) { LDAPCertStoreParameters p = (LDAPCertStoreParameters) params; serverName = p.getServerName(); port = p.getPort(); } else if (params instanceof URICertStoreParameters) { URICertStoreParameters p = (URICertStoreParameters) params; URI u = p.getURI(); if (!u.getScheme().equalsIgnoreCase("ldap")) { throw new InvalidAlgorithmParameterException( "Unsupported scheme '" + u.getScheme() + "', only LDAP URIs are supported " + "for LDAP certstore"); } // Use the same default values as in LDAPCertStoreParameters // if unspecified in URI serverName = u.getHost(); if (serverName == null) { serverName = "localhost"; } port = u.getPort(); if (port == -1) { port = 389; } dn = u.getPath(); if (dn != null && dn.charAt(0) == '/') { dn = dn.substring(1); } } else { throw new InvalidAlgorithmParameterException( "Parameters must be either LDAPCertStoreParameters or " + "URICertStoreParameters, but instance of " + params.getClass().getName() + " passed"); } Key k = new Key(serverName, port); LDAPCertStoreImpl lci = certStoreCache.get(k); if (lci == null) { this.impl = new LDAPCertStoreImpl(serverName, port); certStoreCache.put(k, impl); } else { this.impl = lci; if (debug != null) { debug.println("LDAPCertStore.getInstance: cache hit"); } } this.ldapDN = dn; } private static class Key { volatile int hashCode; String serverName; int port; Key(String serverName, int port) { this.serverName = serverName; this.port = port; } @Override public boolean equals(Object obj) { if (!(obj instanceof Key)) { return false; } Key key = (Key) obj; return (port == key.port && serverName.equalsIgnoreCase(key.serverName)); } @Override public int hashCode() { if (hashCode == 0) { int result = 17; result = 37*result + port; result = 37*result + serverName.toLowerCase(Locale.ENGLISH).hashCode(); hashCode = result; } return hashCode; } } /** * Returns an LDAPCertStoreImpl object. This method consults a cache of * LDAPCertStoreImpl objects (shared per JVM) using the corresponding * LDAP server name and port info as a key. */ private static final Cache<Key, LDAPCertStoreImpl> certStoreCache = Cache.newSoftMemoryCache(185); // Exist solely for regression test for ensuring that caching is done static synchronized LDAPCertStoreImpl getInstance(LDAPCertStoreParameters params) throws NoSuchAlgorithmException, InvalidAlgorithmParameterException { String serverName = params.getServerName(); int port = params.getPort(); Key k = new Key(serverName, port); LDAPCertStoreImpl lci = certStoreCache.get(k); if (lci == null) { lci = new LDAPCertStoreImpl(serverName, port); certStoreCache.put(k, lci); } else { if (debug != null) { debug.println("LDAPCertStore.getInstance: cache hit"); } } return lci; } /** * Returns a <code>Collection</code> of <code>Certificate</code>s that * match the specified selector. If no <code>Certificate</code>s * match the selector, an empty <code>Collection</code> will be returned. * <p> * It is not practical to search every entry in the LDAP database for * matching <code>Certificate</code>s. Instead, the <code>CertSelector</code> * is examined in order to determine where matching <code>Certificate</code>s * are likely to be found (according to the PKIX LDAPv2 schema, RFC 2587). * If the subject is specified, its directory entry is searched. If the * issuer is specified, its directory entry is searched. If neither the * subject nor the issuer are specified (or the selector is not an * <code>X509CertSelector</code>), a <code>CertStoreException</code> is * thrown. * * @param selector a <code>CertSelector</code> used to select which * <code>Certificate</code>s should be returned. * @return a <code>Collection</code> of <code>Certificate</code>s that * match the specified selector * @throws CertStoreException if an exception occurs */ @Override public synchronized Collection<X509Certificate> engineGetCertificates (CertSelector selector) throws CertStoreException { if (debug != null) { debug.println("LDAPCertStore.engineGetCertificates() selector: " + String.valueOf(selector)); } if (selector == null) { selector = new X509CertSelector(); } else if (!(selector instanceof X509CertSelector)) { throw new CertStoreException("Need X509CertSelector to find certs, " + "but instance of " + selector.getClass().getName() + " passed"); } return impl.getCertificates((X509CertSelector) selector, ldapDN); } /** * Returns a <code>Collection</code> of <code>CRL</code>s that * match the specified selector. If no <code>CRL</code>s * match the selector, an empty <code>Collection</code> will be returned. * <p> * It is not practical to search every entry in the LDAP database for * matching <code>CRL</code>s. Instead, the <code>CRLSelector</code> * is examined in order to determine where matching <code>CRL</code>s * are likely to be found (according to the PKIX LDAPv2 schema, RFC 2587). * If issuerNames or certChecking are specified, the issuer's directory * entry is searched. If neither issuerNames or certChecking are specified * (or the selector is not an <code>X509CRLSelector</code>), a * <code>CertStoreException</code> is thrown. * * @param selector A <code>CRLSelector</code> used to select which * <code>CRL</code>s should be returned. Specify <code>null</code> * to return all <code>CRL</code>s. * @return A <code>Collection</code> of <code>CRL</code>s that * match the specified selector * @throws CertStoreException if an exception occurs */ @Override public synchronized Collection<X509CRL> engineGetCRLs(CRLSelector selector) throws CertStoreException { if (debug != null) { debug.println("LDAPCertStore.engineGetCRLs() selector: " + selector); } // Set up selector and collection to hold CRLs if (selector == null) { selector = new X509CRLSelector(); } else if (!(selector instanceof X509CRLSelector)) { throw new CertStoreException("Need X509CRLSelector to find CRLs, " + "but instance of " + selector.getClass().getName() + " passed"); } return impl.getCRLs((X509CRLSelector) selector, ldapDN); } }