/**
* Copyright (c) Istituto Nazionale di Fisica Nucleare, 2006-2014.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.italiangrid.voms.store.impl;
import java.io.File;
import java.io.FileFilter;
import java.io.FileInputStream;
import java.io.FilenameFilter;
import java.io.IOException;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import javax.security.auth.x500.X500Principal;
import org.italiangrid.voms.VOMSError;
import org.italiangrid.voms.store.LSCInfo;
import org.italiangrid.voms.store.VOMSTrustStore;
import org.italiangrid.voms.store.VOMSTrustStoreStatusListener;
import org.italiangrid.voms.util.NullListener;
import eu.emi.security.authn.x509.helpers.trust.OpensslTruststoreHelper;
import eu.emi.security.authn.x509.impl.CertificateUtils;
import eu.emi.security.authn.x509.impl.CertificateUtils.Encoding;
/**
*
* The default implementation for the VOMS trust store. This implementation
* <b>does not</b> refresh the trust information on a periodic basis. For an
* updating trust store see {@link DefaultUpdatingVOMSTrustStore}.
*
* @author Andrea Ceccanti
*
*/
public class DefaultVOMSTrustStore implements VOMSTrustStore {
/**
* The default directory where local VOMS trust information is rooted:
* {@value #DEFAULT_VOMS_DIR}
**/
public static final String DEFAULT_VOMS_DIR = "/etc/grid-security/vomsdir";
/**
* The filename suffix used to match certificates in the VOMS local trust
* directories
**/
public static final String CERTIFICATE_FILENAME_SUFFIX = ".pem";
/**
* The filename suffix used to match LSC files in the VOMS local trust
* directories
**/
public static final String LSC_FILENAME_SUFFIX = ".lsc";
/**
* The list of local trusted directories that is searched for trust
* information (certs or LSC files)
**/
private final List<String> localTrustedDirs;
/** Map of local parsed AA certificates keyed by certificate subject hash **/
private Map<String, X509Certificate> localAACertificatesByHash = new HashMap<String, X509Certificate>();
/** The set of local parsed LSC information keyed by VO **/
private Map<String, Set<LSCInfo>> localLSCInfo = new HashMap<String, Set<LSCInfo>>();
/**
* The trust store status listener that will be notified of changes in this
* trust store
**/
private VOMSTrustStoreStatusListener listener;
/** The read/write lock that implements thread safety for this store **/
protected final ReadWriteLock rwLock = new ReentrantReadWriteLock();
/** A reference to the read lock **/
protected final Lock read = rwLock.readLock();
/** A reference to the write lock **/
protected final Lock write = rwLock.writeLock();
/** A lock to guard the setting of the status listener **/
protected final Object listenerLock = new Object();
/**
* Builds a list of trusted directories containing only
* {@link #DEFAULT_VOMS_DIR}.
*
* @return a list of default trusted directory containing the
* {@link #DEFAULT_VOMS_DIR}
**/
protected static List<String> buildDefaultTrustedDirs() {
List<String> tDirs = new ArrayList<String>();
tDirs.add(DEFAULT_VOMS_DIR);
return tDirs;
}
/**
*
* @param localTrustDirs
* a non-null list of local trust directories
* @param listener
* the {@link VOMSTrustStoreStatusListener} to use for this trust
* store
* @throws IllegalArgumentException
* when the list passed as argument is null
*
*/
public DefaultVOMSTrustStore(List<String> localTrustDirs,
VOMSTrustStoreStatusListener listener) {
if (localTrustDirs == null)
throw new IllegalArgumentException(
"Please provide a non-null list of local trust directories!");
this.localTrustedDirs = localTrustDirs;
this.listener = listener;
loadTrustInformation();
}
public DefaultVOMSTrustStore(VOMSTrustStoreStatusListener listener) {
this(buildDefaultTrustedDirs(), listener);
}
public DefaultVOMSTrustStore(List<String> localTrustDirs) {
this(localTrustDirs, NullListener.INSTANCE);
}
/**
* Default constructor.
*
* Sets the local trusted directories to the default of
* {@value #DEFAULT_VOMS_DIR}.
*
*
*/
public DefaultVOMSTrustStore() {
this(buildDefaultTrustedDirs());
}
public List<String> getLocalTrustedDirectories() {
read.lock();
try {
return localTrustedDirs;
} finally {
read.unlock();
}
}
public List<X509Certificate> getLocalAACertificates() {
read.lock();
try {
return Collections.unmodifiableList(new ArrayList<X509Certificate>(
localAACertificatesByHash.values()));
} finally {
read.unlock();
}
}
public LSCInfo getLSC(String voName, String hostname) {
read.lock();
try {
Set<LSCInfo> candidates = localLSCInfo.get(voName);
if (candidates == null)
return null;
for (LSCInfo lsc : candidates) {
if (lsc.getHostname().equals(hostname))
return lsc;
}
return null;
} finally {
read.unlock();
}
}
/**
* Loads all the certificates in the local directory. Only files with the
* extension matching the {@link #CERTIFICATE_FILENAME_PATTERN} are
* considered.
*
* @param directory
*/
private void loadCertificatesFromDirectory(File directory) {
directorySanityChecks(directory);
synchronized (listenerLock) {
listener.notifyCertficateLookupEvent(directory.getAbsolutePath());
}
File[] certFiles = directory.listFiles(new FilenameFilter() {
public boolean accept(File dir, String name) {
return name.endsWith(CERTIFICATE_FILENAME_SUFFIX);
}
});
for (File f : certFiles)
loadCertificateFromFile(f);
}
/**
* Loads a VOMS AA certificate from a given file and stores this certificate
* in the local map of trusted VOMS AA certificate.
*
* @param file
*/
private void loadCertificateFromFile(File file) {
certificateFileSanityChecks(file);
try {
X509Certificate aaCert = CertificateUtils.loadCertificate(
new FileInputStream(file), Encoding.PEM);
// Get certificate subject hash, using the CANL implementation for CA
// files
String aaCertHash = getOpensslCAHash(aaCert.getSubjectX500Principal());
// Store certificate in the local map
localAACertificatesByHash.put(aaCertHash, aaCert);
synchronized (listenerLock) {
listener.notifyCertificateLoadEvent(aaCert, file);
}
} catch (IOException e) {
String errorMessage = String.format(
"Error parsing VOMS trusted certificate from %s. Reason: %s",
file.getAbsolutePath(), e.getMessage());
throw new VOMSError(errorMessage, e);
}
}
/**
*
* @param directory
*/
private void loadLSCFromDirectory(File directory) {
directorySanityChecks(directory);
synchronized (listenerLock) {
listener.notifyLSCLookupEvent(directory.getAbsolutePath());
}
File[] lscFiles = directory.listFiles(new FilenameFilter() {
public boolean accept(File dir, String name) {
return name.endsWith(LSC_FILENAME_SUFFIX);
}
});
if (lscFiles.length == 0)
return;
DefaultLSCFileParser lscParser = new DefaultLSCFileParser();
// In the VOMS trust anchor structure, LSC files are contained in a
// directory named
// as the VO the LSC belongs to
String voName = directory.getName();
for (File lsc : lscFiles) {
String lscFileName = lsc.getName();
// In the VOMS trust anchor structure, LSC files are named as
// <hostname>.lsc where hostname
// is the name of host where the VOMS AA is running
String hostname = lscFileName.substring(0,
lscFileName.indexOf(LSC_FILENAME_SUFFIX));
LSCInfo info = null;
info = lscParser.parse(voName, hostname, lsc);
Set<LSCInfo> localLscForVo = localLSCInfo.get(voName);
if (localLscForVo == null) {
localLscForVo = new HashSet<LSCInfo>();
localLSCInfo.put(voName, localLscForVo);
}
localLscForVo.add(info);
listener.notifyLSCLoadEvent(info, lsc);
}
}
/**
* Performs basic sanity checks performed on a file supposed to hold a VOMS AA
* certificate.
*
* @param certFile
*/
private void certificateFileSanityChecks(File certFile) {
if (!certFile.exists())
throw new VOMSError("Local VOMS trusted certificate does not exist:"
+ certFile.getAbsolutePath());
if (!certFile.canRead())
throw new VOMSError("Local VOMS trusted certificate is not readable:"
+ certFile.getAbsolutePath());
}
/**
* Performs basic sanity checks on a directory that is supposed to contain
* VOMS AA certificates and LSC files.
*
* @param directory
*/
private void directorySanityChecks(File directory) {
if (!directory.exists())
throw new VOMSError("Local trust directory does not exists:"
+ directory.getAbsolutePath());
if (!directory.isDirectory())
throw new VOMSError("Local trust directory is not a directory:"
+ directory.getAbsolutePath());
if (!directory.canRead())
throw new VOMSError("Local trust directory is not readable:"
+ directory.getAbsolutePath());
if (!directory.canExecute())
throw new VOMSError("Local trust directory is not traversable:"
+ directory.getAbsolutePath());
}
private void cleanupStores() {
localAACertificatesByHash.clear();
localLSCInfo.clear();
}
public void loadTrustInformation() {
write.lock();
try {
if (localTrustedDirs.isEmpty()) {
throw new VOMSError(
"No local trust directory was specified for this trust store. Please provide at least one path where LSC and VOMS service certificates will be searched for.");
}
cleanupStores();
for (String localDir : localTrustedDirs) {
File baseTrustDir = new File(localDir);
// Legacy VOMS dir structure put all the certificates in the base trust
// directory
loadCertificatesFromDirectory(baseTrustDir);
// Load LSC and certificates files starting from each of the
// sub-directory of the starting trust info directory
File[] voDirs = baseTrustDir.listFiles(new FileFilter() {
public boolean accept(File pathname) {
return pathname.isDirectory();
}
});
for (File voDir : voDirs) {
loadLSCFromDirectory(voDir);
loadCertificatesFromDirectory(voDir);
}
}
} finally {
write.unlock();
}
}
private String getOpensslCAHash(X500Principal principal) {
return OpensslTruststoreHelper.getOpenSSLCAHash(principal, false);
}
public X509Certificate getAACertificateBySubject(X500Principal aaCertSubject) {
read.lock();
try {
String theCertHash = getOpensslCAHash(aaCertSubject);
return localAACertificatesByHash.get(theCertHash);
} finally {
read.unlock();
}
}
public Map<String, Set<LSCInfo>> getAllLSCInfo() {
read.lock();
try {
return Collections.unmodifiableMap(localLSCInfo);
} finally {
read.unlock();
}
}
public void setStatusListener(VOMSTrustStoreStatusListener statusListener) {
synchronized (listenerLock) {
this.listener = statusListener;
}
}
}