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