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