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 } |
|