package proof;

import sdsi.*;
import sdsi.sexp.*;
import servlet.*;
import jp.*;
import Tools.*;

import java.io.*;
import java.security.*;
import java.util.*;

/**
 * This class is a utility for programs acting as Snowflake clients.
 * It manages a collection of delegations, including authority over
 * certain principals. This tool is described in the dissertation in
 * Section {@tex \ref{the_Prover}}.<p>
 * 
 * When I say ``principal,'' I mean it as in Snowflake (just about
 * anything even remotely principal-like), not as in SDSI, where only
 * SDSIPrincipals qualify. 
 */
public class Prover2 {

	// These two maps hold the graph of proof objects. Given a node
	// (a principal), you can follow outgoing edges (proofsBySubject)
	// or incoming edges (proofsByIssuer).
	MultiMap	proofsBySubject;
	// MultiMap	proofsByIssuer;
		// Okay, maybe we never really care about proofsByIssuer (incoming
		// edges).
	Set			principalSet;
		// The set of principals appearing as proof subjects and issuers.
	Map			nicknames;
		// cache of Object => getName(Object) mappings.
		// These aren't secure names, at least right now; just mnemonic
		// (debugging) names.

	/**
	 * Print out some simple stats on the objects this Prover has
	 * collected.
	 */
	public void stats() {
		System.out.println("proofs: "+proofsBySubject.size());
		System.out.println("principals: "+principalSet.size());
		System.out.println("nicknames: "+nicknames.size());
	}

	String dirname;
	File cacheDir;
	long lastCheck = -1L;

	SDSIPublicKey cachedIdentityPublicKey;
	SDSIPrivateKey cachedIdentityPrivateKey;
	Hash cachedIdentityHash;

	/**
	 * A flag to turn off when doing a certain performance evaluation,
	 * the RMI/ssh experiment, where I want to know how long it takes
	 * to create the authorization.
	 */
	public boolean saveCreatedProofs = true;

	/**
	 * used for debugging proof lookup
	 */
	public IndentWriter iw;

	/**
	 * Create a new Prover2 tool.
	 *
	 * @params dirname - the name of a directory in which to find and save
	 * certificates or keys that might help with constructing proofs.
	 */
	public Prover2(String dirname) {
		proofsBySubject = new MultiMap();
		principalSet = new HashSet();
		nicknames = new HashMap();

		this.dirname = dirname;
		if (dirname!=null) {
			cacheDir = new File(dirname);
		}

		iw = new IndentWriter(new OutputStreamWriter(System.err));
	}

	/**
	 * Get the entire set of proofs currently known to this Prover.
	 */
	public Set getProofs() {
		Set outputSet = new HashSet();
		Iterator subjectsi = proofsBySubject.keys().iterator();
		while (subjectsi.hasNext()) {
			Object subject = subjectsi.next();
			outputSet.addAll(proofsBySubject.getSet(subject));
		}
		return outputSet;
	}

	/**
	 * Get the entire set of principals currently known to this Prover.
	 */
	public Set getPrincipals() {
		return new HashSet(principalSet);
	}

	/**
	 * Get the set of principals that belong to class <code>c</code>.
	 */
	public Set getPrincipalsByType(Class c) {
		HashSet hs = new HashSet();
		Iterator iter = principalSet.iterator();
		while (iter.hasNext()) {
			Object o = iter.next();
			if (c.isInstance(o)) {
				hs.add(o);
			}
		}
		return hs;
	}

	/**
	 * Get a secure name for the object. That's a name defined with
	 * SPKI name bindings relative to a principal we consider final. A
	 * final principal might be one we control, like a public key for
	 * which we have the corresponding private key.
	 */
	public String getSecureName(Object obj) {
		return null;
		// TODO: unimpl. Actually, it is implemented, but this interface
		// to it hasn't been restored yet. It's used by the sf-authentication
		// experiment, at least.
	}

	/**
	 * Get a name, secure or mnemonic, for the object. Used for
	 * debugging, since it can help you tell keys apart more easily
	 * than you might with a hex dump. Not useful for production use,
	 * since mnemonic names are easily faked. That's the point of
	 * secure names.
	 */
	public String getName(Object obj) {
		String name;
		name = (String) nicknames.get(obj);
		if (name!=null) {
			return name;
		}
		if (obj instanceof Hash) {
			Hash hash = (Hash) obj;
			if (hash.getHashID().equals("md5")) {
				String finalStars = isFinal(hash) ? "**" : "";
				name = finalStars+Mnemonic.get20(hash.getHashValue());
			} else {
				// try to convert non-md5 hashes to md5 hashes.
				SDSIPublicKey publicKey = getPublicKeyForHash(hash);
				if (publicKey==null) {
					byte[] value = hash.getHashValue();
					name = hash.getHashID()
						+"-"+Mnemonic.get20(hash.getHashValue());
				} else {
					name = getName(publicKey);
				}
			}
		} else if (obj instanceof SDSIKeyPair) {
			name = getName(((SDSIKeyPair) obj).getPublicKey());
		} else if (obj instanceof SDSIPublicKey) {
			try {
				Hash hash = new Hash("md5", (SDSIPublicKey) obj);
				name = getName(hash);
			} catch (NoSuchAlgorithmException ex) {
				name = ex.toString();
			}
		} else if (obj instanceof Proof) {
			return getProofName((Proof) obj, false);
		} else if (obj instanceof Quoting) {
			name = getName(((Quoting) obj).getQuoter())
				+" | "+getName(((Quoting) obj).getQuotee());
		} else if (obj instanceof Name) {
			Name nameObj = (Name) obj;
			name = getName(nameObj.getPrincipal());
			for (int i=0; i<nameObj.size(); i++) {
				name = name+"'s "+nameObj.getNameIndex(i);
			}
		} else if (obj instanceof ObjectHash) {
			name = "OH<"+getName(((ObjectHash)obj).getHash())+">";
		} else if (obj == null) {
			name = "null";
		} else {
			name = ">>"+obj.getClass().getName();
		}
		nicknames.put(obj, name);
		return name;
	}

	/**
	 * Used by getName().
	 */
	public String getShortClassName(Object o) {
		String s = o.getClass().getName();
		int x = s.lastIndexOf('.');
		if (x<0) {
			return s;
		} else {
			return s.substring(x+1);
		}
	}

	/**
	 * Produce a nice string representation for a proof's conclusion.
	 * 
	 * @param longForm if true, principals are followed by the class
	 * that defines them, and the restriction tag is printed.
	 */
	public String getProofName(Proof proof, boolean longForm) {
		String sn = getName(proof.getSubject());
		String in = getName(proof.getIssuer());
		if (longForm) {
			return sn
				+"("+getShortClassName(proof.getSubject())+")"
				+" => "
				+in
				+"("+getShortClassName(proof.getIssuer())+")"
				+" rgdg "
				+proof.getTag().toReadableString(72);
		} else {
			return sn +" => " +in;
		}
	}

	static Prover2 dummyProver = null;
	/**
	 * Get a debugging name for an object, even if you have no
	 * Prover available.
	 */
	public static String staticGetName(Object obj) {
		if (dummyProver == null) {
			dummyProver = new Prover2(null);
		}
		return dummyProver.getName(obj);
	}

	/**
	 * Pulls in any files in the directory <CODE>dirname</CODE> that
	 * have changed since we last checked the directory. 
	 */
	public void loadCache() {
		if (cacheDir==null) {
			return;
		}
		try {
			if (cacheDir.lastModified()<=lastCheck) {
				// this check assumes that files are never rewritten in situ
				return;
			}
			long thisCheck = System.currentTimeMillis();

			iw.println("Loading cache "+cacheDir);
			iw.addIndent(4);
			File[] files = cacheDir.listFiles();

			// First pass through objects in cache dir
			for (int i=0; files!=null && i<files.length; i++) {
				if (files[i].lastModified()<=lastCheck) {
					continue;
				}

				SDSIObject so = KeyTools.processFile(files[i]);
				String filename = files[i].getName();

				if (so!=null) {
					SDSIObject what = introduceObject(so);
//					System.out.println("loaded cached object "+files[i]
//						+": "+getName(what));
				} else {
					System.err.println("object parses to null: "+filename);
				}
			}
			lastCheck = thisCheck;
			iw.addIndent(-4);
		} catch (FileNotFoundException ex) {
			ex.printStackTrace();
		}
	}

	/**
	 * Introduce an SDISObject to this Prover. If it's a proof or
	 * delegation, it will get cached and used when a proof is
	 * requested later.
	 */
	public SDSIObject introduceObject(SDSIObject so) {
		try {
			return introduceObject(so, false);
		} catch (IOException ex) {
			// "can't happen," since persist=false
			throw new RuntimeException(ex.toString());
		}
	}

	/**
	 * Introduce an SDISObject to this Prover. If it's a proof or
	 * delegation, it will get cached and used when a proof is
	 * requested later.
	 *
	 * @param persist If true, the object will also be saved in
	 * the cache dir specified in the constructor.
	 */
	public SDSIObject introduceObject(SDSIObject so, boolean persist)
		throws IOException {
		SDSIObject result;
		if (so instanceof SignedCertificate) {
			SignedCertificate sc = (SignedCertificate) so;
			SignedCertificateProof scp = new SignedCertificateProof(sc);
			result = scp;
			introduceProof(scp);
		} else if (so instanceof SDSIPrincipal
				|| so instanceof Subject
				|| so instanceof SDSIKeyPair) {
			result = so;
			introducePrincipal(so);
		} else if (so instanceof Proof) {
			introduceProof((Proof) so);
			result = so;	// and some other contents
		} else {
			System.err.println("introduceObject ignores "+so.getClass());
			return null;
		}
		if (persist) {
			// Spit the object out into the cachedir for future runs
			try {
				FileOutputStream fos = new FileOutputStream(newFilename("object"));
				fos.write(result.toReadableString(72).getBytes("8859_1"));
				fos.close();
			} catch (UnsupportedEncodingException ex) {
				throw new RuntimeException(ex.toString());
			}
		}
		return result;
	}

	/**
	 * Introduce a principal. Useful, among other times, for
	 * introducing a public key when the Prover might encounter proofs
	 * that supply only hashes of the key. 
	 */
	public void introducePrincipal(SDSIObject so) {
		if (principalSet.contains(so)) {
			return;
		}
		principalSet.add(so);
		SDSIObject hashable=so;
		if (so instanceof SDSIKeyPair) {
			hashable = ((SDSIKeyPair) so).getPublicKey();
		} else if (so instanceof Hash) {
			return;	 // don't need to hash it up...
		}
		if (hashable instanceof SDSIPublicKey) {
			hashKey((SDSIPublicKey) hashable);
		} else if (hashable instanceof Quoting) {
		} else if (hashable instanceof ObjectHash) {
		} else {
			System.err.println("You expect me to hash up a "
				+hashable.getClass()+"?");
		}
	}

	/**
	 * Introduce a proof.
	 */
	void introduceProof(Proof proof) {
		if (proofsBySubject.getSet(proof.getSubject()).contains(proof)) {
			return;
		}
		proofsBySubject.add(proof.getSubject(), proof);
		introducePrincipal(proof.getIssuer());
		introducePrincipal((SDSIObject) proof.getSubject());
	}

	/**
	 * Map a public key, hash, or SDSIKeyPair to a public key object.
	 */
	public SDSIPublicKey getPublicKeyForPrincipal(SDSIObject obj) {
		if (obj instanceof SDSIPublicKey) {
			return (SDSIPublicKey) obj;
		} else if (obj instanceof SDSIKeyPair) {
			return ((SDSIKeyPair) obj).getPublicKey();
		} else if (obj instanceof Hash) {
			return getPublicKeyForHash((Hash) obj);
		} else {
			return null;
		}
	}

	/**
	 * Map a hash back to a public key.
	 */
	public SDSIPublicKey getPublicKeyForHash(Hash hash) {
		Iterator iter = proofsBySubject.iterator(hash);
		while (iter.hasNext()) {
			Proof proof = (Proof) iter.next();
			if (proof.getIssuer() instanceof SDSIPublicKey) {
				SDSIPublicKey pubKey = (SDSIPublicKey) proof.getIssuer();
				if (KeyTools.arePrincipalsEquivalent(pubKey, hash)) {
					return pubKey;
				}
			}
		}
		return null;
	}

//	SDSIKeyPair getKeyPairForHash(Hash hash) {
//		Iterator iter = proofsBySubject.iterator(hash);
//		while (iter.hasNext()) {
//			Proof proof = (Proof) iter.next();
//			if (proof.getIssuer() instanceof SDSIKeyPair) {
//				SDSIKeyPair skp = (SDSIKeyPair) proof.getIssuer();
//				SDSIPublicKey pubKey = skp.getPublicKey();
//				if (KeyTools.arePrincipalsEquivalent(pubKey, hash)) {
//					return skp;
//				}
//			}
//		}
//		return null;
//	}

	/**
	 * Map a private key to the corresponding public key.
	 */
	public SDSIPublicKey getPublicKeyForPrivate(SDSIPrivateKey privateKey) {
		// TODO: security issue -- we shouldn't really be handing private
		// keys around to various parts of the application. They could be
		// sloppy with them! Instead, we should give out handles that we
		// can hash back to the real private key later.

		Iterator iter = getPrincipalsByType(SDSIKeyPair.class).iterator();
		while (iter.hasNext()) {
			SDSIKeyPair kp = (SDSIKeyPair) iter.next();
			if (kp.getPrivateKey().equals(privateKey)) {
				return kp.getPublicKey();
			}
		}
		return null;
	}

	/**
	 * Map a public key to a private key.
	 */
	public SDSIPrivateKey getPrivateKeyForPublic(SDSIPublicKey publicKey) {
		Iterator iter = getPrincipalsByType(SDSIKeyPair.class).iterator();
		while (iter.hasNext()) {
			SDSIKeyPair kp = (SDSIKeyPair) iter.next();
			if (kp.getPublicKey().equals(publicKey)) {
				return kp.getPrivateKey();
			}
		}
		return null;
	}

	/**
	 * Given a public key, hash it up and introduce the hashes and
	 * corresponding HashProofs into this Prover.
	 */
	void hashKey(SDSIPublicKey pub) {
		try {
			introduceProof(new HashProof(pub, true, "md5"));
			introduceProof(new HashProof(pub, false, "md5"));
		} catch (NoSuchAlgorithmException ex) {
		}
		try {
			introduceProof(new HashProof(pub, true, "sha1"));
			introduceProof(new HashProof(pub, false, "sha1"));
		} catch (NoSuchAlgorithmException ex) {
		}
	}

	/**
	 * An internal class used to build up proofs incrementally while
	 * walking the proof graph.
	 */
	class ProofProgress {
		Proof proof;
		ProofProgress parent;

		ProofProgress(ProofProgress parent, Proof proof){
			this.parent = parent;
			this.proof = proof;
		}

		Proof getProof() {
			ProofProgress ptr = this;
			// proof construction strategy: collect all the SignedCertificate
			// proofs (figure all the Hashy guys will be "obvious" from
			// context), and assemble with two-step-proofs.
			Proof existingProof = null;
			while (ptr!=null) {
				Proof nextProof = ptr.proof;
				ptr = ptr.parent;
				if (nextProof instanceof HashProof) {
					// ignore hash steps
					// iw.println("Ignoring hash step "+getName(nextProof));
				} else if (nextProof instanceof SignedCertificateProof
						|| nextProof instanceof QuotingRule) {
					// iw.println("Incorporating "+getName(nextProof));
					if (existingProof == null) {
						existingProof = nextProof;
					} else {
						existingProof =
							new TwoStepProof(existingProof, nextProof);
					}
				} else {
					System.err.println(
						"ProofProgress.getProof ignores "
						+nextProof.getClass().getName()+" prooflet "
						+getName(nextProof));
				}
			}
			if (existingProof == null) {
				// but we HAVE a proof! It must be trivial.
				existingProof = new TrivialProof(this.proof.getIssuer());
				// iw.println("Using trivial proof "+getName(existingProof));
			}
			return existingProof;
		}

		void dumpProgress() {
			ProofProgress ptr = this;
			while (ptr!=null) {
				System.out.println(":: "+getProofName(ptr.proof,true));
				ptr = ptr.parent;
			}
		}

		// What we should really do here is write a proof! Then
		// we just .verify() it, and its subject and issuer say all
		// we want to say.
		Proof getNameProof() {
			// dumpProgress();
			try {
				ProofProgress ptr = this;
				Proof workingProof = ptr.proof;
				ptr = ptr.parent;
				while (ptr!=null) {
					/* cases:
					 * ptr.proof.issuer and workingProof.subject are the same,
					   in which case the new proof is just a TwoStepProof.
					 * ptr.proof.issuer is a prefix of workingProof.subject,
					   in which case we write a NameLeftMonotonicity proof:
					   ptr.proof.subject \cdot S => ptr.proof.issuer \cdot S
					   but the right hand side is equal to
					   workingProof.subject, so we now can reduce to the previous
					   case.
					 */
					Proof topProof = ptr.proof;
					Proof bottomProof = workingProof;
					int topNames = getNameCount(ptr.proof.getIssuer());
					int wkgNames = getNameCount(
						(SDSIPrincipal) workingProof.getSubject());
					if (topNames==wkgNames) {
						// topProof.issuer must equal bottomProof.subject;
						// attach them and let .verify()
						// ensure we didn't screw that up.
					} else if (topNames>wkgNames) {
						// topProof.issuer has 1 name,
						// bottomProof.subject has 0 names.
						// Expand bottomProof using NameLeftMonotonicity,
						// and attach topProof.
						bottomProof = new NameLeftMonotonicity(bottomProof,
							extractNames(topProof.getIssuer()));
					} else {
						// topProof.issuer has 0 name,
						// bottomProof.subject has 1+ names.
						// Expand topProof using NameLeftMonotonicity by
						// all names in bottomProof.subject, and
						// attach bottomProof.
						topProof = new NameLeftMonotonicity(topProof,
							extractNames(
								(SDSIPrincipal) bottomProof.getSubject()));
					}

					workingProof = new TwoStepProof(bottomProof, topProof);
					// TODO: that constructor sure feels backwards.
	
					ptr = ptr.parent;
				}
//				System.out.println("verifying name chain proof: "	
//					+getProofName(workingProof, true));
				workingProof.verify();
				return workingProof;
			} catch (SexpParseException ex) {
				throw new RuntimeException(ex.toString());
			} catch (InvalidProofException ex) {
				throw new RuntimeException(ex.toString());
			}
		}
	}

	static int getNameCount(SDSIPrincipal ps) {
		if (ps instanceof Name) {
			return ((Name) ps).size();
		} else {
			return 0;
		}
	}

	/**
	 * Return the series of names appended to ps in a form that can be
	 * used to construct a NameLeftMonotonicity proof (SexpString[]). I
	 * do this instead of using sdsi.Name's getNameString() method
	 * because I don't trust the Unicode-happy java.lang.String with
	 * the contents of the arbitrary bytestring names.
	 */
	static SexpString[] extractNames(SDSIPrincipal ps) {
		SexpString[] result;
		if (ps instanceof Name) {
			Name ns = (Name) ps;
			result = new SexpString[ns.size()];
			for (int i=0; i<ns.size(); i++) {
				result[i] = (SexpString) ns.getSrep().elementAt(2+i);
				// TODO: yuk yuk yuk -- put the right interface into sdsi.Name.
			}
		} else {
			throw new RuntimeException(
				"this shouldn't happen. empty name list.");
			// result = new SexpString[0];
		}
		return result;
	}

	Set makeProgresses(ProofProgress parent, Set proofs) {
		HashSet output = new HashSet();
		Iterator iter = proofs.iterator();
		while (iter.hasNext()) {
			Proof proof = (Proof) iter.next();
			output.add(new ProofProgress(parent, proof));
		}
		return output;
	}

	/**
	 * Keep track of the state of a breadth-first search of the proof
	 * graph.
	 */
	class BFSState {
		Set /* of SDSIPrincipals */ issuers;
		Subject subject;
		Tag authTag;
		boolean wantNames;
		List queue = new Vector();		// of ProofProgress objects
		Set visited = new HashSet();	// of Proofs (edges) we've traversed

		/**
		 * two meaningful modes:
		 * 
		 * wantNames true: reply is a list of names. authTag should probably
		 * be *
		 * 
		 * wantNames false: reply is a proof
		 */
		BFSState(Set issuers, Subject subject,
			Tag authTag, boolean wantNames) {
			this.issuers = issuers;
			this.subject = subject;
			this.authTag = authTag;
			this.wantNames = wantNames;

			Set proofs = proofsBySubject.getSet(subject);
			queue.addAll(makeProgresses(null, proofs));
			visited.addAll(proofs);
		}

		// TODO: handle conjunctions, quoting.
		Proof next() {
			while (queue.size()>0) {
				ProofProgress progress = (ProofProgress) queue.remove(0);
// System.out.println("next(): proof: "+getProofName(progress.proof, true));
// System.out.println("this pp: "+getProofName(progress.getNameProof(), true));
// System.out.println("next(): at issue: "+getName(progress.proof.getIssuer()));
				if (!progress.proof.getTag().hasSubset(authTag)) {
					System.out.println("Bah! I spit on this proof with "
						+"an insufficient tag!"
						+progress.proof.getTag().toReadableString(72));
					continue;
				}
				if (issuers.contains(progress.proof.getIssuer())) {
					// hey, this gets us all the way to the end
					if (wantNames) {
						return progress.getNameProof();
					} else {
						return progress.getProof();
					}
				}
				// well, see where we can go from here using edges (Proofs)
				// that we have not yet explored
				Subject newSubject = progress.proof.getIssuer();
				if (wantNames) {
					if (newSubject instanceof sdsi.Name) {
						newSubject = ((sdsi.Name) newSubject).getPrincipal();
					}
				}
				Set proofs = proofsBySubject.getSet(newSubject);
				Iterator iter = proofs.iterator();
				while (iter.hasNext()) {
					Proof proof = (Proof) iter.next();
					if (!visited.contains(proof)) {
// System.out.println("found new prooflet "+getProofName(proof, true));
						visited.add(proof);
						ProofProgress pp = new ProofProgress(progress, proof);
						queue.add(pp);
// System.out.println("queue.add(): "+getProofName(pp.getNameProof(), true));
// System.out.println("pp.parent: "+getProofName(pp.parent.proof,true));
					}
				}
			}
			return null;
		}
	}

	/**
	 * find a proof that the request speaks for the issuer (ultimate
	 * server) regarding all of the statements in authTag.
	 *
	 * <p>Algorithm is a BFS over the graph of proofs.
	 * 
	 * <P> When we find a principal for which we hold the corresponding
	 * private key, we're done. (We could find any principal that we
	 * could "make" equivalent to the subject, but short of creating a
	 * new certificate to do so, which takes a private key, the only
	 * current alternative would be to find the exact request itself.)
	 * 
	 * @todo this algorithm won't yet do anything with conjuct or quoting
	 * principals
	 */
	public String getProofString(SDSIPrincipal issuer, Subject subject,
		Tag authTag) {
		Proof p = getProof(issuer, subject, authTag);
		if (p!=null) {
			return p.toReadableString(72);
		} else {
			return null;
		}
	}

	/**
	 * @see #getProofString(SDSIPrincipal,Subject,Tag)
	 */
	public Proof getProof(SDSIPrincipal issuer, Subject subject,
		Tag authTag) {
		Set issuerSet = new HashSet();
		issuerSet.add(issuer);
		BFSState bfs = new BFSState(issuerSet, subject, authTag, false);
		Proof proof = (Proof) bfs.next();
		return proof;
	}

	/**
	 * Calls getProof(), but if proof doesn't exist, will look for a
	 * proof for an issuer we control, and sign off on a delegation for
	 * the last step {@tex subject $\speaksfor$ myIssuer}.
	 */
	public Proof makeProof(SDSIPrincipal issuer, Subject subject,
		Tag authTag) {
		Proof optimisticProof = getProof(issuer, subject, authTag);
		if (optimisticProof!=null) {
			return optimisticProof;
		}
		SDSIPublicKey identityPublicKey = getIdentityPublicKey();
		// TODO: handle multiple final keys. BFSState allows
		// set for issuers, but not for subjects.
		Proof proof = getProof(issuer, identityPublicKey, authTag);
		if (proof==null) {
			return null;
		}
		Proof delegation = createAuth(subject, identityPublicKey, authTag,
			identityPublicKey);
		return new TwoStepProof(proof, delegation);
	}

	/**
	 * Finds every possible name for <code>p</code> rooted in a public
	 * key for which we have the private key.
	 * Algorithm is BFS, so that we can stop once we find a few good,
	 * short names.
	 * 
	 * @param numDesired the maximum number of name chains to return.
	 * Specify -1 to completely explore the name graph.
	 * @returns a List of sdsi.Name objects, upon which you may call
	 * size()/getNameIndex(i).
	 */
	public List getNames(SDSIPrincipal subject, int numDesired) {
		BFSState bfs = new BFSState(getFinalPrincipals(),
			subject, Tag.getTagStar(), true);
		List output = new Vector();
		for (int i=0; numDesired==-1 || i<numDesired; i++) {
			Proof nameProof = (Proof) bfs.next();
			if (nameProof==null) {
				break;
			}
			output.add(nameProof.getIssuer());
		}
		// TODO: verify signed Defs; don't just accept any
		// Def we stumble across...
		return output;
	}

	/**
	 * Get the set of principals we consider ``final:'' those public
	 * keys for which we control the corresponding private key, for
	 * example, or any other principal that we can cause to say
	 * something.
	 */
	public Set getFinalPrincipals() {
		Iterator iter = principalSet.iterator();
		Set set = new HashSet();
		while (iter.hasNext()) {
			SDSIObject obj = (SDSIObject) iter.next();
			if (isFinal(obj)) {
				set.add(obj);
			}
		}
		return set;
	}

	/**
	 * Return true if the subject is one for which we control the
	 * private key here.
	 */
	public boolean isFinal(SDSIObject s) {
		SDSIPublicKey publicKey;
		if (s instanceof SDSIPublicKey) {
			publicKey = (SDSIPublicKey) s;
		} else if (s instanceof Hash) {
			publicKey = getPublicKeyForHash((Hash) s);
		} else {
			return false;
		}
		SDSIPrivateKey privateKey = getPrivateKeyForPublic(publicKey);
		return privateKey!=null;
	}

	/**
	 * A test/debug function. Tries to name all of the objects loaded into
	 * the cache.
	 */
	public static void main(String args[]) {
		Prover2 prover = new Prover2("certs-client");
		prover.loadCache();

		System.out.println("----------");
		Iterator iter = prover.getPrincipals().iterator();
		while (iter.hasNext()) {
			Object o = iter.next();
			String desc = prover.getSecureName(o)==null
				? " (mnemonic)"
				: " (secure)";
			System.out.println(" object: "+prover.getName(o)+desc);
		}
	}

	/**
	 * When someone sends us a proof, this method takes it all apart
	 * and saves all the certificates. We can use the digested parts
	 * later to build our own proof.<p>
	 *
	 * @todo A smarter way to handle this would stop recursing after we
	 * see a proof we've seen before (duh).
	 */
	public void digestProof(Proof p) {
		Iterator iter;

		// Cache complete proofs/"prooflets" (little proofs that may
		// not be useful on their own, but saves us the time of discovering
		// & constructing them.) [In some cases, e.g. a signedcertificate
		// with handoff, we may not know *how* to construct them.]
		iter = p.preorderProofs().iterator();
		while (iter.hasNext()) {
			Proof pr = (Proof) iter.next();
			if (!proofsBySubject.getSet(pr.getSubject()).contains(pr)) {
				introduceProof(pr);
			}
		}
	}

	/**
	 * Create a delegation that shows that
	 * {@tex subject $\speaksfor$ issuer}.
	 */
	public Proof createAuth(Subject subject, SDSIObject issuer) {
		SDSIPublicKey publicKey = getPublicKeyForPrincipal(issuer);
		return createAuth(subject, issuer, publicKey);
	}

	/**
	 * Create a delegation that shows that
	 * {@tex subject $\speaksfor$ issuer}.
	 *
	 * @param publicKey <code>subject</code> is in fact this public key.
	 */
	public Proof createAuth(Subject subject, SDSIObject issuer,
		SDSIPublicKey publicKey) {
		return createAuth(subject, issuer, Tag.getTagStar(), publicKey);
	}

	/**
	 * Create a delegation that shows that
	 * {@tex subject $\sfrg{\text{tag}}$ issuer}.
	 */
	public Proof createAuth(Subject subject, SDSIObject issuer, Tag tag,
		SDSIPublicKey publicKey) {
		try {
			SDSIPrivateKey privateKey = getPrivateKeyForPublic(publicKey);
			Validity v = new Validity();
			v.updateAfter(new Date(System.currentTimeMillis()+30000L));
			Auth authCert = new Auth(publicKey, subject, tag,
				true, v);
			SDSISignature ss = new SDSISignature(authCert,
				privateKey, publicKey);
			SignedCertificate sc = new SignedCertificate(authCert, ss);
			// TODO: handoff proof from BasicUnlockKey
			Proof handoff = null;
			Proof proof = new SignedCertificateProof(sc, handoff);
			if (saveCreatedProofs) {
				digestProof(proof);
			}
			return proof;
		} catch (java.security.SignatureException ex) {
			return null;
		}
	}

	/**
	 * An internal tool for debugging proof generation.
	 */
	public class DebugTool {

		HashMap objectToAbbr = new HashMap();
		int nextObjAbbr = 1;

		public Object getAbbr(Object o) {
			Object abbr = objectToAbbr.get(o);
			if (abbr == null) {
				abbr = new Integer(nextObjAbbr++);
				objectToAbbr.put(o, abbr);
			}
			return abbr;
		}

		public void dumpObjects(Set s) {
			iw.println("Dumping "+s.size()+" objects");
			Iterator iter = s.iterator();
			while (iter.hasNext()) {
				Object o = iter.next();
				if (o instanceof Proof) {
					Proof proof = (Proof) o;
					Subject sub = proof.getSubject();
					Subject iss = proof.getIssuer();

					Object proofAbbr = getAbbr(proof);
					Object subAbbr = getAbbr(sub);
					Object issAbbr = getAbbr(iss);
					iw.println("proof "+proofAbbr+": "+subAbbr+" => "+issAbbr
						+"   "+proof.getClass().getName());
				} else {
					iw.println(getAbbr(o)+" ("+o.getClass().getName()+")");
				}
			}
		}

		public void dumpObjects() {
			dumpObjects(getAllProofs());
			dumpObjects(getPrincipals());
		}

		public Set getAllProofs() {
			HashSet set = new HashSet();
			Iterator iter = proofsBySubject.keys().iterator();
			while (iter.hasNext()) {
				Iterator i2 = proofsBySubject.iterator(iter.next());
				while (i2.hasNext()) {
					Proof p = (Proof) i2.next();
					set.add(p);
				}
			}
			return set;
		}

		public String getDebugName(Object o) {
			Object abbr = getAbbr(o);
			return abbr.toString()+" ("+getName(o)+")";
		}
	}

	class FilenameTest implements BinarySearch.Test {
		String suffix;
		FilenameTest(String suffix) { this.suffix = suffix; }
		public boolean test(int value) {
			File f = new File(cacheDir, ""+value+"."+suffix);
			// System.out.println("does file "+f+" exist?");
			return !f.exists();
		}
	}

	File newFilename(String suffix) {
		int i = BinarySearch.unbounded(0, true, new FilenameTest(suffix));
		return new File(cacheDir, ""+i+"."+suffix);
	}

	/**
	 * Get some unique notion of identity, by which the caller means he
	 * hopes there aren't multiple public keys I control.
	 *
	 * @todo Adapt interface to handle programs that want to manage multiple
	 * notions of principal simultaneously.
	 */
	public SDSIPublicKey getIdentityPublicKey() {
		if (cachedIdentityPublicKey==null) {
			loadCache();
			Set finalPrincipals = getFinalPrincipals();
			if (finalPrincipals.size()==0) {
				System.err.println("Hey, no identity! Gasp.");
				return null;
			}
			SDSIPrincipal principal = 
				(SDSIPrincipal) finalPrincipals.iterator().next();
			cachedIdentityPublicKey = getPublicKeyForPrincipal(principal);
		}
		return cachedIdentityPublicKey;
	}

	/**
	 * Return the private key corresponding to my identity, if my
	 * identity is a public key.
	 * 
	 * @todo Um, should this be public? What are the (inside-a-JVM)
	 * security semantics of holding a reference to a prover?
	 */
	public SDSIPrivateKey getIdentityPrivateKey() {
		if (cachedIdentityPrivateKey==null) {
			cachedIdentityPrivateKey =
				getPrivateKeyForPublic(getIdentityPublicKey());
		}
		return cachedIdentityPrivateKey;
	}

	/**
	 * Get a hash abbreviation for my identity.
	 */
	public Hash getIdentityHash() {
		if (cachedIdentityHash==null) {
			try {
				cachedIdentityHash = new Hash("md5", getIdentityPublicKey());
			} catch (NoSuchAlgorithmException ex) {
				return null;
			}
		}
		return cachedIdentityHash;
	}

	/**
	 * Dump the proofs cached in the prover. A debugging method.
	 */
	public void dumpProofs() {
		Iterator iter = getProofs().iterator();
		while (iter.hasNext()) {
			Proof p = (Proof) iter.next();
			System.out.println("* "+getProofName(p, true));
		}
	}
}
