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 }