/*
* 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.InputStream;
import java.security.cert.TrustAnchor;
import java.security.cert.X509Certificate;
import java.util.Collection;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.WeakHashMap;
import javax.security.auth.x500.X500Principal;
import eu.emi.security.authn.x509.StoreUpdateListener;
import eu.emi.security.authn.x509.StoreUpdateListener.Severity;
import eu.emi.security.authn.x509.helpers.CachedElement;
import eu.emi.security.authn.x509.helpers.ObserversHandler;
import eu.emi.security.authn.x509.helpers.ns.LazyEuGridPmaNamespacesStore;
import eu.emi.security.authn.x509.helpers.ns.LazyGlobusNamespacesStore;
import eu.emi.security.authn.x509.helpers.ns.NamespacesStore;
import eu.emi.security.authn.x509.impl.CertificateUtils;
import eu.emi.security.authn.x509.impl.CertificateUtils.Encoding;
import eu.emi.security.authn.x509.impl.X500NameUtils;
/**
* Implementation of the truststore which uses CA certificates from a single directory
* in OpenSSL format. Each certificate should be stored in a file named HASH.NUM,
* where HASH is an 8 digit hex number. The NUM must be a number, starting from 0.
* The hash can be either of openssl pre 1.0.0 version
* (with 8 least significant digits of the MD5 hash of the certificate subject in DER format)
* or in openssl 1.0.0 and above format (SHA1 hash of specially normalized DN). The class is configured
* to use one or another, never both.
* <p>
* This class (contrary to the {@link OpensslTrustAnchorStoreImpl}) doesn't extend {@link DirectoryTrustAnchorStore}
* and therefore certificates (and all corresponding files) are not loaded at startup and kept in memory.
* The files are loaded on-demand and are only cached in memory for no longer then the updateInterval is.
*
* @author K. Benedyczak
*/
public class LazyOpensslTrustAnchorStoreImpl extends AbstractTrustAnchorStore implements OpensslTrustAnchorStore
{
public static final String CERTS_REGEXP = "........\\.[0-9]+";
protected CachedElement<Set<TrustAnchorExt>> cachedAnchors;
protected Map<X500Principal, CachedElement<Set<TrustAnchorExt>>> cachedAnchorsPerIssuer;
private boolean openssl1Mode;
private NamespacesStore pmaNsStore;
private NamespacesStore globusNsStore;
private File baseDirectory;
public LazyOpensslTrustAnchorStoreImpl(String basePath, long updateInterval,
ObserversHandler observers, boolean openssl1Mode)
{
super(updateInterval, observers);
this.baseDirectory = new File(basePath);
this.openssl1Mode = openssl1Mode;
this.cachedAnchorsPerIssuer = new WeakHashMap<X500Principal, CachedElement<Set<TrustAnchorExt>>>(150);
pmaNsStore = new LazyEuGridPmaNamespacesStore(observers, openssl1Mode, basePath, updateInterval);
globusNsStore = new LazyGlobusNamespacesStore(observers, openssl1Mode, basePath, updateInterval);
}
protected X509Certificate tryLoadCertInternal(File file)
{
X509Certificate cert;
try
{
InputStream is = new BufferedInputStream(new FileInputStream(file));
cert = CertificateUtils.loadCertificate(is, Encoding.PEM);
observers.notifyObservers(file.getAbsolutePath(),
StoreUpdateListener.CA_CERT,
Severity.NOTIFICATION, null);
return cert;
} catch (Exception e)
{
observers.notifyObservers(file.getAbsolutePath(), StoreUpdateListener.CA_CERT,
Severity.ERROR, e);
return null;
}
}
protected void tryLoadCert(File file, Set<TrustAnchorExt> set)
{
String fileHash = OpensslTruststoreHelper.getFileHash(file.getPath(),
OpensslTruststoreHelper.CERT_REGEXP);
if (fileHash == null)
return;
X509Certificate cert = tryLoadCertInternal(file);
if (cert == null)
return;
String certHash = OpensslTruststoreHelper.getOpenSSLCAHash(cert.getSubjectX500Principal(), openssl1Mode);
if (!fileHash.equalsIgnoreCase(certHash))
return;
TrustAnchorExt anchor = new TrustAnchorExt(cert, null);
set.add(anchor);
}
@Override
public NamespacesStore getPmaNsStore()
{
return pmaNsStore;
}
@Override
public NamespacesStore getGlobusNsStore()
{
return globusNsStore;
}
private Set<TrustAnchorExt> loadTrustAnchors()
{
Collection<File> certs = OpensslTruststoreHelper.getFilesWithRegexp(CERTS_REGEXP, baseDirectory);
Set<TrustAnchorExt> ret = new HashSet<TrustAnchorExt>(certs.size());
for (File cert: certs)
tryLoadCert(cert, ret);
return ret;
}
@Override
public synchronized Set<TrustAnchor> getTrustAnchors()
{
if (cachedAnchors == null || cachedAnchors.isExpired(getUpdateInterval()))
{
Set<TrustAnchorExt> loaded = loadTrustAnchors();
cachedAnchors = new CachedElement<Set<TrustAnchorExt>>(loaded);
}
Set<TrustAnchor> ret = new HashSet<TrustAnchor>();
ret.addAll(cachedAnchors.getElement());
return ret;
}
@Override
public X509Certificate[] getTrustedCertificates()
{
Set<TrustAnchor> anchors = getTrustAnchors();
X509Certificate[] ret = new X509Certificate[anchors.size()];
int i=0;
for (TrustAnchor ta: anchors)
ret[i++] = ta.getTrustedCert();
return ret;
}
@Override
public void dispose()
{
}
/**
* Algorithm is as follows: for each certificate subject in chain, and for the issuer of the last
* certificate in chain, it is tried to load a trust anchor defined for such subject. If successful
* then also it is tried recursively to load all parent trust anchors for the loaded one.
*
* @param certChain certificate chain
* @return set of trust anchors for a given certificate chain
*/
public Set<TrustAnchor> getTrustAnchorsFor(X509Certificate[] certChain)
{
Set<TrustAnchorExt> ret = new HashSet<TrustAnchorExt>();
for (X509Certificate c: certChain)
{
tryLoadTAFor(c.getSubjectX500Principal(), ret);
}
tryLoadTAFor(certChain[certChain.length-1].getIssuerX500Principal(), ret);
return new HashSet<TrustAnchor>(ret);
}
private synchronized void tryLoadTAFor(X500Principal issuer, Set<TrustAnchorExt> ret)
{
CachedElement<Set<TrustAnchorExt>> cached = cachedAnchorsPerIssuer.get(issuer);
if (cached != null && !cached.isExpired(getUpdateInterval()))
{
ret.addAll(cached.getElement());
return;
}
Set<TrustAnchorExt> toCache = new HashSet<TrustAnchorExt>();
String hash = OpensslTruststoreHelper.getOpenSSLCAHash(issuer, openssl1Mode);
Collection<File> certs = OpensslTruststoreHelper.getFilesWithRegexp(hash+"\\.[0-9]+", baseDirectory);
for (File file: certs)
{
X509Certificate cert = tryLoadCertInternal(file);
if (X500NameUtils.rfc3280Equal(cert.getSubjectX500Principal(), issuer))
{
toCache.add(new TrustAnchorExt(cert, null));
X500Principal certIssuer = cert.getIssuerX500Principal();
if (!X500NameUtils.rfc3280Equal(certIssuer, issuer))
tryLoadTAFor(certIssuer, toCache);
}
}
ret.addAll(toCache);
cachedAnchorsPerIssuer.put(issuer, new CachedElement<Set<TrustAnchorExt>>(toCache));
}
}