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