Home | History | Annotate | Download | only in certpath
      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