package org.bouncycastle.jce.cert;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.security.PublicKey;
import java.security.cert.X509Certificate;

import org.bouncycastle.asn1.ASN1Sequence;
import org.bouncycastle.asn1.DERInputStream;
import org.bouncycastle.asn1.DERObject;

/**
 * A trust anchor or most-trusted Certification Authority (CA). <br />
 * <br />
 * This class represents a "most-trusted CA", which is used as a trust anchor
 * for validating X.509 certification paths. A most-trusted CA includes the
 * public key of the CA, the CA's name, and any constraints upon the set of
 * paths which may be validated using this key. These parameters can be
 * specified in the form of a trusted X509Certificate or as individual
 * parameters. <br />
 * <br />
 * <strong>Concurrent Access</strong><br />
 * <br />
 * All TrustAnchor objects must be immutable and thread-safe. That is, multiple
 * threads may concurrently invoke the methods defined in this class on a
 * single TrustAnchor object (or more than one) with no ill effects. Requiring
 * TrustAnchor objects to be immutable and thread-safe allows them to be passed
 * around to various pieces of code without worrying about coordinating access.
 * This stipulation applies to all public fields and methods of this class and
 * any added or overridden by subclasses.<br />
 * <br />
 * <b>TODO: implement better nameConstraints testing.</b>
 **/
public class TrustAnchor
{
    private X509Certificate trustCert = null;
    private PublicKey       trustPublicKey = null;
    private String          trustName = null;
    private byte[]          nameConstraints = null;

	/**
	 * Creates an instance of TrustAnchor with the specified
	 * X509Certificate and optional name constraints, which are
	 * intended to be used as additional constraints when
	 * validating an X.509 certification path.<br />
	 * <br /> 
	 * The name constraints are specified as a byte array. This
	 * byte array should contain the DER encoded form of the name
	 * constraints, as they would appear in the NameConstraints
	 * structure defined in RFC 2459 and X.509. The ASN.1
	 * definition of this structure appears below.<br />
	 * <br />
	 * <pre>
	 *  NameConstraints ::= SEQUENCE {
	 *       permittedSubtrees       [0]     GeneralSubtrees OPTIONAL,
	 *       excludedSubtrees        [1]     GeneralSubtrees OPTIONAL }
	 *
	 *  GeneralSubtrees ::= SEQUENCE SIZE (1..MAX) OF GeneralSubtree
	 *
	 *  GeneralSubtree ::= SEQUENCE {
	 *       base                    GeneralName,
	 *       minimum         [0]     BaseDistance DEFAULT 0,
	 *       maximum         [1]     BaseDistance OPTIONAL }
	 *
	 *  BaseDistance ::= INTEGER (0..MAX)
	 *
	 *  GeneralName ::= CHOICE {
	 *       otherName                       [0]     OtherName,
	 *       rfc822Name                      [1]     IA5String,
	 *       dNSName                         [2]     IA5String,
	 *       x400Address                     [3]     ORAddress,
	 *       directoryName                   [4]     Name,
	 *       ediPartyName                    [5]     EDIPartyName,
	 *       uniformResourceIdentifier       [6]     IA5String,
	 *       iPAddress                       [7]     OCTET STRING,
	 *       registeredID                    [8]     OBJECT IDENTIFIER}
	 * </pre><br />
	 * <br />
	 * Note that the name constraints byte array supplied is
	 * cloned to protect against subsequent modifications.
	 *
	 * @param trustedCert a trusted X509Certificate
	 * @param nameConstraints a byte array containing the ASN.1
	 * DER encoding of a NameConstraints extension to be used for
	 * checking name constraints. Only the value of the extension
	 * is included, not the OID or criticality flag. Specify null
	 * to omit the parameter.
	 *
	 * @exception IllegalArgumentException if the name constraints
	 * cannot be decoded
	 * @exception NullPointerException if the specified X509Certificate is null
	 **/
    public TrustAnchor( X509Certificate trustedCert,
			byte[] nameConstraints)
    {
	if ( trustedCert == null )
	    throw new NullPointerException( "trustedCert must be non-null" );

	this.trustCert = trustedCert;
	if ( nameConstraints != null )
	{
	    this.nameConstraints = (byte[])nameConstraints.clone();
	    checkNameConstraints(this.nameConstraints);
	}
    }

	/**
	 * Creates an instance of <code>TrustAnchor</code> where the most-trusted
	 * CA is specified as a distinguished name and public
	 * key. Name constraints are an optional parameter, and are
	 * intended to be used as additional constraints when
	 * validating an X.509 certification path.
	 * 
	 * The name constraints are specified as a byte array. This
	 * byte array contains the DER encoded form of the name
	 * constraints, as they would appear in the NameConstraints
	 * structure defined in RFC 2459 and X.509. The ASN.1 notation
	 * for this structure is supplied in the documentation for
	 * {@link #TrustAnchor(X509Certificate trustedCert, byte[]
	 * nameConstraints) TrustAnchor(X509Certificate trustedCert,
	 * byte[] nameConstraints) }.
	 *
	 * Note that the name constraints byte array supplied here is
	 * cloned to protect against subsequent modifications.
	 *
	 * @param caName the X.500 distinguished name of the
	 * most-trusted CA in RFC 2253 String format
	 * @param pubKey the public key of the most-trusted CA
	 * @param nameConstraints a byte array containing the ASN.1
	 * DER encoding of a NameConstraints extension to be used for
	 * checking name constraints. Only the value of the extension
	 * is included, not the OID or criticality flag. Specify null
	 * to omit the parameter.
	 *
	 * @exception IllegalArgumentException if the specified caName
	 * parameter is empty (<code>caName.length() == 0</code>) or incorrectly
	 * formatted or the name constraints cannot be decoded
	 * @exception NullPointerException if the specified caName or pubKey parameter is null
	 **/
    public TrustAnchor(String caName, PublicKey pubKey,
		       byte[] nameConstraints)
    {
	if ( caName == null )
	    throw new NullPointerException( "caName must be non-null" );
	if ( pubKey == null )
	    throw new NullPointerException( "pubKey must be non-null" );
	if ( caName.length() == 0 )
	    throw new IllegalArgumentException( "caName can not be an empty string" );

	this.trustName = caName;
	this.trustPublicKey = pubKey;
	if ( nameConstraints != null )
	{
	    this.nameConstraints = (byte[])nameConstraints.clone();
	    checkNameConstraints(this.nameConstraints);
	}
    }

	/**
	 * Returns the most-trusted CA certificate.
	 *
	 * @return a trusted <code>X509Certificate</code> or <code>null</code> if the trust
	 * anchor was not specified as a trusted certificate
	 **/
    public final X509Certificate getTrustedCert()
    {
	return trustCert;
    }

	/**
	 * Returns the name of the most-trusted CA in RFC 2253 String
	 * format.
	 *
	 * @return the X.500 distinguished name of the most-trusted
	 * CA, or <code>null</code> if the trust anchor was not specified as a
	 * trusted public key and name pair
	 **/
    public final String getCAName()
    {
	return trustName;
    }

	/**
	 * Returns the public key of the most-trusted CA.
	 *
	 * @return the public key of the most-trusted CA, or null if
	 * the trust anchor was not specified as a trusted public key
	 * and name pair
	 **/
    public final PublicKey getCAPublicKey()
    {
	return trustPublicKey;
    }

	/**
	 * Returns the name constraints parameter. The specified name
	 * constraints are associated with this trust anchor and are
	 * intended to be used as additional constraints when
	 * validating an X.509 certification path.<br />
	 * <br />
	 * The name constraints are returned as a byte array. This
	 * byte array contains the DER encoded form of the name
	 * constraints, as they would appear in the NameConstraints
	 * structure defined in RFC 2459 and X.509. The ASN.1 notation
	 * for this structure is supplied in the documentation for
	 * <code>TrustAnchor(X509Certificate trustedCert, byte[]
	 * nameConstraints)</code>.<br />
	 * <br />
	 * Note that the byte array returned is cloned to protect against subsequent modifications.
	 *
	 * @return a byte array containing the ASN.1 DER encoding of a
	 * NameConstraints extension used for checking name
	 * constraints, or <code>null</code> if not set.
	 **/
    public final byte[] getNameConstraints()
    {
	return (byte[])nameConstraints.clone();
    }

	/**
	 * Returns a formatted string describing the <code>TrustAnchor</code>.
	 *
	 * @return a formatted string describing the <code>TrustAnchor</code>
	 */
    public String toString()
    {
	StringBuffer sb = new StringBuffer();
	sb.append("[\n");
	if ( getCAPublicKey() != null) {
	    sb.append("  Trusted CA Public Key: " + getCAPublicKey() + "\n");
	    sb.append("  Trusted CA Issuer Name: " + getCAName() + "\n");
	} else {
	    sb.append("  Trusted CA cert: " + getTrustedCert() + "\n");
	}
	if (nameConstraints != null)
	    sb.append("  Name Constraints: " + nameConstraints.toString() + "\n");
	return sb.toString();
    }

	/**
	 * Check given DER encoded nameConstraints for correct decoding.
	 * Currently only basic DER decoding test.<br />
	 * <br />
	 * <b>TODO: implement more testing.</b>
	 *
	 * @param data the DER encoded nameConstrains to be checked or <code>null</code>
	 * @exception IllegalArgumentException if the check failed.
	 **/
    private void checkNameConstraints(byte[] data)
    {
	if ( data != null )
	{
	    try {
		ByteArrayInputStream inStream = new ByteArrayInputStream(data);
		DERInputStream derInStream = new DERInputStream( inStream );
		DERObject derObject = derInStream.readObject();
		if ( derObject == null || ! ( derObject instanceof ASN1Sequence ) )
		    throw new IllegalArgumentException("nameConstraints parameter decoding error");
	    } catch ( IOException ex ) {
		throw new IllegalArgumentException("nameConstraints parameter decoding error: " + ex);
	    }
	}
    }
}
