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

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.security.KeyStore;
import java.security.cert.CertPath;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import java.util.Arrays;
import java.util.Collections;

import static org.junit.Assert.*;

import org.apache.commons.io.FileUtils;
import org.junit.Test;

import eu.emi.security.authn.x509.CrlCheckingMode;
import eu.emi.security.authn.x509.OCSPCheckingMode;
import eu.emi.security.authn.x509.OCSPParametes;
import eu.emi.security.authn.x509.ProxySupport;
import eu.emi.security.authn.x509.ValidationError;
import eu.emi.security.authn.x509.StoreUpdateListener;
import eu.emi.security.authn.x509.ValidationErrorListener;
import eu.emi.security.authn.x509.ValidationResult;
import eu.emi.security.authn.x509.impl.CertificateUtils.Encoding;

/**
 * Testing of {@link InMemoryKeystoreCertChainValidator} and {@link KeystoreCertChainValidator}
 * is done here. The tests are in fact designed also to test all their parent classes
 * which provide a lot o shared functionality also for other validators.
 * 
 * @author K. Benedyczak
 */
public class TestKSValidators
{
	private boolean gotError;
	private int vError;
	
	public static File initDir() throws IOException
	{
		File dir = new File("target/test-tmp/truststores");
		FileUtils.deleteDirectory(dir);
		dir.mkdirs();
		return dir;
	}

	/**
	 * Tests creation, basic validation
	 */
	@Test
	public void testKeystoreValidator() throws Exception
	{
		String path = "src/test/resources/truststores/truststore1.jks";
		KeystoreCertChainValidator validator1 = new KeystoreCertChainValidator(
				path, "the!njs".toCharArray(), "JKS", -1, 
				new ValidatorParamsExt(RevocationParametersExt.IGNORE, ProxySupport.DENY));
		X509Certificate[] toValidate = CertificateUtils.loadCertificateChain(
				new FileInputStream("src/test/resources/validator-certs/trusted_client.cert"), 
				Encoding.PEM);
		
		CertificateFactory factory = CertificateFactory.getInstance("X.509");
		CertPath certPath = factory.generateCertPath(Arrays.asList(toValidate));
		
		ValidationResult res = validator1.validate(toValidate);
		assertTrue(res.isValid());
		
		ValidationResult res1 = validator1.validate(certPath);
		assertTrue(res1.isValid());
		
		assertEquals(validator1.getTruststorePath(), path);
		validator1.dispose();
	}

	/**
	 * Tests creation, basic validation
	 */
	@Test
	public void testInMemoryKeystoreValidator() throws Exception
	{
		String path = "src/test/resources/truststores/truststore1.jks";
		KeyStore normalKs = KeyStore.getInstance("JKS");
		normalKs.load(new FileInputStream(path), "the!njs".toCharArray());
		InMemoryKeystoreCertChainValidator validator1 = new InMemoryKeystoreCertChainValidator(
				normalKs, 
				new ValidatorParamsExt(RevocationParametersExt.IGNORE, ProxySupport.DENY));
		X509Certificate[] toValidate = CertificateUtils.loadCertificateChain(
				new FileInputStream("src/test/resources/validator-certs/trusted_client.cert"), 
				Encoding.PEM);
		
		CertificateFactory factory = CertificateFactory.getInstance("X.509");
		CertPath certPath = factory.generateCertPath(Arrays.asList(toValidate));
		
		ValidationResult res = validator1.validate(toValidate);
		assertTrue(res.isValid());
		
		ValidationResult res1 = validator1.validate(certPath);
		assertTrue(res1.isValid());
		
		KeyStore emptyKs = KeyStore.getInstance("JKS");
		emptyKs.load(null);
		validator1.setTruststore(emptyKs);
		ValidationResult res2 = validator1.validate(toValidate);
		assertFalse(res2.isValid());
		assertEquals(validator1.getTruststore(), emptyKs);
		validator1.dispose();
	}

	
	/**
	 * Tests creation, basic validation
	 */
	@Test
	public void testValidationListener() throws Exception
	{
		KeyStore emptyKs = KeyStore.getInstance("JKS");
		emptyKs.load(null);
		InMemoryKeystoreCertChainValidator validator1 = new InMemoryKeystoreCertChainValidator(
				emptyKs, 
				new ValidatorParamsExt(RevocationParametersExt.IGNORE, ProxySupport.DENY));
		X509Certificate[] toValidate = CertificateUtils.loadCertificateChain(
				new FileInputStream("src/test/resources/validator-certs/trusted_client.cert"), 
				Encoding.PEM);
		
		ValidationErrorListener l1 = new ValidationErrorListener()
		{
			public boolean onValidationError(ValidationError error)
			{
				vError++;
				System.out.println("L1: " + error);
				return false;
			}
		};
		ValidationErrorListener l2 = new ValidationErrorListener()
		{
			public boolean onValidationError(ValidationError error)
			{
				vError++;
				System.out.println("L2: " + error);
				return true;
			}
		};
		validator1.addValidationListener(l1);
		
		vError = 0;
		ValidationResult res = validator1.validate(toValidate);
		assertFalse(res.isValid());
		assertEquals(2, vError);
		
		validator1.addValidationListener(l2);
		vError = 0;
		ValidationResult res1 = validator1.validate(toValidate);
		assertTrue(res1.getErrors().toString(), res1.isValid());
		assertEquals(4, vError);
		
		validator1.removeValidationListener(l1);
		vError = 0;
		ValidationResult res2 = validator1.validate(toValidate);
		assertTrue(res2.isValid());
		assertEquals(2, vError);
		
		validator1.dispose();
	}

	
	/**
	 * Tests update and notifications
	 */
	@Test
	public void testKeystoreValidatorUpdate() throws Exception
	{
		File dir = initDir();
		File ks = new File(dir, "work.jks");
		FileUtils.copyFile(new File("src/test/resources/truststores/empty.jks"), ks);
		
		KeystoreCertChainValidator validator1 = new KeystoreCertChainValidator(
				ks.getPath(), "the!njs".toCharArray(), "JKS", -1, new ValidatorParamsExt(
				RevocationParametersExt.IGNORE, ProxySupport.DENY));
		X509Certificate[] toValidate = CertificateUtils.loadCertificateChain(
				new FileInputStream("src/test/resources/validator-certs/trusted_client.cert"), 
				Encoding.PEM);
		gotError = false;
		validator1.addUpdateListener(new StoreUpdateListener()
		{
			@Override
			public void loadingNotification(String location, String type,
					Severity level, Exception cause)
			{
				assertEquals(type, StoreUpdateListener.CA_CERT);
				System.out.println(location + " " + cause);
				gotError = true;
			}
		});
		
		ValidationResult res = validator1.validate(toValidate);
		assertFalse(res.isValid());
		
		validator1.setTruststoreUpdateInterval(200);
		FileUtils.copyFile(new File("src/test/resources/truststores/truststore1.jks"), ks);
		Thread.sleep(500);
		
		ValidationResult res2 = validator1.validate(toValidate);
		assertTrue(res2.isValid());

		ks.delete();
		Thread.sleep(500);
		assertTrue(gotError);
		
		validator1.dispose();
	}
	
	/**
	 * Tests update and notifications
	 */
	@Test
	public void testKeystoreValidatorCRL() throws Exception
	{
		String path = "src/test/resources/truststores/truststore1.jks";
		KeystoreCertChainValidator validator1 = new KeystoreCertChainValidator(
				path, "the!njs".toCharArray(), "JKS", -1,  
				new ValidatorParamsExt(
					new RevocationParametersExt(CrlCheckingMode.REQUIRE, new CRLParameters(), 
							new OCSPParametes(OCSPCheckingMode.IGNORE)),
					ProxySupport.DENY));
		X509Certificate[] toValidate1 = CertificateUtils.loadCertificateChain(
				new FileInputStream("src/test/resources/validator-certs/trusted_client.cert"), 
				Encoding.PEM);
		X509Certificate[] toValidate2 = CertificateUtils.loadCertificateChain(
				new FileInputStream("src/test/resources/validator-certs/trusted_client_rev.cert"), 
				Encoding.PEM);
		
		ValidationResult res = validator1.validate(toValidate1);
		assertFalse(res.isValid());
		ValidationResult res2 = validator1.validate(toValidate2);
		assertFalse(res2.isValid());
		
		File dir = initDir();
		validator1.setCrls(Collections.singletonList(dir.getPath() + "/*.crl"));
		
		res = validator1.validate(toValidate1);
		assertFalse(res.isValid());
		res2 = validator1.validate(toValidate2);
		assertFalse(res2.isValid());
		
		validator1.setCRLUpdateInterval(200);
		FileUtils.copyFile(new File("src/test/resources/truststores/maincacrl.pem"), new File(dir, "crl1.crl"));
		Thread.sleep(500);
		
		res = validator1.validate(toValidate1);
		assertTrue(res.isValid());
		res2 = validator1.validate(toValidate2);
		assertFalse(res2.isValid());

		
		
		validator1.dispose();
	}
	
	/**
	 * Tests self-signed certificate which is not in the truststore.
	 * This should fail.
	 */
	@Test
	public void testInvalidSelfSigned() throws Exception
	{
		String path = "src/test/resources/truststores/empty.jks";
		KeystoreCertChainValidator validator1 = new KeystoreCertChainValidator(path, 
			"the!njs".toCharArray(), "JKS", -1);
		X509Certificate[] toValidate = new KeystoreCredential("src/test/resources/selfsigned.jks",
			"the!client".toCharArray(), "the!client".toCharArray(), "mykey", "JKS").getCertificateChain();
		ValidationResult res = validator1.validate(toValidate);
		assertFalse(res.isValid());
	}

	/**
	 * Tests self-signed certificate which is one of trust anchors
	 * Note: this should succeed as issuer (==the checked cert) is trusted
	 */
	@Test
	public void testSelfSignedTA() throws Exception
	{
		String path = "src/test/resources/selfsigned.jks";
		KeystoreCertChainValidator validator1 = new KeystoreCertChainValidator(path, 
			"the!client".toCharArray(), "JKS", -1);
		X509Certificate[] toValidate = new KeystoreCredential(path,
			"the!client".toCharArray(), "the!client".toCharArray(), "mykey", "JKS").getCertificateChain();
		ValidationResult res = validator1.validate(toValidate);
		assertTrue(res.isValid());
	}

	/**
	 * Tests non-self signed certificate which is one of trust anchors
	 * Note: this should fail as *issuer is not trusted*
	 */
	@Test
	public void testNonSelfSignedTA() throws Exception
	{
		String path = "src/test/resources/nonselfsigned.jks";
		KeystoreCertChainValidator validator1 = new KeystoreCertChainValidator(path, 
			"the!client".toCharArray(), "JKS", -1);
		X509Certificate[] toValidate = new KeystoreCredential(path,
			"the!client".toCharArray(), "the!client".toCharArray(), "httpclient", "JKS").getCertificateChain();
		ValidationResult res = validator1.validate(toValidate);
		System.out.println(res);
		assertFalse(res.isValid());
	}
	
	/**
	 * Simple test using PKCS12 as truststore
	 */
	@Test
	public void testPkcs12Truststore() throws Exception
	{
		String path = "src/test/resources/truststore.p12";
		KeystoreCertChainValidator validator1 = new KeystoreCertChainValidator(path, 
			"the!njs".toCharArray(), "PKCS12", -1);
		assertEquals(1, validator1.getTrustedIssuers().length);
	}	
}
