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