package testgenerator;
import iaik.ixsil.init.IXSILInit;
import iaik.ixsil.util.DOMUtilsImpl;
import iaik.ixsil.util.URI;

import iaik.asn1.ASN1Object;
import iaik.asn1.ObjectID;
import iaik.asn1.structures.AlgorithmID;
import iaik.asn1.structures.Attribute;
import iaik.asn1.structures.ChoiceOfTime;
import iaik.asn1.structures.Name;
import iaik.cms.CMSException;
import iaik.cms.ContentInfo;
import iaik.cms.IssuerAndSerialNumber;
import iaik.cms.SignedData;
import iaik.cms.SignerInfo;
import iaik.pkcs.pkcs12.CertificateBag;
import iaik.pkcs.pkcs12.KeyBag;
import iaik.pkcs.pkcs12.PKCS12;
import iaik.security.ecc.interfaces.ECDSAPrivateKey;
import iaik.security.provider.IAIK;
import iaik.utils.Base64OutputStream;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.BufferedWriter;
import java.io.ByteArrayOutputStream;
import java.io.CharArrayWriter;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.FileWriter;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.math.BigInteger;
import java.security.NoSuchAlgorithmException;
import java.security.cert.X509Certificate;
import java.security.interfaces.RSAPrivateKey;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.Properties;

import javax.xml.transform.OutputKeys;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerConfigurationException;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.TransformerFactoryConfigurationError;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;

import org.w3c.dom.Document;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

/**
 * Base class for all tutorial units. 
 * Provides some basic functionality, such as properties and signature
 * serialization.
 */
public class TestCases
{
	public String Node2String(Node outputNode)
		throws
			TransformerFactoryConfigurationError,
			TransformerConfigurationException,
			TransformerException {
		CharArrayWriter caw = new CharArrayWriter();
		TransformerFactory transformerFactory =
			TransformerFactory.newInstance();
		Transformer transformer = transformerFactory.newTransformer();
		transformer.setOutputProperty(OutputKeys.METHOD, "xml");
		transformer.setOutputProperty(OutputKeys.ENCODING, "utf-8");
		transformer.transform(new DOMSource(outputNode), new StreamResult(caw));

		String erg = caw.toString();
		return erg;
	}

	public void findNode(Node base,String name,ArrayList foundNodes)
	{
		findNode(base,name,foundNodes,-1);
	}
	

	public void findNode(Node base,String name,ArrayList foundNodes, int max_level)
	{
		findNode(base,name,foundNodes,max_level,0);	
	}
	

	public void findNode(Node base,String name,ArrayList foundNodes, int max_level, int level)
	{
		if(max_level!=-1 && max_level<=level) return;
		//System.out.println("FINDNODE "+name);
		//System.out.println("CHECKING "+base.getNodeName());
		if(base.getNodeName().equals(name))
		{
			//System.out.println("ADD BASE !"+name);
			foundNodes.add(base);
		}		
		
		NodeList children = base.getChildNodes();
		int size = children.getLength();
		for(int counter=0;counter<size;counter++)
		{
			findNode(children.item(counter),name,foundNodes,max_level,level+1);
		}
	}

	Properties configuration_;
	boolean[] variations_;

	/* ==================================================================================================== */

	public TestCases() throws Exception
	{
		// Set some basic configuration properties
		configuration_ = new Properties();

    String baseDir = "e:/cio/projekte/basismodule/wartung/projekt/spss.test/";
    String webBaseDir = "http://localhost:8080/moa-spss-testdata/";

		configuration_.setProperty("baseDir", baseDir);
		configuration_.setProperty("webbaseDir", webBaseDir);

		configuration_.setProperty("PKCS12file", (baseDir + "/resources/test-ee2003_normal(buergerkarte).p12"));
		configuration_.setProperty("PKCS12password", "buergerkarte");

    configuration_.setProperty("ECDSPKCS12file", baseDir + "/resources/ecc(ego).p12");
    configuration_.setProperty("ECDSPKCS12password", "ego");

		configuration_.setProperty("IXSILInitPropertiesURI", "file:/" + baseDir + "resources/init.properties");
			
		configuration_.setProperty("CERT", baseDir + "resources/test-ee2003_normal_extract.cer");	

		// Initialize IXSIL
		IXSILInit.init(new URI(configuration_.getProperty("IXSILInitPropertiesURI")));

		// Switch on debug information
		IXSILInit.setPrintDebugLog(true);

		// Add IAIK JCE provider
		IAIK.addAsProvider();
	}
	
	public String X509name = null;
	public BigInteger X509number = null;
	public String X509hash = null;
	public String X509sub = null;
	public iaik.x509.X509Certificate user1_sign = null;
	
	public void getX509Content() throws Exception
	{
		BufferedInputStream bis = new BufferedInputStream(new FileInputStream(configuration_.getProperty("CERT")));
		iaik.x509.X509Certificate cert = new iaik.x509.X509Certificate(bis);
		X509name = ((Name)(cert.getIssuerDN())).getRFC2253String();
		X509number = cert.getSerialNumber();
		X509hash = new String(cert.getFingerprintSHA());
		X509sub = ((Name)(cert.getSubjectDN())).getRFC2253String();
	}

	/* ==================================================================================================== */

	public void serialize2File(Document signature, String fileName) throws Exception
	{
		FileOutputStream signatureFIS = new FileOutputStream(fileName);
    DOMUtilsImpl.serializeDocument(signature, signatureFIS);
	}
	
	/* ==================================================================================================== */

	public static HashMap pkcs12cache = new HashMap();

	public PKCS12 decryptPKCS12( String pkcs12file, String password ) throws Exception
	{
		if(pkcs12cache.containsKey(pkcs12file)) return (PKCS12) pkcs12cache.get(pkcs12file);
		PKCS12 pkcs12 = new PKCS12(new FileInputStream(pkcs12file));
		
//		if (!pkcs12.verify(password.toCharArray())) {
//			System.out.println("could not verify pkcs12 " + pkcs12.toString() + " with password " + password);
//		} else {
//			System.out.println("verified pkcs12 " + pkcs12.toString() + " with password " + password);
//		}
			
		pkcs12.decrypt(password.toCharArray());
		System.out.println("decrypted pkcs12  " + pkcs12.toString() + " with password " + password);
		pkcs12cache.put(pkcs12file,pkcs12);
		return pkcs12;
	}

	/* ==================================================================================================== */

	public static HashMap privkeycache = new HashMap();

	public RSAPrivateKey getPrivateKey( PKCS12 pkcs12 ) throws Exception
	{
		if(privkeycache.containsKey(pkcs12)) return (RSAPrivateKey)privkeycache.get(pkcs12);
		KeyBag[] keyBags = pkcs12.getKeyBags();
		System.out.println("PKCS12.getKeyBags(): " + keyBags.length + " KeyBags found");
		privkeycache.put(pkcs12,keyBags[0].getPrivateKey());
		return (RSAPrivateKey) keyBags[0].getPrivateKey();
	}
	
	public static HashMap ecdsaprivkeycache = new HashMap();
	
	public ECDSAPrivateKey getPrivateKeyECDS( PKCS12 pkcs12 ) throws Exception
	{
		if(ecdsaprivkeycache.containsKey(pkcs12)) return (ECDSAPrivateKey)ecdsaprivkeycache.get(pkcs12);
		KeyBag[] keyBags = pkcs12.getKeyBags();
		System.out.println("PKCS12.getKeyBags(): " + keyBags.length + " KeyBags found");
		ecdsaprivkeycache.put(pkcs12,keyBags[0].getPrivateKey());
		return (ECDSAPrivateKey) keyBags[0].getPrivateKey();
	}

	/* ==================================================================================================== */

	public static HashMap x509cache = new HashMap();

	public X509Certificate[] getCertificates( PKCS12 pkcs12 ) throws Exception
	{
		if(x509cache.containsKey(pkcs12)) return (X509Certificate[])x509cache.get(pkcs12);
		X509Certificate[] ret = CertificateBag.getCertificates(pkcs12.getCertificateBags());
		x509cache.put(pkcs12,ret);
		return ret;
	}
	
	/* ==================================================================================================== */
	/* ==================Created and or changed Methods by Stefan Knirsch================================== */
	/* ==================================================================================================== */			
	
	public String vxReqFile(String testNumber)
	{
		return configuration_.getProperty("baseDir") +  
					"/data/VX0/"+
		            configuration_.getProperty("TestClass") +
		            "." + 
		            testNumber + 
		            ".Req.xml";		
	}
	
	public String vxResFile(String testNumber)
	{
		return configuration_.getProperty("baseDir") +  
					"/data/VX0/"+
		            configuration_.getProperty("TestClass") +
		            "." + 
		            testNumber + 
		            ".Res.xml";		
	}	
	
	public String vxReqFileL(String testNumber,String filename)
	{
		return configuration_.getProperty("baseDir") +  
					"/data/LVX"+filename+"/"+
		            configuration_.getProperty("TestClass") +
		            "." + 
		            testNumber + 
		            ".Req.xml";		
	}
	
	public String vxResFileL(String testNumber,String filename)
	{
		return configuration_.getProperty("baseDir") +  
					"/data/LVX"+filename+"/"+
		            configuration_.getProperty("TestClass") +
		            "." + 
		            testNumber + 
		            ".Res.xml";		
	}	
	
	public void createVXConfig() throws Exception {

		String file =
			configuration_.getProperty("baseDir")
				+ "resources/"
				+ configuration_.getProperty("TestClass")
				+ ".Config.xml";
		String config =
			"<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"+
			"<MOAConfiguration xmlns=\"http://reference.e-government.gv.at/namespace/moaconfig/20021122#\">"
				+ "<TrustProfile id=\"TrustProfile1\" uri=\"file://SERVER_WORKING_DIRECTORY/TrustProfile1\"/>"
				+ "<SupplementProfile id=\"SupplementProfile1\" uri=\"file://SERVER_WORKING_DIRECTORY/SupplementProfile1\"/>"
				+ "<SupplementProfile id=\"SupplementProfile2\" uri=\"file://SERVER_WORKING_DIRECTORY/SupplementProfile2\"/>"
				+ "</MOAConfiguration>";

		FileOutputStream fos = new FileOutputStream(file);
		fos.write(config.getBytes());
	}
	
	/**
	 * Method replaceString.
	 * @param input: String to be changed
	 * @param oldPart: subString in input to be changed
	 * @param newPart: new subString instead of the oldPart
	 * @return String
	 * @throws Exception
	 */
	public static String replaceString(
		String input,
		String oldPart,
		String newPart)
		throws Exception {
		String erg = null;

		//First Part
		erg = input.substring(0, input.indexOf(oldPart));
		//Insert new Part
		erg += newPart;

		//insert REST
		erg
			+= input.substring(
				input.indexOf(oldPart) + oldPart.length(),
				input.length());

		return erg;
	}
	
	public static String replaceStringAll(
		String input,
		String oldPart,
		String newPart)
		throws Exception {

		String erg = input;

		while(true)
		{
	
			//First Part
			int pos = input.indexOf(oldPart);
			if(pos==-1) break;
			erg = input.substring(0, pos);
			
			//Insert new Part
			erg += newPart;
	
			//insert REST
			erg
				+= input.substring(
					input.indexOf(oldPart) + oldPart.length(),
					input.length());
			
			input = erg;
		}
		return erg;
	}
	
	/**
	 * Method readFile.
	 * @param filename
	 * @return String
	 * @throws Exception
	 */
	
	public String readFile(String filename) throws Exception {

		/*StringBuffer data = new StringBuffer();
		String line = null;
		BufferedReader br = new BufferedReader(new FileReader(filename));
		while ((line = br.readLine()) != null) {
			data.append(line);
			data.append("\n");
		}
		*/
		RandomAccessFile raf = new RandomAccessFile(filename, "r");
		if (raf.length() > Integer.MAX_VALUE)
			throw new IOException("file too big to fit in byte array.");
		
		byte[] result = new byte[(int) raf.length()];
				
		raf.read(result);
		
		return new String(result);

	}
	/**
	 * Method readBinaryFileAsBase64.
	 * @param filename
	 * @return Stringrepresentation as Base64 of the inputfile and saves that file
	 * @throws Exception
	 */
	public String readBinaryFileAsBase64_new(String filename) throws Exception {

		RandomAccessFile raf = new RandomAccessFile(filename, "r");
		if (raf.length() > Integer.MAX_VALUE)
			throw new IOException("file too big to fit in byte array.");
		byte[] result = new byte[(int) raf.length()];
		//READ the original binary Data
		raf.read(result);

		//Convert the data to bas64 and store it in a new file
		
		ByteArrayOutputStream fos = new ByteArrayOutputStream();
		Base64OutputStream base64os = new Base64OutputStream(fos);
		base64os.write(result);
		base64os.flush();

		return fos.toString();

	}
	public String readBinaryFileAsBase64(String filename) throws Exception {

		RandomAccessFile raf = new RandomAccessFile(filename, "r");
		if (raf.length() > Integer.MAX_VALUE)
			throw new IOException("file too big to fit in byte array.");
		byte[] result = new byte[(int) raf.length()];
		//READ the original binary Data
		raf.read(result);

		//Convert the data to bas64 and store it in a new file
		FileOutputStream fos = new FileOutputStream(filename + "base64.enc");
		Base64OutputStream base64os = new Base64OutputStream(fos);
		base64os.write(result);
		base64os.flush();
		base64os.close();

		//read the converted data und return it
		raf = new RandomAccessFile(filename + "base64.enc", "r");
		if (raf.length() > Integer.MAX_VALUE)
			throw new IOException("Converted base64 file too big to fit in byte array.");
		result = new byte[(int) raf.length()];
		//READ the original binary Data
		raf.read(result);

		return new String(result);

	}
	
	/**
	 * Method writeFile.
	 * @param filename
	 * @param data
	 * @throws Exception
	 */
	public void writeFile(String filename, String data) throws Exception {
		BufferedWriter bw = new BufferedWriter(new FileWriter(filename));
		bw.write(data);
		bw.close();
	}
	
	/**
	 * Method writeFileBinary
	 * @param filename
	 * @param data
	 * @throws Exception
	 */
	public void writeFileBinary(String filename, byte[] data) throws Exception {
		BufferedOutputStream bw = new BufferedOutputStream(new FileOutputStream(filename));
		bw.write(data);
		bw.close();
	}
		/**
	 * Method getDate.
	 * @param changeHours to change the time into the past or future
	 * @return String
	 */

	public String getDate(long changeHours) {

		//Use the XML-Format for the Time
		SimpleDateFormat formatter =
			new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'+01:00");
		//get the current Time
		Date currentTime = new Date();
		//add or substract a few hours
		currentTime.setTime(
			(currentTime.getTime() + changeHours * 1000 * 60 * 60));

		return formatter.format(currentTime);
	}	
	
	public ASN1Object createSignedCMSData(byte[] message, int mode,boolean two_users) throws Exception  {

    System.out.println("Create a new message signed by user 1:");

    // create a new SignedData object which includes the data
    SignedData signed_data = new SignedData(message, mode);
    // SignedData shall include the certificate chain for verifying
    
    PKCS12 pkcs12 = decryptPKCS12(
				configuration_.getProperty("PKCS12file"),
				configuration_.getProperty("PKCS12password"));

	BufferedInputStream bis = new BufferedInputStream(new FileInputStream(configuration_.getProperty("CERT")));
	iaik.x509.X509Certificate cert = new iaik.x509.X509Certificate(bis);
	user1_sign = cert;
	X509name = ((Name)(cert.getIssuerDN())).getRFC2253String();
	X509number = cert.getSerialNumber();
	X509hash = new String(cert.getFingerprintSHA());
	X509sub = ((Name)cert.getSubjectDN()).getRFC2253String();
	
	iaik.x509.X509Certificate[] certarray = new iaik.x509.X509Certificate[1];
	certarray[0] = cert;
	
	signed_data.setCertificates(certarray);
	
	
	RSAPrivateKey privateKey = getPrivateKey( pkcs12 );
		
    // cert at index 0 is the user certificate
    IssuerAndSerialNumber issuer = new IssuerAndSerialNumber((Name)cert.getIssuerDN(),X509number);

    // create a new SignerInfo
    SignerInfo signer_info = new SignerInfo(issuer, AlgorithmID.sha1, privateKey);
    // create some authenticated attributes
    // the message digest attribute is automatically added
    Attribute[] attributes = new Attribute[2];
    // content type is data
    attributes[0] = new Attribute(ObjectID.contentType, new ASN1Object[] {ObjectID.cms_data});
    // signing time is now
    attributes[1] = new Attribute(ObjectID.signingTime, new ASN1Object[] {new ChoiceOfTime().toASN1Object()});
    // set the attributes
    signer_info.setSignedAttributes(attributes);
    // finish the creation of SignerInfo by calling method addSigner
    try {
      signed_data.addSignerInfo(signer_info);

		if(two_users)
		{
		      // another SignerInfo without authenticated attributes and MD5 as hash algorithm
		      signer_info = new SignerInfo(new IssuerAndSerialNumber((Name)cert.getIssuerDN(),X509number),
		          AlgorithmID.md5, privateKey);
		      // the message digest itself is protected
		      signed_data.addSignerInfo(signer_info);
		}

    } catch (NoSuchAlgorithmException ex) {
    	ex.printStackTrace();
      throw new CMSException("No implementation for signature algorithm: "+ex.getMessage());
    }

    ContentInfo ci = new ContentInfo(signed_data);
    return ci.toASN1Object();
  }
  
  public ASN1Object createSignedCMSData(byte[] message, int mode,boolean two_users,String pkcs12file,String pkcs12password,String certname) throws Exception  {

    System.out.println("Create a new message signed by user 1:");

    // create a new SignedData object which includes the data
    SignedData signed_data = new SignedData(message, mode);
    // SignedData shall include the certificate chain for verifying
    
    PKCS12 pkcs12 = decryptPKCS12(
				configuration_.getProperty(pkcs12file),
				configuration_.getProperty(pkcs12password));

	BufferedInputStream bis = new BufferedInputStream(new FileInputStream(configuration_.getProperty(certname)));
	iaik.x509.X509Certificate cert = new iaik.x509.X509Certificate(bis);
	X509name = ((Name)(cert.getIssuerDN())).getRFC2253String();
	X509number = cert.getSerialNumber();
	X509hash = new String(cert.getFingerprintSHA());
	X509sub = ((Name)cert.getSubjectDN()).getRFC2253String();
	
	iaik.x509.X509Certificate[] certarray = new iaik.x509.X509Certificate[1];
	certarray[0] = cert;
	
	signed_data.setCertificates(certarray);
	
	
	RSAPrivateKey privateKey = getPrivateKey( pkcs12 );
		
    // cert at index 0 is the user certificate
    IssuerAndSerialNumber issuer = new IssuerAndSerialNumber((Name)cert.getIssuerDN(),X509number);

    // create a new SignerInfo
    SignerInfo signer_info = new SignerInfo(issuer, AlgorithmID.sha1, privateKey);
    // create some authenticated attributes
    // the message digest attribute is automatically added
    Attribute[] attributes = new Attribute[2];
    // content type is data
    attributes[0] = new Attribute(ObjectID.contentType, new ASN1Object[] {ObjectID.cms_data});
    // signing time is now
    attributes[1] = new Attribute(ObjectID.signingTime, new ASN1Object[] {new ChoiceOfTime().toASN1Object()});
    // set the attributes
    signer_info.setSignedAttributes(attributes);
    // finish the creation of SignerInfo by calling method addSigner
    try {
      signed_data.addSignerInfo(signer_info);

		if(two_users)
		{
		      // another SignerInfo without authenticated attributes and MD5 as hash algorithm
		      signer_info = new SignerInfo(new IssuerAndSerialNumber((Name)cert.getIssuerDN(),X509number),
		          AlgorithmID.md5, privateKey);
		      // the message digest itself is protected
		      signed_data.addSignerInfo(signer_info);
		}

    } catch (NoSuchAlgorithmException ex) {
      throw new CMSException("No implementation for signature algorithm: "+ex.getMessage());
    }

    ContentInfo ci = new ContentInfo(signed_data);
    return ci.toASN1Object();
  }

public String cutXML(String input)
	{
		int pos = input.indexOf(">");
		if(pos!=-1)
			return input.substring(pos+1);
		else
			return input;
	}
	

}