/*
 * Copyright (c) 2011-2012 ICM Uniwersytet Warszawski All rights reserved.
 * See LICENCE file for licensing information.
 */
package eu.emi.security.authn.x509.impl;


import java.security.InvalidAlgorithmParameterException;
import java.security.cert.TrustAnchor;
import java.security.cert.X509Certificate;
import java.util.List;
import java.util.Set;
import java.util.Timer;

import eu.emi.security.authn.x509.NamespaceCheckingMode;
import eu.emi.security.authn.x509.ValidationError;
import eu.emi.security.authn.x509.ValidationResult;
import eu.emi.security.authn.x509.helpers.crl.AbstractCRLStoreSPI;
import eu.emi.security.authn.x509.helpers.crl.LazyOpensslCRLStoreSpi;
import eu.emi.security.authn.x509.helpers.crl.OpensslCRLStoreSpi;
import eu.emi.security.authn.x509.helpers.ns.NamespaceChecker;
import eu.emi.security.authn.x509.helpers.pkipath.AbstractValidator;
import eu.emi.security.authn.x509.helpers.trust.LazyOpensslTrustAnchorStoreImpl;
import eu.emi.security.authn.x509.helpers.trust.OpensslTrustAnchorStore;
import eu.emi.security.authn.x509.helpers.trust.OpensslTrustAnchorStoreImpl;


/**
 * The certificate validator which uses OpenSSL directory as a truststore. The validator can work in two modes:
 * the default <b>lazy</b> mode when the truststore contents is loaded on-demand and in a classic mode,
 * when the whole truststore is loaded to the memory at startup. The latter mode can be useful for server-side
 * as allows to get an information about truststore problems (as expired certificates or invalid files) at startup.
 * Also the performance characteristic is better: validation can be faster and operation time more stable.
 * Unfortunately both advantages are at the cost of a longer initialization time and bigger memory footprint.
 * Therefore the lazy mode is strongly suggested for client tools, where this is a concern.
 * 
 *   
 * @author K. Benedyczak
 */
public class OpensslCertChainValidator extends AbstractValidator
{
	private OpensslTrustAnchorStore trustStore;
	private AbstractCRLStoreSPI crlStore;
	private final NamespaceCheckingMode namespaceMode;
	private String path;
	private final boolean lazyMode;
	protected static final Timer timer=new Timer("caNl validator (openssl) timer", true);

	/**
	 * Constructs a new validator instance. This version is equivalent to the {@link #OpensslCertChainValidator(String, boolean, NamespaceCheckingMode, long, ValidatorParams, boolean)}
	 * with the legacy (pre 1.0) format of the truststore and the lazy mode turned on.
	 *  
	 * @param directory path where trusted certificates are stored.
	 * @param namespaceMode specifies how certificate namespaces should be handled
	 * @param updateInterval specifies in miliseconds how often the directory should be 
	 * checked for updates. The files are reloaded only if their modification timestamp
	 * was changed since last load. Use a &lt;= 0 value to disable automatic updates.
	 * @param params common validator settings (revocation, initial listeners, proxy support, ...) 
	 */
	public OpensslCertChainValidator(String directory, NamespaceCheckingMode namespaceMode, 
			long updateInterval, ValidatorParams params)
	{
		this(directory, false, namespaceMode, updateInterval, params, true);
	}
	
	/**
	 * Constructs a new validator instance. This validator will work in the lazy mode. See 
	 * {@link #OpensslCertChainValidator(String, boolean, NamespaceCheckingMode, long, ValidatorParams, boolean)}
	 * for details. 
	 *  
	 * @param directory path where trusted certificates are stored.
	 * @param openssl1Mode if true then truststore is with hashes in openssl 1+ format. Otherwise
	 * the openssl 0.x format is used. 
	 * @param namespaceMode specifies how certificate namespaces should be handled
	 * @param updateInterval specifies in miliseconds how often the directory should be 
	 * checked for updates. The files are reloaded only if their modification timestamp
	 * was changed since last load. Use a &lt;= 0 value to disable automatic updates.
	 * @param params common validator settings (revocation, initial listeners, proxy support, ...) 
	 */
	public OpensslCertChainValidator(String directory, boolean openssl1Mode, NamespaceCheckingMode namespaceMode, 
			long updateInterval, ValidatorParams params)
	{
		this(directory, openssl1Mode, namespaceMode, updateInterval, params, true);
	}	
	
	/**
	 * Constructs a new validator instance.
	 *  
	 * @since 2.0.0
	 * @param directory path where trusted certificates are stored.
	 * @param openssl1Mode if true then truststore is with hashes in openssl 1+ format. Otherwise
	 * the openssl 0.x format is used. 
	 * @param namespaceMode specifies how certificate namespaces should be handled
	 * @param updateInterval specifies in miliseconds how often the directory should be 
	 * checked for updates. The files are reloaded only if their modification timestamp
	 * was changed since last load. Use a &lt;= 0 value to disable automatic updates.
	 * @param params common validator settings (revocation, initial listeners, proxy support, ...)
	 * @param lazyMode if true then certificates, CRLs and namespace definitions are loaded on-demand
	 *  (with in-memory caching). If false then the whole truststore contents is loaded at startup and kept in memory. 
	 */
	public OpensslCertChainValidator(String directory, boolean openssl1Mode, NamespaceCheckingMode namespaceMode, 
			long updateInterval, ValidatorParams params, boolean lazyMode)
	{
		super(params.getInitialListeners());
		path = directory;
		this.lazyMode = lazyMode;
		this.namespaceMode = namespaceMode;
		trustStore = lazyMode ?  
				new LazyOpensslTrustAnchorStoreImpl(directory, updateInterval, 
						observers, openssl1Mode)
				:
				new OpensslTrustAnchorStoreImpl(directory, timer, updateInterval, 
						namespaceMode.globusEnabled(), namespaceMode.euGridPmaEnabled(), 
						observers, openssl1Mode);
		try
		{
			crlStore = lazyMode ? 
				new LazyOpensslCRLStoreSpi(directory, updateInterval, observers, openssl1Mode)
				:
				new OpensslCRLStoreSpi(directory, updateInterval, timer, observers, openssl1Mode);
		} catch (InvalidAlgorithmParameterException e)
		{
			throw new RuntimeException("BUG: OpensslCRLStoreSpi " +
					"can not be initialized", e);
		}
		init(trustStore, crlStore, params.isAllowProxy(), params.getRevocationSettings());
	}
	
	/**
	 * Constructs a new validator instance with default additional settings
	 * (see {@link ValidatorParams#ValidatorParams()}).
	 * 
	 * The legacy, pre openssl 1.0 format of the truststore is used as well as the lazy loading mode.
	 *  
	 * @param directory path where trusted certificates are stored.
	 * @param namespaceMode specifies how certificate namespaces should be handled
	 * @param updateInterval specifies in miliseconds how often the directory should be 
	 * checked for updates. The files are reloaded only if their modification timestamp
	 * was changed since last load.
	 */
	public OpensslCertChainValidator(String directory, NamespaceCheckingMode namespaceMode, 
			long updateInterval)
	{
		this(directory, namespaceMode, updateInterval, new ValidatorParams());
	}

	/**
	 * Constructs a new validator instance using the default settings:
	 * CRLs are used if present, proxy certificates are supported and
	 * directory is rescanned every 10mins. EuGridPMA namespaces are checked in the first place,
	 * if not found then Globus EACLs are tried. Lack of namespaces is ignored.
	 * 
	 * The legacy, pre openssl 1.0 format of the truststore is used as well as the lazy loading mode.
	 *  
	 * @param directory path where trusted certificates are stored.
	 */
	public OpensslCertChainValidator(String directory)
	{
		this(directory, NamespaceCheckingMode.EUGRIDPMA_GLOBUS, 600000, 
			new ValidatorParamsExt());
	}
	
	/**
	 * Returns the trusted certificates directory path
	 * @return the path
	 */
	public String getTruststorePath()
	{
		return path;
	}
	
	/**
	 * Returns the namespace checking mode.
	 * @return the namespace mode
	 */
	public NamespaceCheckingMode getNamespaceCheckingMode()
	{
		return namespaceMode;
	}
	
	/**
	 * Returns the interval between subsequent checks of the trusted certificates
	 * directory. Note that files are actually reread only if their modification
	 * time has changed.
	 * @return the current refresh interval in milliseconds
	 */
	public long getUpdateInterval()
	{
		return trustStore.getUpdateInterval();
	}

	/**
	 * Sets a new interval between subsequent checks of the trusted certificates
	 * directory. Note that files are actually reread only if their modification
	 * time has changed.
	 * @param updateInterval the new interval to be set in milliseconds
	 */
	public void setUpdateInterval(long updateInterval)
	{
		trustStore.setUpdateInterval(updateInterval);
		crlStore.setUpdateInterval(updateInterval);
	}

	@Override
	public void dispose()
	{
		super.dispose();
		trustStore.dispose();
		crlStore.dispose();
	}
	
	/**
	 * {@inheritDoc}
	 */
	@Override
	public ValidationResult validate(X509Certificate[] certChain)
	{
		Set<TrustAnchor> anchors;
		if (lazyMode)
		{
			LazyOpensslTrustAnchorStoreImpl lazyTAStore = (LazyOpensslTrustAnchorStoreImpl) trustStore;
			anchors = lazyTAStore.getTrustAnchorsFor(certChain);
		} else
		{
			anchors = trustStore.getTrustAnchors(); 
		}
		ValidationResult result = super.validate(certChain, anchors); 
		
		
		NamespaceChecker checker = new NamespaceChecker(namespaceMode, trustStore.getPmaNsStore(), 
				trustStore.getGlobusNsStore());
		List<ValidationError> errors = checker.check(certChain);
		processErrorList(errors);
		result.addErrors(errors);

		return result;
	}
}





