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