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
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.X509Certificate;
46import java.security.cert.X509CertSelector;
47import java.security.cert.X509CRL;
48import java.security.cert.X509CRLSelector;
49import java.util.ArrayList;
50import java.util.Collection;
51import java.util.Collections;
52import java.util.List;
53import java.util.Locale;
54import sun.security.action.GetIntegerAction;
55import sun.security.x509.AccessDescription;
56import sun.security.x509.GeneralNameInterface;
57import sun.security.x509.URIName;
58import sun.security.util.Cache;
59import 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 */
89class 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