/* * 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)); } }