/* * Copyright (c) 2006, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Oracle designates this * particular file as subject to the "Classpath" exception as provided * by Oracle in the LICENSE file that accompanied this code. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ package sun.security.provider.certpath; import java.io.InputStream; import java.io.IOException; import java.net.HttpURLConnection; import java.net.URI; import java.net.URLConnection; import java.security.InvalidAlgorithmParameterException; import java.security.NoSuchAlgorithmException; import java.security.Provider; import java.security.cert.CertificateException; import java.security.cert.CertificateFactory; import java.security.cert.CertSelector; import java.security.cert.CertStore; import java.security.cert.CertStoreException; import java.security.cert.CertStoreParameters; import java.security.cert.CertStoreSpi; import java.security.cert.CRLException; import java.security.cert.CRLSelector; import java.security.cert.X509Certificate; import java.security.cert.X509CertSelector; import java.security.cert.X509CRL; import java.security.cert.X509CRLSelector; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.List; import sun.security.x509.AccessDescription; import sun.security.x509.GeneralNameInterface; import sun.security.x509.URIName; import sun.security.util.Cache; import sun.security.util.Debug; /** * A <code>CertStore</code> that retrieves <code>Certificates</code> or * <code>CRL</code>s from a URI, for example, as specified in an X.509 * AuthorityInformationAccess or CRLDistributionPoint extension. * <p> * For CRLs, this implementation retrieves a single DER encoded CRL per URI. * For Certificates, this implementation retrieves a single DER encoded CRL or * a collection of Certificates encoded as a PKCS#7 "certs-only" CMS message. * <p> * This <code>CertStore</code> also implements Certificate/CRL caching. * Currently, the cache is shared between all applications in the VM and uses a * hardcoded policy. The cache has a maximum size of 185 entries, which are held * by SoftReferences. A request will be satisfied from the cache if we last * checked for an update within CHECK_INTERVAL (last 30 seconds). Otherwise, * we open an URLConnection to download the Certificate(s)/CRL using an * If-Modified-Since request (HTTP) if possible. Note that both positive and * negative responses are cached, i.e. if we are unable to open the connection * or the Certificate(s)/CRL cannot be parsed, we remember this result and * additional calls during the CHECK_INTERVAL period do not try to open another * connection. * <p> * The URICertStore is not currently a standard CertStore type. We should * consider adding a standard "URI" CertStore type. * * @author Andreas Sterbenz * @author Sean Mullan * @since 7.0 */ class URICertStore extends CertStoreSpi { private static final Debug debug = Debug.getInstance("certpath"); // interval between checks for update of cached Certificates/CRLs // (30 seconds) private final static int CHECK_INTERVAL = 30 * 1000; // size of the cache (see Cache class for sizing recommendations) private final static int CACHE_SIZE = 185; // X.509 certificate factory instance private final CertificateFactory factory; // cached Collection of X509Certificates (may be empty, never null) private Collection<X509Certificate> certs = Collections.<X509Certificate>emptySet(); // cached X509CRL (may be null) private X509CRL crl; // time we last checked for an update private long lastChecked; // time server returned as last modified time stamp // or 0 if not available private long lastModified; // the URI of this CertStore private URI uri; // true if URI is ldap private boolean ldap = false; private CertStore ldapCertStore; private String ldapPath; /** * Creates a URICertStore. * * @param parameters specifying the URI */ URICertStore(CertStoreParameters params) throws InvalidAlgorithmParameterException, NoSuchAlgorithmException { super(params); if (!(params instanceof URICertStoreParameters)) { throw new InvalidAlgorithmParameterException ("params must be instanceof URICertStoreParameters"); } this.uri = ((URICertStoreParameters) params).uri; // if ldap URI, use an LDAPCertStore to fetch certs and CRLs if (uri.getScheme().toLowerCase().equals("ldap")) { ldap = true; ldapCertStore = LDAPCertStore.getInstance(LDAPCertStore.getParameters(uri)); ldapPath = uri.getPath(); // strip off leading '/' if (ldapPath.charAt(0) == '/') { ldapPath = ldapPath.substring(1); } } try { factory = CertificateFactory.getInstance("X.509"); } catch (CertificateException e) { throw new RuntimeException(); } } /** * Returns a URI CertStore. This method consults a cache of * CertStores (shared per JVM) using the URI as a key. */ private static final Cache certStoreCache = Cache.newSoftMemoryCache(CACHE_SIZE); static synchronized CertStore getInstance(URICertStoreParameters params) throws NoSuchAlgorithmException, InvalidAlgorithmParameterException { if (debug != null) { debug.println("CertStore URI:" + params.uri); } CertStore ucs = (CertStore) certStoreCache.get(params); if (ucs == null) { ucs = new UCS(new URICertStore(params), null, "URI", params); certStoreCache.put(params, ucs); } else { if (debug != null) { debug.println("URICertStore.getInstance: cache hit"); } } return ucs; } /** * Creates a CertStore from information included in the AccessDescription * object of a certificate's Authority Information Access Extension. */ static CertStore getInstance(AccessDescription ad) { if (!ad.getAccessMethod().equals(AccessDescription.Ad_CAISSUERS_Id)) { return null; } GeneralNameInterface gn = ad.getAccessLocation().getName(); if (!(gn instanceof URIName)) { return null; } URI uri = ((URIName) gn).getURI(); try { return URICertStore.getInstance (new URICertStore.URICertStoreParameters(uri)); } catch (Exception ex) { if (debug != null) { debug.println("exception creating CertStore: " + ex); ex.printStackTrace(); } return null; } } /** * Returns a <code>Collection</code> of <code>X509Certificate</code>s that * match the specified selector. If no <code>X509Certificate</code>s * match the selector, an empty <code>Collection</code> will be returned. * * @param selector a <code>CertSelector</code> used to select which * <code>X509Certificate</code>s should be returned. Specify * <code>null</code> to return all <code>X509Certificate</code>s. * @return a <code>Collection</code> of <code>X509Certificate</code>s that * match the specified selector * @throws CertStoreException if an exception occurs */ public synchronized Collection<X509Certificate> engineGetCertificates (CertSelector selector) throws CertStoreException { // if ldap URI we wrap the CertSelector in an LDAPCertSelector to // avoid LDAP DN matching issues (see LDAPCertSelector for more info) if (ldap) { X509CertSelector xsel = (X509CertSelector) selector; try { xsel = new LDAPCertStore.LDAPCertSelector (xsel, xsel.getSubject(), ldapPath); } catch (IOException ioe) { throw new CertStoreException(ioe); } // Fetch the certificates via LDAP. LDAPCertStore has its own // caching mechanism, see the class description for more info. return (Collection<X509Certificate>) ldapCertStore.getCertificates(xsel); } // Return the Certificates for this entry. It returns the cached value // if it is still current and fetches the Certificates otherwise. // For the caching details, see the top of this class. long time = System.currentTimeMillis(); if (time - lastChecked < CHECK_INTERVAL) { if (debug != null) { debug.println("Returning certificates from cache"); } return getMatchingCerts(certs, selector); } lastChecked = time; InputStream in = null; try { URLConnection connection = uri.toURL().openConnection(); if (lastModified != 0) { connection.setIfModifiedSince(lastModified); } in = connection.getInputStream(); long oldLastModified = lastModified; lastModified = connection.getLastModified(); if (oldLastModified != 0) { if (oldLastModified == lastModified) { if (debug != null) { debug.println("Not modified, using cached copy"); } return getMatchingCerts(certs, selector); } else if (connection instanceof HttpURLConnection) { // some proxy servers omit last modified HttpURLConnection hconn = (HttpURLConnection) connection; if (hconn.getResponseCode() == HttpURLConnection.HTTP_NOT_MODIFIED) { if (debug != null) { debug.println("Not modified, using cached copy"); } return getMatchingCerts(certs, selector); } } } if (debug != null) { debug.println("Downloading new certificates..."); } certs = (Collection<X509Certificate>) factory.generateCertificates(in); return getMatchingCerts(certs, selector); } catch (IOException e) { if (debug != null) { debug.println("Exception fetching certificates:"); e.printStackTrace(); } } catch (CertificateException e) { if (debug != null) { debug.println("Exception fetching certificates:"); e.printStackTrace(); } } finally { if (in != null) { try { in.close(); } catch (IOException e) { // ignore } } } // exception, forget previous values lastModified = 0; certs = Collections.<X509Certificate>emptySet(); return certs; } /** * Iterates over the specified Collection of X509Certificates and * returns only those that match the criteria specified in the * CertSelector. */ private static Collection<X509Certificate> getMatchingCerts (Collection<X509Certificate> certs, CertSelector selector) { // if selector not specified, all certs match if (selector == null) { return certs; } List<X509Certificate> matchedCerts = new ArrayList<X509Certificate>(certs.size()); for (X509Certificate cert : certs) { if (selector.match(cert)) { matchedCerts.add(cert); } } return matchedCerts; } /** * Returns a <code>Collection</code> of <code>X509CRL</code>s that * match the specified selector. If no <code>X509CRL</code>s * match the selector, an empty <code>Collection</code> will be returned. * * @param selector A <code>CRLSelector</code> used to select which * <code>X509CRL</code>s should be returned. Specify <code>null</code> * to return all <code>X509CRL</code>s. * @return A <code>Collection</code> of <code>X509CRL</code>s that * match the specified selector * @throws CertStoreException if an exception occurs */ public synchronized Collection<X509CRL> engineGetCRLs(CRLSelector selector) throws CertStoreException { // if ldap URI we wrap the CRLSelector in an LDAPCRLSelector to // avoid LDAP DN matching issues (see LDAPCRLSelector for more info) if (ldap) { X509CRLSelector xsel = (X509CRLSelector) selector; try { xsel = new LDAPCertStore.LDAPCRLSelector(xsel, null, ldapPath); } catch (IOException ioe) { throw new CertStoreException(ioe); } // Fetch the CRLs via LDAP. LDAPCertStore has its own // caching mechanism, see the class description for more info. return (Collection<X509CRL>) ldapCertStore.getCRLs(xsel); } // Return the CRLs for this entry. It returns the cached value // if it is still current and fetches the CRLs otherwise. // For the caching details, see the top of this class. long time = System.currentTimeMillis(); if (time - lastChecked < CHECK_INTERVAL) { if (debug != null) { debug.println("Returning CRL from cache"); } return getMatchingCRLs(crl, selector); } lastChecked = time; InputStream in = null; try { URLConnection connection = uri.toURL().openConnection(); if (lastModified != 0) { connection.setIfModifiedSince(lastModified); } in = connection.getInputStream(); long oldLastModified = lastModified; lastModified = connection.getLastModified(); if (oldLastModified != 0) { if (oldLastModified == lastModified) { if (debug != null) { debug.println("Not modified, using cached copy"); } return getMatchingCRLs(crl, selector); } else if (connection instanceof HttpURLConnection) { // some proxy servers omit last modified HttpURLConnection hconn = (HttpURLConnection) connection; if (hconn.getResponseCode() == HttpURLConnection.HTTP_NOT_MODIFIED) { if (debug != null) { debug.println("Not modified, using cached copy"); } return getMatchingCRLs(crl, selector); } } } if (debug != null) { debug.println("Downloading new CRL..."); } crl = (X509CRL) factory.generateCRL(in); return getMatchingCRLs(crl, selector); } catch (IOException e) { if (debug != null) { debug.println("Exception fetching CRL:"); e.printStackTrace(); } } catch (CRLException e) { if (debug != null) { debug.println("Exception fetching CRL:"); e.printStackTrace(); } } finally { if (in != null) { try { in.close(); } catch (IOException e) { // ignore } } } // exception, forget previous values lastModified = 0; crl = null; return Collections.<X509CRL>emptyList(); } /** * Checks if the specified X509CRL matches the criteria specified in the * CRLSelector. */ private static Collection<X509CRL> getMatchingCRLs (X509CRL crl, CRLSelector selector) { if (selector == null || (crl != null && selector.match(crl))) { return Collections.<X509CRL>singletonList(crl); } else { return Collections.<X509CRL>emptyList(); } } /** * CertStoreParameters for the URICertStore. */ static class URICertStoreParameters implements CertStoreParameters { private final URI uri; private volatile int hashCode = 0; URICertStoreParameters(URI uri) { this.uri = uri; } public boolean equals(Object obj) { if (!(obj instanceof URICertStoreParameters)) { return false; } URICertStoreParameters params = (URICertStoreParameters) obj; return uri.equals(params.uri); } public int hashCode() { if (hashCode == 0) { int result = 17; result = 37*result + uri.hashCode(); hashCode = result; } return hashCode; } public Object clone() { try { return super.clone(); } catch (CloneNotSupportedException e) { /* Cannot happen */ throw new InternalError(e.toString()); } } } /** * This class allows the URICertStore to be accessed as a CertStore. */ private static class UCS extends CertStore { protected UCS(CertStoreSpi spi, Provider p, String type, CertStoreParameters params) { super(spi, p, type, params); } } }