package jp;

import proof.*;
import sdsi.*;
import sdsi.sexp.*;
import servlet.*;
import java.security.*;
import java.security.interfaces.*;
import java.io.*;
import java.math.*;
import java.util.*;

/**
 * A rudimentary manual tool for setting up and packaging keys
 * and delegations. It was originally called from the command line
 * while I experimented with different delegations and keys; now
 * its main methods are called from {@link PrincipalManager}.
 */

public class Tool {
	static String myArgs[];
	static String getArg(int index, String def) {
		return index<myArgs.length
			? myArgs[index]
			: def;
	}

	/**
	 * The original command-line interface to {@link jp.Tool}.
	 */
	public static void main(String args[]) {
		myArgs = args;

		String command = getArg(0, "-help");
		if (command.equals("-newkey")) {
			generateKeyPair(getArg(1, "newkey"));
		} else if (command.equals("-auth")
			&& myArgs.length>=4) {
			generateAuthCertificate(
				getArg(1, "newcert"),	// destination file for cert
				getArg(2, "issuer"),
				getArg(3, "subject.public"),
				getArg(4, "(tag *)"),	// restriction expression
				getArg(5, "true").equalsIgnoreCase("true"),
										// propagate delegation ability
				Integer.parseInt(getArg(6, "600"))
										// not-after validity date, hours
										// (default is 10 days. No reason.
				);
		} else if (command.equals("-def")
			&& myArgs.length>=5) {
			generateDefCertificate(
				getArg(1, "newcert"),	// destination file for cert
				getArg(2, "issuer"),
				getArg(3, "name"),		// name being defined in issuer's space
				getArg(4, "subject.public"),
				Integer.parseInt(getArg(5, "600"))
										// not-after validity date, hours
										// (default is 10 days. No reason.
				);
		} else {
			System.err.print(
"java "+Tool.class.getName()+" -newkey basename\n"
+"    -- writes files basename.public, basename.private with a\n"
+"    new RSA key pair.\n"
+"java "+Tool.class.getName()+" \n"
+"       -auth destfile issuer subject [[[tag] propagate] validityDays]\n"
+"    -- writes and signs a certificate into destfile.\n"
+"       issuer should be the base name of a public/private key pair\n"
+"       in your possession.\n"
+"       subject is the filename of any subject s-expression\n"
+"       tag is a (single-argument) s-expression\n"
+"       propagate is a boolean\n"
+"       validityDays is how many days from now the certificate is valid\n"
+"java "+Tool.class.getName()+" \n"
+"       -def destfile issuer name subject [validityDays]\n"
			);
		}
	}

	/**
	 * Standalone key generation.
	 * This code to generate and save an RSA key pair is essentially
	 * lifted from sdsi.control.SDSIMainFrame.
	 */
	public static void generateKeyPair(String baseFilename) {
		try {
			SDSIKeyPair pair = generateKeyPair();

			SDSIRSAPrivateKey sdsiPrivKey =
				(SDSIRSAPrivateKey) pair.getPrivateKey();
			SDSIRSAPublicKey sdsiPubKey =
				(SDSIRSAPublicKey) pair.getPublicKey();

			FileOutputStream fos;

			fos = new FileOutputStream(baseFilename+".private");
			fos.write(sdsiPrivKey.toReadableString(72).getBytes("8859_1"));
			fos.close();

			fos = new FileOutputStream(baseFilename+".public");
			fos.write(sdsiPubKey.toReadableString(72).getBytes("8859_1"));
			fos.close();

			System.err.println("Keys written to "
				+baseFilename+".{public,private}");
		} catch (Exception ex) {
			ex.printStackTrace();
		}
	}

	/**
	 * The central work of key-pair generation, factored into a
	 * component useful to the {@link PrincipalManager}.
	 */
	public static SDSIKeyPair generateKeyPair()
		throws NoSuchAlgorithmException {
		KeyPairGenerator kpg = KeyPairGenerator.getInstance("RSA");
		((RSAKeyPairGenerator)kpg).initialize(1024,
			new BigInteger("10001",16),
			new SecureRandom());
		KeyPair kp = kpg.generateKeyPair();
		PublicKey pubKey = kp.getPublic();
		PrivateKey privKey = kp.getPrivate();

		SDSIRSAPrivateKey sdsiPrivKey
			= new SDSIRSAPrivateKey((CryptixRSAPrivateKey)privKey);

		SDSIRSAPublicKey sdsiPubKey
			= new SDSIRSAPublicKey((CryptixRSAPublicKey)pubKey);

		return new SDSIKeyPair(sdsiPrivKey, sdsiPubKey);
	}

	/**
	 * Standalone delegation generator.
	 */
	public static void generateAuthCertificate(
		String destFilename, String issuerFilename, String subjectFilename,
		String restrictionTag, boolean propagate, int validDays) {
		try {
			SDSIPublicKey issuerPublic = KeyTools.getPublicKey(issuerFilename);
			SDSIPrivateKey issuerPrivate
				= KeyTools.getPrivateKey(issuerFilename);
			Subject subject =
				(SDSIPrincipal) KeyTools.processFilename(subjectFilename);

			SignedCertificate sc = generateAuthCertificate(
				issuerPublic, issuerPrivate, subject,
				restrictionTag, propagate, validDays);

			FileOutputStream fos = new FileOutputStream(destFilename);
			fos.write(sc.toReadableString(72).getBytes("8859_1"));
			fos.close();
		} catch (Exception ex) {
			ex.printStackTrace();
		}
	}

	/**
	 * The central work of creating a delegation; used by {@link
	 * PrincipalManager}.
	 */
	public static SignedCertificate generateAuthCertificate(
		SDSIPublicKey issuerPublic, SDSIPrivateKey issuerPrivate,
		Subject subject,
		String restrictionTag, boolean propagate, int validDays)
		throws NoSuchAlgorithmException, SDSIException, SignatureException,
			SexpException, SexpParseException {

		Hash issuerHash = new Hash("md5", issuerPublic);

		// Always hash up public key to save space
		if (subject instanceof SDSIPublicKey) {
			subject = new Hash("md5", (SDSIPublicKey) subject);
		}

		PushbackInputStream pis = new PushbackInputStream(
			new ByteArrayInputStream(restrictionTag.getBytes()));
		SexpList sl = (SexpList) Sexp.parse(pis);

		Tag tag = new Tag(sl);

		Validity validity = getValidity((long) validDays);

		Auth auth = new Auth(issuerHash,
			subject, tag, propagate, validity);

		SDSISignature sig
			= new SDSISignature(auth, issuerPrivate, issuerPublic);
	
		return new SignedCertificate(auth, sig);
	}

	/**
	 * Standalone name-binding delegation generator.
	 */
	public static void generateDefCertificate(
		String destFilename, String issuerFilename, String name,
		String subjectFilename, int validDays) {
		try {
			SDSIPublicKey issuerPublic = KeyTools.getPublicKey(issuerFilename);
			SDSIPrivateKey issuerPrivate
				= KeyTools.getPrivateKey(issuerFilename);
			Subject subject =
				(SDSIPrincipal) KeyTools.processFilename(subjectFilename);

			SignedCertificate sc = generateDefCertificate(
				issuerPublic, issuerPrivate, name, subject, validDays);

			FileOutputStream fos = new FileOutputStream(destFilename);
			fos.write(sc.toReadableString(72).getBytes("8859_1"));
			fos.close();
		} catch (Exception ex) {
			ex.printStackTrace();
		}
	}

	/**
	 * The central work of creating a name-binding delegation
	 * certificate (what SPKI calls a ``def'').
	 */
	public static SignedCertificate generateDefCertificate(
		SDSIPublicKey issuerPublic, SDSIPrivateKey issuerPrivate, String name,
		Subject subject, int validDays)
		throws NoSuchAlgorithmException, SDSIException, SignatureException {

		Hash issuerHash = new Hash("md5", issuerPublic);
		if (subject instanceof SDSIPublicKey) {
			subject = new Hash("md5", (SDSIPublicKey) subject);
		}

		Validity validity = getValidity((long) validDays);

		Def def = new Def(issuerHash, name, subject, validity);

		SDSISignature sig
			= new SDSISignature(def, issuerPrivate, issuerPublic);
	
		SignedCertificate sc = new SignedCertificate(def, sig);
		return sc;
	}

	/**
	 * Convert an integer number of days into a SPKI representation of
	 * an expiration time.
	 * 
	 * @todo I think this has a bug.
	 */
	static Validity getValidity(long daysFromNow) {
		Validity validity = new Validity();
		long now = System.currentTimeMillis();
		long validInternal = (1000L*60L*60L*24L)*daysFromNow;
		validity.updateAfter(new Date(now+validInternal));
		return validity;
	}
}
