package sdsi;

import sdsi.*;
import sdsi.sexp.*;
import proof.InvalidProofException;

/**
 * This object represents the Snowflake statement:
 * <center>
 *      {@tex $A | Q_1 | Q_2 \ldots\ \says\ ( B \speaksfor A | Q_1 | Q_2 | \ldots )$}
 * </center>
 * for a public-key principal A. The actual statement representation
 * is a certificate (Cert) representing the
 * <center>
 *      {@tex $( B \speaksfor A | Q_1 | Q_2 | ... )$}
 * </center>
 * statement (including the auth tag I have omitted from the formula),
 * plus a signature by A on the whole statement. Since A is free to
 * quote anyone she wants, her ``saying'' the right-hand statement (by
 * calling this ctor) is taken to mean she is quoting
 * {@tex $Q_1 \ldots Q_n$}, hence the {@tex $A|Q_1| \ldots |Q_n$}
 * in the left argument of the 'says' operator.
 *
 * <p>
 * A SignedCertificate is not really a SDSI object, it's more of a Snowflake
 * object. It's a member of a superset of SDSIObject, conceptually
 * (although not according to the way I'm abusing the class hierarchy).
 *
 * <p>
 * Currently it lives here in sdsi.*, because SDSIObject isn't designed
 * to be extended outside its own package.
 *
 * <p>
 * This class may want to be extended to allow the speaker to be different
 * than the cert issuer.
 *
 * <p>
 * This instantiation of SignedCertificate embodies a form of the handoff
 * rule (if you unequivocally believe verify()):
 * <center>
 *      {@tex $A\ \says\ C \speaksfor A|B \implies C \speaksfor A|B$}
 * </center>
 * The handoff rule and an implicit assumption that A's signature
 * on the statement actually means {@tex $A|B \says \ldots$}
 * makes that so.
 * This assumption doesn't allow the
 * ``undesirable form'' of the handoff rule mentioned in Lampson, though:
 * <center>
 *      {@tex $A \speaksfor G \wedge
 *				A\ \says\ B \speaksfor G \implies B \speaksfor G$}
 * </center>
 * So it's a pretty restricted, sensible part of the rule.
 *
 * @jonh@cs.dartmouth.edu
 */
public class SignedCertificate
	extends SDSIObject {

	Cert certificate;
		// The issuer should be a SDSIPublicKey or
		// a Quoting chain beginning Quoting(SDSIPublicKey, Quoting(...)).
	SDSISignature signature;
		// The signature is presumed to be made by the SDSIPublicKey
		// appearing in the issuer (for purposes of verification).

	public Cert getCertificate() { return certificate; }
	public SDSISignature getSignature() { return signature; }

	/**
	 * Construct a signed certificate from a certificate and a
	 * signature for the certificate.
	 */
	public SignedCertificate(Cert certificate, SDSISignature signature) {
		this.certificate = certificate;
		this.signature = signature;
		Sexp[] sexpary = new Sexp[3];
		sexpary[0] = new SexpString(LABEL);
		sexpary[1] = certificate.getSrep();
		sexpary[2] = signature.getSrep();
		srep = new SexpList(sexpary);
	}

	/**
	 * Parse a SignedCertificate from an S-expression.
	 */
	public SignedCertificate(SexpList l)
		throws SexpParseException {
		super(l);
		if (!srep.getType().equals(LABEL)) {
			throw new SexpParseException("Not a signed certificate (type="
				+srep.getType()+", not "+LABEL+")");
		}
		if (srep.size()<3) {
			throw new SexpParseException("Incorrectly formatted "+
				LABEL+" object: too few parts.");
		}
		certificate = Cert.create((SexpList) srep.elementAt(1));
		signature = new SDSISignature((SexpList) srep.elementAt(2));
	}

	public String getType() {
		return LABEL;
	}

	/**
	 * @returns if and only if the signature is a valid signature
	 * of the certificate by the key mentioned as the issuer in the
	 * certificate.
	 * @throws InvalidProofException if the signature is invalid
	 */
	public void verify()
		throws InvalidProofException {
		verify(getBaseSpeaker());
	}

	/**
	 * Return the public key actually speaking (signing the cert); less
	 * the list of principals he's quoting.
	 */
	public SDSIPublicKey getBaseSpeaker()
		throws InvalidProofException {
		return unwindQuoting(certificate.getIssuer());
	}

	/**
	 * Unwind the quoted principals from a principal quoting a chain of
	 * other principals.
	 */
	public SDSIPublicKey unwindQuoting(SDSIPrincipal principal)
		throws InvalidProofException {
		if (principal instanceof SDSIPublicKey) {
			return (SDSIPublicKey) principal;
		} else if (principal instanceof Quoting) {
			return unwindQuoting(
				(SDSIPrincipal) ((Quoting) principal).getQuoter());
		} else if (proof.KeyTools.arePrincipalsEquivalent(
			signature.getPrincipal(), principal)
			&& (signature.getPrincipal() instanceof SDSIPublicKey)) {
			return (SDSIPublicKey) signature.getPrincipal();
		} else {
			throw new InvalidProofException("Can't verify signature because "
				+"I don't know the public key that goes with this "
				+(principal.getClass())+".");
		}
	}

	void verify(SDSIPrincipal signer)
		throws InvalidProofException {
		try {
			if (signature.verify(signer, certificate)) {
				return;
			}
			throw new InvalidProofException("Signature does not verify.");
		} catch (Exception ex) {
			throw new InvalidProofException(ex.toString());
		}
	}

	public static final String LABEL = "signed-certificate";
}
