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      */
initializeTimeout()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      */
URICertStore(CertStoreParameters params)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);
getInstance(URICertStoreParameters params)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      */
getInstance(AccessDescription ad)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")
engineGetCertificates(CertSelector selector)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      */
getMatchingCerts(Collection<X509Certificate> certs, CertSelector selector)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")
engineGetCRLs(CRLSelector selector)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      */
getMatchingCRLs(X509CRL crl, CRLSelector selector)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;
URICertStoreParameters(URI uri)454         URICertStoreParameters(URI uri) {
455             this.uri = uri;
456         }
equals(Object obj)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         }
hashCode()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         }
clone()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 {
UCS(CertStoreSpi spi, Provider p, String type, CertStoreParameters params)486         protected UCS(CertStoreSpi spi, Provider p, String type,
487             CertStoreParameters params) {
488             super(spi, p, type, params);
489         }
490     }
491 }
492