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