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.X509CRL; 48import java.util.ArrayList; 49import java.util.Collection; 50import java.util.Collections; 51import java.util.List; 52import java.util.Locale; 53import sun.security.action.GetIntegerAction; 54import sun.security.x509.AccessDescription; 55import sun.security.x509.GeneralNameInterface; 56import sun.security.x509.URIName; 57import sun.security.util.Cache; 58import 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 1.7 87 */ 88class 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 static final int CHECK_INTERVAL = 30 * 1000; 95 96 // size of the cache (see Cache class for sizing recommendations) 97 private static final 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 = Collections.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 122 // Default maximum connect timeout in milliseconds (15 seconds) 123 // allowed when downloading CRLs 124 private static final int DEFAULT_CRL_CONNECT_TIMEOUT = 15000; 125 126 /** 127 * Integer value indicating the connect timeout, in seconds, to be 128 * used for the CRL download. A timeout of zero is interpreted as 129 * an infinite timeout. 130 */ 131 private static final int CRL_CONNECT_TIMEOUT = initializeTimeout(); 132 133 /** 134 * Initialize the timeout length by getting the CRL timeout 135 * system property. If the property has not been set, or if its 136 * value is negative, set the timeout length to the default. 137 */ 138 private static int initializeTimeout() { 139 Integer tmp = java.security.AccessController.doPrivileged( 140 new GetIntegerAction("com.sun.security.crl.timeout")); 141 if (tmp == null || tmp < 0) { 142 return DEFAULT_CRL_CONNECT_TIMEOUT; 143 } 144 // Convert to milliseconds, as the system property will be 145 // specified in seconds 146 return tmp * 1000; 147 } 148 149 /** 150 * Creates a URICertStore. 151 * 152 * @param parameters specifying the URI 153 */ 154 URICertStore(CertStoreParameters params) 155 throws InvalidAlgorithmParameterException, NoSuchAlgorithmException { 156 super(params); 157 if (!(params instanceof URICertStoreParameters)) { 158 throw new InvalidAlgorithmParameterException 159 ("params must be instanceof URICertStoreParameters"); 160 } 161 this.uri = ((URICertStoreParameters) params).getURI(); 162 // if ldap URI, use an LDAPCertStore to fetch certs and CRLs 163 if (uri.getScheme().toLowerCase(Locale.ENGLISH).equals("ldap")) { 164 ldap = true; 165 ldapCertStore = CertStore.getInstance("LDAP", params); 166 } 167 try { 168 factory = CertificateFactory.getInstance("X.509"); 169 } catch (CertificateException e) { 170 throw new RuntimeException(); 171 } 172 } 173 174 /** 175 * Returns a URI CertStore. This method consults a cache of 176 * CertStores (shared per JVM) using the URI as a key. 177 */ 178 private static final Cache<URICertStoreParameters, CertStore> 179 certStoreCache = Cache.newSoftMemoryCache(CACHE_SIZE); 180 static synchronized CertStore getInstance(URICertStoreParameters params) 181 throws NoSuchAlgorithmException, InvalidAlgorithmParameterException { 182 if (debug != null) { 183 debug.println("CertStore URI:" + params.getURI()); 184 } 185 CertStore ucs = certStoreCache.get(params); 186 if (ucs == null) { 187 ucs = new UCS(new URICertStore(params), null, "URI", params); 188 certStoreCache.put(params, ucs); 189 } else { 190 if (debug != null) { 191 debug.println("URICertStore.getInstance: cache hit"); 192 } 193 } 194 return ucs; 195 } 196 197 /** 198 * Creates a CertStore from information included in the AccessDescription 199 * object of a certificate's Authority Information Access Extension. 200 */ 201 static CertStore getInstance(AccessDescription ad) { 202 if (!ad.getAccessMethod().equals( 203 AccessDescription.Ad_CAISSUERS_Id)) { 204 return null; 205 } 206 GeneralNameInterface gn = ad.getAccessLocation().getName(); 207 if (!(gn instanceof URIName)) { 208 return null; 209 } 210 URI uri = ((URIName) gn).getURI(); 211 try { 212 return URICertStore.getInstance(new URICertStoreParameters(uri)); 213 } catch (Exception ex) { 214 if (debug != null) { 215 debug.println("exception creating CertStore: " + ex); 216 ex.printStackTrace(); 217 } 218 return null; 219 } 220 } 221 222 /** 223 * Returns a <code>Collection</code> of <code>X509Certificate</code>s that 224 * match the specified selector. If no <code>X509Certificate</code>s 225 * match the selector, an empty <code>Collection</code> will be returned. 226 * 227 * @param selector a <code>CertSelector</code> used to select which 228 * <code>X509Certificate</code>s should be returned. Specify 229 * <code>null</code> to return all <code>X509Certificate</code>s. 230 * @return a <code>Collection</code> of <code>X509Certificate</code>s that 231 * match the specified selector 232 * @throws CertStoreException if an exception occurs 233 */ 234 @Override 235 @SuppressWarnings("unchecked") 236 public synchronized Collection<X509Certificate> engineGetCertificates 237 (CertSelector selector) throws CertStoreException { 238 239 if (ldap) { 240 // caching mechanism, see the class description for more info. 241 return (Collection<X509Certificate>) 242 ldapCertStore.getCertificates(selector); 243 } 244 245 // Return the Certificates for this entry. It returns the cached value 246 // if it is still current and fetches the Certificates otherwise. 247 // For the caching details, see the top of this class. 248 long time = System.currentTimeMillis(); 249 if (time - lastChecked < CHECK_INTERVAL) { 250 if (debug != null) { 251 debug.println("Returning certificates from cache"); 252 } 253 return getMatchingCerts(certs, selector); 254 } 255 lastChecked = time; 256 try { 257 URLConnection connection = uri.toURL().openConnection(); 258 if (lastModified != 0) { 259 connection.setIfModifiedSince(lastModified); 260 } 261 long oldLastModified = lastModified; 262 try (InputStream in = connection.getInputStream()) { 263 lastModified = connection.getLastModified(); 264 if (oldLastModified != 0) { 265 if (oldLastModified == lastModified) { 266 if (debug != null) { 267 debug.println("Not modified, using cached copy"); 268 } 269 return getMatchingCerts(certs, selector); 270 } else if (connection instanceof HttpURLConnection) { 271 // some proxy servers omit last modified 272 HttpURLConnection hconn = (HttpURLConnection)connection; 273 if (hconn.getResponseCode() 274 == HttpURLConnection.HTTP_NOT_MODIFIED) { 275 if (debug != null) { 276 debug.println("Not modified, using cached copy"); 277 } 278 return getMatchingCerts(certs, selector); 279 } 280 } 281 } 282 if (debug != null) { 283 debug.println("Downloading new certificates..."); 284 } 285 // Safe cast since factory is an X.509 certificate factory 286 certs = (Collection<X509Certificate>) 287 factory.generateCertificates(in); 288 } 289 return getMatchingCerts(certs, selector); 290 } catch (IOException | CertificateException e) { 291 if (debug != null) { 292 debug.println("Exception fetching certificates:"); 293 e.printStackTrace(); 294 } 295 } 296 // exception, forget previous values 297 lastModified = 0; 298 certs = Collections.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 = new ArrayList<>(certs.size()); 314 for (X509Certificate cert : certs) { 315 if (selector.match(cert)) { 316 matchedCerts.add(cert); 317 } 318 } 319 return matchedCerts; 320 } 321 322 /** 323 * Returns a <code>Collection</code> of <code>X509CRL</code>s that 324 * match the specified selector. If no <code>X509CRL</code>s 325 * match the selector, an empty <code>Collection</code> will be returned. 326 * 327 * @param selector A <code>CRLSelector</code> used to select which 328 * <code>X509CRL</code>s should be returned. Specify <code>null</code> 329 * to return all <code>X509CRL</code>s. 330 * @return A <code>Collection</code> of <code>X509CRL</code>s that 331 * match the specified selector 332 * @throws CertStoreException if an exception occurs 333 */ 334 @Override 335 @SuppressWarnings("unchecked") 336 public synchronized Collection<X509CRL> engineGetCRLs(CRLSelector selector) 337 throws CertStoreException { 338 339 if (ldap) { 340 // Fetch the CRLs via LDAP. LDAPCertStore has its own 341 // caching mechanism, see the class description for more info. 342 try { 343 return (Collection<X509CRL>) ldapCertStore.getCRLs(selector); 344 } catch (CertStoreException cse) { 345 throw new PKIX.CertStoreTypeException("LDAP", cse); 346 } 347 } 348 349 // Return the CRLs for this entry. It returns the cached value 350 // if it is still current and fetches the CRLs otherwise. 351 // For the caching details, see the top of this class. 352 long time = System.currentTimeMillis(); 353 if (time - lastChecked < CHECK_INTERVAL) { 354 if (debug != null) { 355 debug.println("Returning CRL from cache"); 356 } 357 return getMatchingCRLs(crl, selector); 358 } 359 lastChecked = time; 360 try { 361 URLConnection connection = uri.toURL().openConnection(); 362 if (lastModified != 0) { 363 connection.setIfModifiedSince(lastModified); 364 } 365 long oldLastModified = lastModified; 366 connection.setConnectTimeout(CRL_CONNECT_TIMEOUT); 367 try (InputStream in = connection.getInputStream()) { 368 lastModified = connection.getLastModified(); 369 if (oldLastModified != 0) { 370 if (oldLastModified == lastModified) { 371 if (debug != null) { 372 debug.println("Not modified, using cached copy"); 373 } 374 return getMatchingCRLs(crl, selector); 375 } else if (connection instanceof HttpURLConnection) { 376 // some proxy servers omit last modified 377 HttpURLConnection hconn = (HttpURLConnection)connection; 378 if (hconn.getResponseCode() 379 == HttpURLConnection.HTTP_NOT_MODIFIED) { 380 if (debug != null) { 381 debug.println("Not modified, using cached copy"); 382 } 383 return getMatchingCRLs(crl, selector); 384 } 385 } 386 } 387 if (debug != null) { 388 debug.println("Downloading new CRL..."); 389 } 390 crl = (X509CRL) factory.generateCRL(in); 391 } 392 return getMatchingCRLs(crl, selector); 393 } catch (IOException | CRLException e) { 394 if (debug != null) { 395 debug.println("Exception fetching CRL:"); 396 e.printStackTrace(); 397 } 398 // exception, forget previous values 399 lastModified = 0; 400 crl = null; 401 throw new PKIX.CertStoreTypeException("URI", 402 new CertStoreException(e)); 403 } 404 } 405 406 /** 407 * Checks if the specified X509CRL matches the criteria specified in the 408 * CRLSelector. 409 */ 410 private static Collection<X509CRL> getMatchingCRLs 411 (X509CRL crl, CRLSelector selector) { 412 if (selector == null || (crl != null && selector.match(crl))) { 413 return Collections.singletonList(crl); 414 } else { 415 return Collections.emptyList(); 416 } 417 } 418 419 /** 420 * This class allows the URICertStore to be accessed as a CertStore. 421 */ 422 private static class UCS extends CertStore { 423 protected UCS(CertStoreSpi spi, Provider p, String type, 424 CertStoreParameters params) { 425 super(spi, p, type, params); 426 } 427 } 428} 429