blob: 3c0220bbcf94e1c5e575583515dcd6bb55ed3d7f [file] [log] [blame]
J. Duke319a3b92007-12-01 00:00:00 +00001/*
2 * Copyright 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.InputStream;
29import java.io.IOException;
30import java.net.HttpURLConnection;
31import java.net.URI;
32import java.net.URLConnection;
33import java.security.InvalidAlgorithmParameterException;
34import java.security.NoSuchAlgorithmException;
35import java.security.Provider;
36import java.security.cert.CertificateException;
37import java.security.cert.CertificateFactory;
38import java.security.cert.CertSelector;
39import java.security.cert.CertStore;
40import java.security.cert.CertStoreException;
41import java.security.cert.CertStoreParameters;
42import java.security.cert.CertStoreSpi;
43import java.security.cert.CRLException;
44import java.security.cert.CRLSelector;
45import java.security.cert.X509Certificate;
46import java.security.cert.X509CertSelector;
47import java.security.cert.X509CRL;
48import java.security.cert.X509CRLSelector;
49import java.util.ArrayList;
50import java.util.Collection;
51import java.util.Collections;
52import java.util.List;
53import sun.security.x509.AccessDescription;
54import sun.security.x509.GeneralNameInterface;
55import sun.security.x509.URIName;
56import sun.security.util.Cache;
57import sun.security.util.Debug;
58
59/**
60 * A <code>CertStore</code> that retrieves <code>Certificates</code> or
61 * <code>CRL</code>s from a URI, for example, as specified in an X.509
62 * AuthorityInformationAccess or CRLDistributionPoint extension.
63 * <p>
64 * For CRLs, this implementation retrieves a single DER encoded CRL per URI.
65 * For Certificates, this implementation retrieves a single DER encoded CRL or
66 * a collection of Certificates encoded as a PKCS#7 "certs-only" CMS message.
67 * <p>
68 * This <code>CertStore</code> also implements Certificate/CRL caching.
69 * Currently, the cache is shared between all applications in the VM and uses a
70 * hardcoded policy. The cache has a maximum size of 185 entries, which are held
71 * by SoftReferences. A request will be satisfied from the cache if we last
72 * checked for an update within CHECK_INTERVAL (last 30 seconds). Otherwise,
73 * we open an URLConnection to download the Certificate(s)/CRL using an
74 * If-Modified-Since request (HTTP) if possible. Note that both positive and
75 * negative responses are cached, i.e. if we are unable to open the connection
76 * or the Certificate(s)/CRL cannot be parsed, we remember this result and
77 * additional calls during the CHECK_INTERVAL period do not try to open another
78 * connection.
79 * <p>
80 * The URICertStore is not currently a standard CertStore type. We should
81 * consider adding a standard "URI" CertStore type.
82 *
83 * @author Andreas Sterbenz
84 * @author Sean Mullan
85 * @since 7.0
86 */
87class URICertStore extends CertStoreSpi {
88
89 private static final Debug debug = Debug.getInstance("certpath");
90
91 // interval between checks for update of cached Certificates/CRLs
92 // (30 seconds)
93 private final static int CHECK_INTERVAL = 30 * 1000;
94
95 // size of the cache (see Cache class for sizing recommendations)
96 private final static int CACHE_SIZE = 185;
97
98 // X.509 certificate factory instance
99 private final CertificateFactory factory;
100
101 // cached Collection of X509Certificates (may be empty, never null)
102 private Collection<X509Certificate> certs =
103 Collections.<X509Certificate>emptySet();
104
105 // cached X509CRL (may be null)
106 private X509CRL crl;
107
108 // time we last checked for an update
109 private long lastChecked;
110
111 // time server returned as last modified time stamp
112 // or 0 if not available
113 private long lastModified;
114
115 // the URI of this CertStore
116 private URI uri;
117
118 // true if URI is ldap
119 private boolean ldap = false;
120 private CertStore ldapCertStore;
121 private String ldapPath;
122
123 /**
124 * Creates a URICertStore.
125 *
126 * @param parameters specifying the URI
127 */
128 URICertStore(CertStoreParameters params)
129 throws InvalidAlgorithmParameterException, NoSuchAlgorithmException {
130 super(params);
131 if (!(params instanceof URICertStoreParameters)) {
132 throw new InvalidAlgorithmParameterException
133 ("params must be instanceof URICertStoreParameters");
134 }
135 this.uri = ((URICertStoreParameters) params).uri;
136 // if ldap URI, use an LDAPCertStore to fetch certs and CRLs
137 if (uri.getScheme().toLowerCase().equals("ldap")) {
138 ldap = true;
139 ldapCertStore =
140 LDAPCertStore.getInstance(LDAPCertStore.getParameters(uri));
141 ldapPath = uri.getPath();
142 // strip off leading '/'
143 if (ldapPath.charAt(0) == '/') {
144 ldapPath = ldapPath.substring(1);
145 }
146 }
147 try {
148 factory = CertificateFactory.getInstance("X.509");
149 } catch (CertificateException e) {
150 throw new RuntimeException();
151 }
152 }
153
154 /**
155 * Returns a URI CertStore. This method consults a cache of
156 * CertStores (shared per JVM) using the URI as a key.
157 */
158 private static final Cache certStoreCache =
159 Cache.newSoftMemoryCache(CACHE_SIZE);
160 static synchronized CertStore getInstance(URICertStoreParameters params)
161 throws NoSuchAlgorithmException, InvalidAlgorithmParameterException {
162 if (debug != null) {
163 debug.println("CertStore URI:" + params.uri);
164 }
165 CertStore ucs = (CertStore) certStoreCache.get(params);
166 if (ucs == null) {
167 ucs = new UCS(new URICertStore(params), null, "URI", params);
168 certStoreCache.put(params, ucs);
169 } else {
170 if (debug != null) {
171 debug.println("URICertStore.getInstance: cache hit");
172 }
173 }
174 return ucs;
175 }
176
177 /**
178 * Creates a CertStore from information included in the AccessDescription
179 * object of a certificate's Authority Information Access Extension.
180 */
181 static CertStore getInstance(AccessDescription ad) {
182 if (!ad.getAccessMethod().equals(AccessDescription.Ad_CAISSUERS_Id)) {
183 return null;
184 }
185 GeneralNameInterface gn = ad.getAccessLocation().getName();
186 if (!(gn instanceof URIName)) {
187 return null;
188 }
189 URI uri = ((URIName) gn).getURI();
190 try {
191 return URICertStore.getInstance
192 (new URICertStore.URICertStoreParameters(uri));
193 } catch (Exception ex) {
194 if (debug != null) {
195 debug.println("exception creating CertStore: " + ex);
196 ex.printStackTrace();
197 }
198 return null;
199 }
200 }
201
202 /**
203 * Returns a <code>Collection</code> of <code>X509Certificate</code>s that
204 * match the specified selector. If no <code>X509Certificate</code>s
205 * match the selector, an empty <code>Collection</code> will be returned.
206 *
207 * @param selector a <code>CertSelector</code> used to select which
208 * <code>X509Certificate</code>s should be returned. Specify
209 * <code>null</code> to return all <code>X509Certificate</code>s.
210 * @return a <code>Collection</code> of <code>X509Certificate</code>s that
211 * match the specified selector
212 * @throws CertStoreException if an exception occurs
213 */
214 public synchronized Collection<X509Certificate> engineGetCertificates
215 (CertSelector selector) throws CertStoreException {
216
217 // if ldap URI we wrap the CertSelector in an LDAPCertSelector to
218 // avoid LDAP DN matching issues (see LDAPCertSelector for more info)
219 if (ldap) {
220 X509CertSelector xsel = (X509CertSelector) selector;
221 try {
222 xsel = new LDAPCertStore.LDAPCertSelector
223 (xsel, xsel.getSubject(), ldapPath);
224 } catch (IOException ioe) {
225 throw new CertStoreException(ioe);
226 }
227 // Fetch the certificates via LDAP. LDAPCertStore has its own
228 // caching mechanism, see the class description for more info.
229 return (Collection<X509Certificate>)
230 ldapCertStore.getCertificates(xsel);
231 }
232
233 // Return the Certificates for this entry. It returns the cached value
234 // if it is still current and fetches the Certificates otherwise.
235 // For the caching details, see the top of this class.
236 long time = System.currentTimeMillis();
237 if (time - lastChecked < CHECK_INTERVAL) {
238 if (debug != null) {
239 debug.println("Returning certificates from cache");
240 }
241 return getMatchingCerts(certs, selector);
242 }
243 lastChecked = time;
244 InputStream in = null;
245 try {
246 URLConnection connection = uri.toURL().openConnection();
247 if (lastModified != 0) {
248 connection.setIfModifiedSince(lastModified);
249 }
250 in = connection.getInputStream();
251 long oldLastModified = lastModified;
252 lastModified = connection.getLastModified();
253 if (oldLastModified != 0) {
254 if (oldLastModified == lastModified) {
255 if (debug != null) {
256 debug.println("Not modified, using cached copy");
257 }
258 return getMatchingCerts(certs, selector);
259 } else if (connection instanceof HttpURLConnection) {
260 // some proxy servers omit last modified
261 HttpURLConnection hconn = (HttpURLConnection) connection;
262 if (hconn.getResponseCode()
263 == HttpURLConnection.HTTP_NOT_MODIFIED) {
264 if (debug != null) {
265 debug.println("Not modified, using cached copy");
266 }
267 return getMatchingCerts(certs, selector);
268 }
269 }
270 }
271 if (debug != null) {
272 debug.println("Downloading new certificates...");
273 }
274 certs = (Collection<X509Certificate>)
275 factory.generateCertificates(in);
276 return getMatchingCerts(certs, selector);
277 } catch (IOException e) {
278 if (debug != null) {
279 debug.println("Exception fetching certificates:");
280 e.printStackTrace();
281 }
282 } catch (CertificateException e) {
283 if (debug != null) {
284 debug.println("Exception fetching certificates:");
285 e.printStackTrace();
286 }
287 } finally {
288 if (in != null) {
289 try {
290 in.close();
291 } catch (IOException e) {
292 // ignore
293 }
294 }
295 }
296 // exception, forget previous values
297 lastModified = 0;
298 certs = Collections.<X509Certificate>emptySet();
299 return certs;
300 }
301
302 /**
303 * Iterates over the specified Collection of X509Certificates and
304 * returns only those that match the criteria specified in the
305 * CertSelector.
306 */
307 private static Collection<X509Certificate> getMatchingCerts
308 (Collection<X509Certificate> certs, CertSelector selector) {
309 // if selector not specified, all certs match
310 if (selector == null) {
311 return certs;
312 }
313 List<X509Certificate> matchedCerts =
314 new ArrayList<X509Certificate>(certs.size());
315 for (X509Certificate cert : certs) {
316 if (selector.match(cert)) {
317 matchedCerts.add(cert);
318 }
319 }
320 return matchedCerts;
321 }
322
323 /**
324 * Returns a <code>Collection</code> of <code>X509CRL</code>s that
325 * match the specified selector. If no <code>X509CRL</code>s
326 * match the selector, an empty <code>Collection</code> will be returned.
327 *
328 * @param selector A <code>CRLSelector</code> used to select which
329 * <code>X509CRL</code>s should be returned. Specify <code>null</code>
330 * to return all <code>X509CRL</code>s.
331 * @return A <code>Collection</code> of <code>X509CRL</code>s that
332 * match the specified selector
333 * @throws CertStoreException if an exception occurs
334 */
335 public synchronized Collection<X509CRL> engineGetCRLs(CRLSelector selector)
336 throws CertStoreException {
337
338 // if ldap URI we wrap the CRLSelector in an LDAPCRLSelector to
339 // avoid LDAP DN matching issues (see LDAPCRLSelector for more info)
340 if (ldap) {
341 X509CRLSelector xsel = (X509CRLSelector) selector;
342 try {
343 xsel = new LDAPCertStore.LDAPCRLSelector(xsel, null, ldapPath);
344 } catch (IOException ioe) {
345 throw new CertStoreException(ioe);
346 }
347 // Fetch the CRLs via LDAP. LDAPCertStore has its own
348 // caching mechanism, see the class description for more info.
349 return (Collection<X509CRL>) ldapCertStore.getCRLs(xsel);
350 }
351
352 // Return the CRLs for this entry. It returns the cached value
353 // if it is still current and fetches the CRLs otherwise.
354 // For the caching details, see the top of this class.
355 long time = System.currentTimeMillis();
356 if (time - lastChecked < CHECK_INTERVAL) {
357 if (debug != null) {
358 debug.println("Returning CRL from cache");
359 }
360 return getMatchingCRLs(crl, selector);
361 }
362 lastChecked = time;
363 InputStream in = null;
364 try {
365 URLConnection connection = uri.toURL().openConnection();
366 if (lastModified != 0) {
367 connection.setIfModifiedSince(lastModified);
368 }
369 in = connection.getInputStream();
370 long oldLastModified = lastModified;
371 lastModified = connection.getLastModified();
372 if (oldLastModified != 0) {
373 if (oldLastModified == lastModified) {
374 if (debug != null) {
375 debug.println("Not modified, using cached copy");
376 }
377 return getMatchingCRLs(crl, selector);
378 } else if (connection instanceof HttpURLConnection) {
379 // some proxy servers omit last modified
380 HttpURLConnection hconn = (HttpURLConnection) connection;
381 if (hconn.getResponseCode()
382 == HttpURLConnection.HTTP_NOT_MODIFIED) {
383 if (debug != null) {
384 debug.println("Not modified, using cached copy");
385 }
386 return getMatchingCRLs(crl, selector);
387 }
388 }
389 }
390 if (debug != null) {
391 debug.println("Downloading new CRL...");
392 }
393 crl = (X509CRL) factory.generateCRL(in);
394 return getMatchingCRLs(crl, selector);
395 } catch (IOException e) {
396 if (debug != null) {
397 debug.println("Exception fetching CRL:");
398 e.printStackTrace();
399 }
400 } catch (CRLException e) {
401 if (debug != null) {
402 debug.println("Exception fetching CRL:");
403 e.printStackTrace();
404 }
405 } finally {
406 if (in != null) {
407 try {
408 in.close();
409 } catch (IOException e) {
410 // ignore
411 }
412 }
413 }
414 // exception, forget previous values
415 lastModified = 0;
416 crl = null;
417 return Collections.<X509CRL>emptyList();
418 }
419
420 /**
421 * Checks if the specified X509CRL matches the criteria specified in the
422 * CRLSelector.
423 */
424 private static Collection<X509CRL> getMatchingCRLs
425 (X509CRL crl, CRLSelector selector) {
426 if (selector == null || (crl != null && selector.match(crl))) {
427 return Collections.<X509CRL>singletonList(crl);
428 } else {
429 return Collections.<X509CRL>emptyList();
430 }
431 }
432
433 /**
434 * CertStoreParameters for the URICertStore.
435 */
436 static class URICertStoreParameters implements CertStoreParameters {
437 private final URI uri;
438 private volatile int hashCode = 0;
439 URICertStoreParameters(URI uri) {
440 this.uri = uri;
441 }
442 public boolean equals(Object obj) {
443 if (!(obj instanceof URICertStoreParameters)) {
444 return false;
445 }
446 URICertStoreParameters params = (URICertStoreParameters) obj;
447 return uri.equals(params.uri);
448 }
449 public int hashCode() {
450 if (hashCode == 0) {
451 int result = 17;
452 result = 37*result + uri.hashCode();
453 hashCode = result;
454 }
455 return hashCode;
456 }
457 public Object clone() {
458 try {
459 return super.clone();
460 } catch (CloneNotSupportedException e) {
461 /* Cannot happen */
462 throw new InternalError(e.toString());
463 }
464 }
465 }
466
467 /**
468 * This class allows the URICertStore to be accessed as a CertStore.
469 */
470 private static class UCS extends CertStore {
471 protected UCS(CertStoreSpi spi, Provider p, String type,
472 CertStoreParameters params) {
473 super(spi, p, type, params);
474 }
475 }
476}