/*
* Copyright (c) 2011-2012 ICM Uniwersytet Warszawski All rights reserved.
* See LICENCE.txt file for licensing information.
*/
package eu.emi.security.authn.x509.helpers.trust;
import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.URISyntaxException;
import java.net.URL;
import java.net.URLConnection;
import java.security.cert.CertificateEncodingException;
import java.security.cert.TrustAnchor;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.Timer;
import java.util.Map.Entry;
import eu.emi.security.authn.x509.StoreUpdateListener;
import eu.emi.security.authn.x509.StoreUpdateListener.Severity;
import eu.emi.security.authn.x509.helpers.ObserversHandler;
import eu.emi.security.authn.x509.helpers.pkipath.PlainStoreUtils;
import eu.emi.security.authn.x509.impl.CertificateUtils;
import eu.emi.security.authn.x509.impl.CertificateUtils.Encoding;
/**
* Retrieves CA certificates from locations given as local paths with wildcards
* or URLs.
* @author K. Benedyczak
*/
public class DirectoryTrustAnchorStore extends TimedTrustAnchorStoreBase
{
private final PlainStoreUtils utils;
private final int connTimeout;
private final String cacheDir;
protected Set<TrustAnchorExt> anchors;
protected Map<URL, TrustAnchorExt> locations2anchors;
protected Encoding encoding;
public DirectoryTrustAnchorStore(List<String> locations, String diskCache,
int connectionTimeout, Timer t, long updateInterval, Encoding encoding,
ObserversHandler listeners)
{
this(locations, diskCache, connectionTimeout, t,
updateInterval, encoding, listeners, false);
}
protected DirectoryTrustAnchorStore(List<String> locations, String diskCache,
int connectionTimeout, Timer t, long updateInterval, Encoding encoding,
ObserversHandler observers, boolean noFirstUpdate)
{
super(t, updateInterval, observers);
this.utils = new PlainStoreUtils(diskCache, "-cacert", locations);
if (connectionTimeout < 0)
throw new IllegalArgumentException("Remote connection timeout must be a non negative number");
this.connTimeout = connectionTimeout;
this.cacheDir = diskCache;
anchors = new HashSet<TrustAnchorExt>();
locations2anchors = new HashMap<URL, TrustAnchorExt>();
this.encoding = encoding;
if (!noFirstUpdate)
{
update();
scheduleUpdate();
}
}
protected X509Certificate[] loadCerts(URL url) throws IOException, URISyntaxException, CertificateEncodingException
{
String protocol = url.getProtocol();
boolean local = false;
if (protocol.equalsIgnoreCase("file"))
local = true;
X509Certificate[] ret;
try
{
URLConnection conn = url.openConnection();
if (!local)
{
conn.setConnectTimeout(connTimeout);
conn.setReadTimeout(connTimeout);
}
InputStream is = new BufferedInputStream(conn.getInputStream());
ret = CertificateUtils.loadCertificates(is, getEncoding());
observers.notifyObservers(url.toExternalForm(),
StoreUpdateListener.CA_CERT,
Severity.NOTIFICATION, null);
} catch (IOException e)
{
if (!local && cacheDir != null)
{
File input = utils.getCacheFile(url);
if (input.exists())
{
InputStream is = new BufferedInputStream(
new FileInputStream(input));
ret = CertificateUtils.loadCertificates(is, getEncoding());
is.close();
observers.notifyObservers(url.toExternalForm(),
StoreUpdateListener.CA_CERT,
Severity.WARNING,
new IOException("Warning: CA certificate was not loaded from its URL, " +
"but its previous cached copy was loaded from disk file " + input.getPath(), e));
return ret;
} else
throw e;
}
throw e;
}
if (!local && ret.length == 1)
utils.saveCacheFile(ret[0].getEncoded(), url);
return ret;
}
/**
* For all URLs tries to load a CA cert. Information for extensions:
* this method is guaranteed to be called once per update.
*
* @param locations a collection of URLs
*/
protected void reloadCerts(Collection<URL> locations)
{
Set<TrustAnchorExt> tmpAnchors = new HashSet<TrustAnchorExt>();
Map<URL, TrustAnchorExt> tmpLoc2anch = new HashMap<URL, TrustAnchorExt>();
for (URL location: locations)
{
X509Certificate[] certs;
try
{
certs = loadCerts(location);
} catch (Exception e)
{
observers.notifyObservers(location.toExternalForm(),
StoreUpdateListener.CA_CERT,
Severity.ERROR, e);
continue;
}
for (X509Certificate cert: certs)
{
checkValidity(location.toExternalForm(), cert, false);
TrustAnchorExt anchor = new TrustAnchorExt(cert, null);
tmpAnchors.add(anchor);
tmpLoc2anch.put(location, anchor);
}
}
synchronized(this)
{
anchors.addAll(tmpAnchors);
locations2anchors.putAll(tmpLoc2anch);
}
}
/**
* Removes those certs which are for the not known locations.
* Happens when a file was removed from a wildcard listing.
*/
private synchronized void removeStaleCas()
{
Iterator<Entry<URL, TrustAnchorExt>> itMain = locations2anchors.entrySet().iterator();
while (itMain.hasNext())
{
Entry<URL, TrustAnchorExt> entry = itMain.next();
if (!utils.isPresent(entry.getKey()))
{
anchors.remove(entry.getValue());
itMain.remove();
}
}
}
/**
* 1. work only if schedulingNeeded()
* 2. for all wildcards refresh file lists
* 3. remove the locations not valid anymore
* 4. for all location URLs try to get the cert
* 5. update timestamp
* 6. schedule the next update if enabled
*/
protected void update()
{
utils.establishWildcardsLocations();
removeStaleCas();
List<URL> resolvedLocations = new ArrayList<URL>();
resolvedLocations.addAll(utils.getURLLocations());
resolvedLocations.addAll(utils.getResolvedWildcards());
reloadCerts(resolvedLocations);
}
@Override
public synchronized Set<TrustAnchor> getTrustAnchors()
{
Set<TrustAnchor> ret = new HashSet<TrustAnchor>();
ret.addAll(anchors);
return ret;
}
@Override
public synchronized X509Certificate[] getTrustedCertificates()
{
X509Certificate[] ret = new X509Certificate[anchors.size()];
int i=0;
for (TrustAnchor ta: anchors)
ret[i++] = ta.getTrustedCert();
return ret;
}
public List<String> getLocations()
{
return utils.getLocations();
}
public int getConnTimeout()
{
return connTimeout;
}
public String getCacheDir()
{
return cacheDir;
}
public Encoding getEncoding()
{
return encoding;
}
}