blob: f7b567bef397c0d83e7cebe2da37f9eecf6bb555 [file] [log] [blame]
J. Duke319a3b92007-12-01 00:00:00 +00001/*
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
26package sun.security.provider.certpath;
27
28import java.io.ByteArrayInputStream;
29import java.io.IOException;
30import java.math.BigInteger;
31import java.net.URI;
32import java.util.*;
33import javax.naming.Context;
34import javax.naming.NamingEnumeration;
35import javax.naming.NamingException;
36import javax.naming.NameNotFoundException;
37import javax.naming.directory.Attribute;
38import javax.naming.directory.Attributes;
39import javax.naming.directory.BasicAttributes;
40import javax.naming.directory.DirContext;
41import javax.naming.directory.InitialDirContext;
42
43import java.security.*;
44import java.security.cert.Certificate;
45import java.security.cert.*;
46import javax.security.auth.x500.X500Principal;
47
48import sun.misc.HexDumpEncoder;
49import sun.security.util.Cache;
50import sun.security.util.Debug;
51import sun.security.x509.X500Name;
52import 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 */
105public 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}