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 }