package java.security.cert;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.math.BigInteger;
import java.security.PublicKey;
import java.text.SimpleDateFormat;
import java.util.*;

import org.bouncycastle.asn1.ASN1OctetString;
import org.bouncycastle.asn1.ASN1Sequence;
import org.bouncycastle.asn1.ASN1TaggedObject;
import org.bouncycastle.asn1.DERGeneralizedTime;
import org.bouncycastle.asn1.DERInputStream;
import org.bouncycastle.asn1.DERObject;
import org.bouncycastle.asn1.DERObjectIdentifier;
import org.bouncycastle.asn1.DEROutputStream;
import org.bouncycastle.asn1.util.ASN1Dump;
import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
import org.bouncycastle.asn1.x509.ExtendedKeyUsage;
import org.bouncycastle.asn1.x509.KeyPurposeId;
import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
import org.bouncycastle.asn1.x509.X509Extensions;
import org.bouncycastle.asn1.x509.X509Name;

/**
 * A <code>CertSelector</code> that selects <code>X509Certificates that match all
 * specified criteria. This class is particularly useful when
 * selecting certificates from a CertStore to build a PKIX-compliant
 * certification path.<br />
 * <br />
 * When first constructed, an <code>X509CertSelector</code> has no criteria enabled
 * and each of the get methods return a default value (<code>null</code>, or -1 for
 * the {@link #getBasicConstraints} method). Therefore, the {@link #match} method would
 * return true for any <code>X509Certificate</code>. Typically, several criteria
 * are enabled (by calling {@link #setIssuer} or {@link #setKeyUsage}, for instance) and
 * then the <code>X509CertSelector</code> is passed to {@link CertStore#getCertificates} or
 * some similar method.<br />
 * <br />
 * Several criteria can be enabled (by calling {@link #setIssuer} and
 * {@link #setSerialNumber}, for example) such that the match method usually
 * uniquely matches a single <code>X509Certificate</code>. We say usually, since it
 * is possible for two issuing CAs to have the same distinguished name
 * and each issue a certificate with the same serial number. Other
 * unique combinations include the issuer, subject,
 * subjectKeyIdentifier and/or the subjectPublicKey criteria.<br />
 * <br />
 * Please refer to RFC 2459 for definitions of the X.509 certificate
 * extensions mentioned below.<br />
 * <br />
 * <b>Concurrent Access</b><br />
 * <br />
 * Unless otherwise specified, the methods defined in this class are
 * not thread-safe. Multiple threads that need to access a single
 * object concurrently should synchronize amongst themselves and
 * provide the necessary locking. Multiple threads each manipulating
 * separate objects need not synchronize.<br />
 * <br />
 * <b>TODO: implement name constraints</b>
 * <b>TODO: implement match check for path to names</b><br />
 * <br />
 * Uses {@link org.bouncycastle.asn1.DERInputStream DERInputStream},
 * {@link org.bouncycastle.asn1.ASN1Sequence ASN1Sequence},
 * {@link org.bouncycastle.asn1.DERObjectIdentifier DERObjectIdentifier},
 * {@link org.bouncycastle.asn1.DEROutputStream DEROutputStream},
 * {@link org.bouncycastle.asn1.DERObject DERObject},
 * {@link org.bouncycastle.asn1.OIDTokenizer OIDTokenizer},
 * {@link org.bouncycastle.asn1.x509.X509Name X509Name},
 * {@link org.bouncycastle.asn1.x509.X509Extensions X509Extensions},
 * {@link org.bouncycastle.asn1.x509.ExtendedKeyUsage ExtendedKeyUsage},
 * {@link org.bouncycastle.asn1.x509.KeyPurposeId KeyPurposeId},
 * {@link org.bouncycastle.asn1.x509.SubjectPublicKeyInfo SubjectPublicKeyInfo},
 * {@link org.bouncycastle.asn1.x509.AlgorithmIdentifier AlgorithmIdentifier} and
 * {@link org.bouncycastle.asn1.util.ASN1Dump#_dumpAsString _dumpAsString}
 **/
public class X509CertSelector implements CertSelector
{
    private static final Hashtable keyPurposeIdMap = new Hashtable();
    static {
	keyPurposeIdMap.put(KeyPurposeId.id_kp_serverAuth.getId(), KeyPurposeId.id_kp_serverAuth);
	keyPurposeIdMap.put(KeyPurposeId.id_kp_clientAuth.getId(), KeyPurposeId.id_kp_clientAuth);
	keyPurposeIdMap.put(KeyPurposeId.id_kp_codeSigning.getId(), KeyPurposeId.id_kp_codeSigning);
	keyPurposeIdMap.put(KeyPurposeId.id_kp_emailProtection.getId(), KeyPurposeId.id_kp_emailProtection);
	keyPurposeIdMap.put(KeyPurposeId.id_kp_ipsecEndSystem.getId(), KeyPurposeId.id_kp_ipsecEndSystem);
	keyPurposeIdMap.put(KeyPurposeId.id_kp_ipsecTunnel.getId(), KeyPurposeId.id_kp_ipsecTunnel);
	keyPurposeIdMap.put(KeyPurposeId.id_kp_ipsecUser.getId(), KeyPurposeId.id_kp_ipsecUser);
	keyPurposeIdMap.put(KeyPurposeId. id_kp_timeStamping.getId(), KeyPurposeId. id_kp_timeStamping);
    }

    private X509Certificate x509Cert = null;
    private BigInteger serialNumber = null;
    private Object issuerDN = null;
    private X509Name issuerDNX509 = null;
    private Object subjectDN = null;
    private X509Name subjectDNX509 = null;
    private byte[] subjectKeyID = null;
    private byte[] authorityKeyID = null;
    private Date certValid = null;
    private Date privateKeyValid = null;
    private DERObjectIdentifier subjectKeyAlgID = null;
    private PublicKey subjectPublicKey = null;
    private byte[] subjectPublicKeyByte = null;
    private boolean[] keyUsage = null;
    private Set keyPurposeSet = null;
    private boolean matchAllSubjectAltNames = true;
    private Set subjectAltNames = null;
    private Set subjectAltNamesByte = null;
    private int minMaxPathLen = -1;
    private Set policy = null;
    private Set policyOID = null;
    private Set pathToNames = null;
    private Set pathToNamesByte = null;

	/**
	 * Creates an <code>X509CertSelector</code>. Initially, no criteria are set
	 * so any <code>X509Certificate</code> will match.
	 **/
    public X509CertSelector() {}

        /**
	 * Sets the certificateEquals criterion. The specified
	 * <code>X509Certificate</code> must be equal to the <code>X509Certificate</code> passed
	 * to the match method. If <code>null</code>, then this check is not
	 * applied.<br />
	 * <br />
	 * This method is particularly useful when it is necessary to
	 * match a single certificate. Although other criteria can be
	 * specified in conjunction with the certificateEquals
	 * criterion, it is usually not practical or necessary.
	 *
	 * @param cert the X509Certificate to match (or <code>null</code>)
	 *
	 * @see #getCertificate()
	 **/
    public void setCertificate(X509Certificate cert)
    {
        x509Cert = cert;
    }

	/**
	 * Sets the serialNumber criterion. The specified serial
	 * number must match the certificate serial number in the
	 * <code>X509Certificate</code>. If <code>null</code>, any certificate serial number
	 * will do.
	 * 
	 * @param serial the certificate serial number to match (or <code>null</code>)
	 *
	 * @see #getSerialNumber()
	 **/
    public void setSerialNumber(BigInteger serial)
    {
        serialNumber = serial;
    }

	/**
	 * Sets the issuer criterion. The specified distinguished name
	 * must match the issuer distinguished name in the
	 * <code>X509Certificate</code>. If <code>null</code>, any
	 * issuer distinguished name will do.<br />
	 * <br />
	 * If <code>issuerDN</code> is not <code>null</code>, it
	 * should contain a distinguished name, in RFC 2253 format.<br />
	 * <br />
	 * Uses {@link org.bouncycastle.asn1.x509.X509Name X509Name}
	 * for parsing the issuerDN.
	 *
	 * @param issuerDN  a distinguished name in RFC 2253 format (or <code>null</code>)
	 *
	 * @exception IOException  if a parsing error occurs (incorrect form for DN)
	 **/
    public void setIssuer( String issuerDN )
        throws IOException
    {
        if (issuerDN == null)
        {
            this.issuerDN = null;
            this.issuerDNX509 = null;
        }
        else
        {
            X509Name nameX509;
            try {
            nameX509 = new X509Name(issuerDN);
            } catch ( IllegalArgumentException ex ) {
            throw new IOException(ex.getMessage());
            }
            this.issuerDNX509 = nameX509;
            this.issuerDN = issuerDN;
        }
    }

	/**
	 * Sets the issuer criterion. The specified distinguished name
	 * must match the issuer distinguished name in the
	 * <code>X509Certificate</code>. If null is specified, the issuer criterion
	 * is disabled and any issuer distinguished name will do.<br />
	 * <br />
	 * If <code>issuerDN</code> is not <code>null</code>, it should contain a single DER
	 * encoded distinguished name, as defined in X.501. The ASN.1
	 * notation for this structure is as follows.<br />
	 * <br /> <pre>
	 *   Name ::= CHOICE {
	 *     RDNSequence }
	 *
	 *   RDNSequence ::= SEQUENCE OF RelativeDistinguishedName
	 *
	 *   RelativeDistinguishedName ::=
	 *     SET SIZE (1 .. MAX) OF AttributeTypeAndValue
	 *
	 *   AttributeTypeAndValue ::= SEQUENCE {
	 *     type     AttributeType,
	 *     value    AttributeValue }
	 *
	 *   AttributeType ::= OBJECT IDENTIFIER
	 *
	 *   AttributeValue ::= ANY DEFINED BY AttributeType
	 *   ....
	 *   DirectoryString ::= CHOICE {
	 *     teletexString           TeletexString (SIZE (1..MAX)),
	 *     printableString         PrintableString (SIZE (1..MAX)),
	 *     universalString         UniversalString (SIZE (1..MAX)),
	 *     utf8String              UTF8String (SIZE (1.. MAX)),
	 *     bmpString               BMPString (SIZE (1..MAX)) }
	 * </pre><br />
	 * <br />
	 * Note that the byte array specified here is cloned to
	 * protect against subsequent modifications.<br />
	 * <br />
	 * Uses {@link org.bouncycastle.asn1.DERInputStream DERInputStream},
	 * {@link org.bouncycastle.asn1.DERObject DERObject},
	 * {@link org.bouncycastle.asn1.ASN1Sequence ASN1Sequence},
	 * {@link org.bouncycastle.asn1.x509.X509Name X509Name}
	 *
	 * @param issuerDN - a byte array containing the distinguished
	 * name in ASN.1 DER encoded form (or <code>null</code>)
	 *
	 * @exception IOException if an encoding error occurs
	 * (incorrect form for DN)
	 **/
    public void setIssuer(byte[] issuerDN)
	throws IOException
    {
        if (issuerDN == null)
        {
            this.issuerDN = null;
            this.issuerDNX509 = null;
        }
        else
        {
            ByteArrayInputStream inStream = new ByteArrayInputStream(issuerDN);
            DERInputStream derInStream = new DERInputStream(inStream);
            DERObject obj = derInStream.readObject();
            if ( obj != null && obj instanceof ASN1Sequence )
            {
            this.issuerDNX509 = new X509Name((ASN1Sequence)obj);
            }
            else
            {
            throw new IOException("parsing error");
            }
            this.issuerDN = (byte[])issuerDN.clone();
        }
    }

	/**
	 * Sets the subject criterion. The specified distinguished
	 * name must match the subject distinguished name in the
	 * <code>X509Certificate</code>. If null, any subject distinguished name
	 * will do.<br />
	 * <br />
	 * If <code>subjectDN</code> is not <code>null</code>, it
	 * should contain a distinguished name, in RFC 2253 format.<br />
	 * <br />
	 * Uses {@link org.bouncycastle.asn1.x509.X509Name X509Name}
	 * for parsing the subjectDN.
	 *
	 * @param subjectDN a distinguished name in RFC 2253 format (or <code>null</code>)
	 *
	 * @exception IOException if a parsing error occurs (incorrect form for DN)
	 **/
    public void setSubject(String subjectDN)
        throws IOException
    {
        if (subjectDN == null)
        {
            this.subjectDN = null;
            this.subjectDNX509 = null;
        }
        else
        {
            X509Name nameX509;
            try
            {
                nameX509 = new X509Name(subjectDN);
            }
            catch (IllegalArgumentException ex)
            {
                throw new IOException(ex.getMessage());
            }

            this.subjectDNX509 = nameX509;
            this.subjectDN = subjectDN;
        }
    }
    
	/**
	 * Sets the subject criterion. The specified distinguished
	 * name must match the subject distinguished name in the
	 * <code>X509Certificate</code>. If null, any subject distinguished name
	 * will do.<br />
	 * <br />
	 * If <code>subjectDN</code> is not <code>null</code>, it should contain a single DER
	 * encoded distinguished name, as defined in X.501. For the
	 * ASN.1 notation for this structure, see 
	 * {@link #setIssuer(byte []) setIssuer(byte [] issuerDN)}.<br />
	 * <br />
	 * Uses {@link org.bouncycastle.asn1.DERInputStream DERInputStream},
	 * {@link org.bouncycastle.asn1.DERObject DERObject},
	 * {@link org.bouncycastle.asn1.ASN1Sequence ASN1Sequence},
	 * {@link org.bouncycastle.asn1.x509.X509Name X509Name}
	 *
	 * @param subjectDN a byte array containing the distinguished
	 * name in ASN.1 DER format (or <code>null</code>)
	 *
	 * @exception IOException if an encoding error occurs (incorrect form for DN)
	 **/
    public void setSubject(byte[] subjectDN)
        throws IOException
    {
        if (subjectDN == null)
        {
            this.subjectDN = null;
            this.subjectDNX509 = null;
        }
        else
        {
            ByteArrayInputStream inStream = new ByteArrayInputStream(subjectDN);
            DERInputStream derInStream = new DERInputStream(inStream);
            DERObject obj = derInStream.readObject();

            if (obj != null && obj instanceof ASN1Sequence)
            {
                this.subjectDNX509 = new X509Name((ASN1Sequence)obj);
            }
            else
            {
                throw new IOException("parsing error");
            }
            this.subjectDN = (byte[])subjectDN.clone();
        }
    }

	/**
	 * Sets the subjectKeyIdentifier criterion. The
	 * <code>X509Certificate</code> must contain a SubjectKeyIdentifier
	 * extension for which the contents of the extension matches
	 * the specified criterion value. If the criterion value is
	 * null, no subjectKeyIdentifier check will be done.<br />
	 * <br />
	 * If <code>subjectKeyID</code> is not <code>null</code>, it should contain a single DER
	 * encoded value corresponding to the contents of the
	 * extension value (not including the object identifier,
	 * criticality setting, and encapsulating OCTET STRING) for a
	 * SubjectKeyIdentifier extension. The ASN.1 notation for this
	 * structure follows.<br />
	 * <br />
	 * <pre>
	 *   SubjectKeyIdentifier ::= KeyIdentifier
	 *
	 *   KeyIdentifier ::= OCTET STRING
	 * </pre><br />
	 * <br />
	 * Since the format of subject key identifiers is not mandated
	 * by any standard, subject key identifiers are not parsed by
	 * the <code>X509CertSelector</code>. Instead, the values are compared
	 * using a byte-by-byte comparison.<br />
	 * <br />
	 * Note that the byte array supplied here is cloned to protect
	 * against subsequent modifications.
	 * 
	 * @param subjectKeyID - the subject key identifier (or <code>null</code>)
	 *
	 * @see #getSubjectKeyIdentifier()
	**/
    public void setSubjectKeyIdentifier(byte[] subjectKeyID)
    {
        if (subjectKeyID == null)
        {
            this.subjectKeyID = null;
        }
        else
        {
            this.subjectKeyID = (byte[])subjectKeyID.clone();
        }
    }

	/**
	 * Sets the authorityKeyIdentifier criterion. The
	 * <code>X509Certificate</code> must contain an AuthorityKeyIdentifier
	 * extension for which the contents of the extension value
	 * matches the specified criterion value. If the criterion
	 * value is <code>null</code>, no authorityKeyIdentifier check will be
	 * done.<br />
	 * <br />
	 * If <code>authorityKeyID</code> is not <code>null</code>, it should contain a single
	 * DER encoded value corresponding to the contents of the
	 * extension value (not including the object identifier,
	 * criticality setting, and encapsulating OCTET STRING) for an
	 * AuthorityKeyIdentifier extension. The ASN.1 notation for
	 * this structure follows.<br />
	 * <br />
	 * <pre>
	 *   AuthorityKeyIdentifier ::= SEQUENCE {
	 *     keyIdentifier             [0] KeyIdentifier           OPTIONAL,
	 *     authorityCertIssuer       [1] GeneralNames            OPTIONAL,
	 *     authorityCertSerialNumber [2] CertificateSerialNumber OPTIONAL  }
	 *
	 *   KeyIdentifier ::= OCTET STRING
	 * </pre><br />
	 * <br />
	 * Authority key identifiers are not parsed by the
	 * <code>X509CertSelector</code>. Instead, the values are compared using a
	 * byte-by-byte comparison.<br />
	 * <br />
	 * When the <code>keyIdentifier</code> field of <code>AuthorityKeyIdentifier</code> is
	 * populated, the value is usually taken from the
	 * SubjectKeyIdentifier extension in the issuer's
	 * certificate. Note, however, that the result of
	 * X509Certificate.getExtensionValue(<SubjectKeyIdentifier
	 * Object Identifier>) on the issuer's certificate may NOT be
	 * used directly as the input to
	 * setAuthorityKeyIdentifier. This is because the
	 * SubjectKeyIdentifier contains only a KeyIdentifier OCTET
	 * STRING, and not a SEQUENCE of KeyIdentifier, GeneralNames,
	 * and CertificateSerialNumber. In order to use the extension
	 * value of the issuer certificate's SubjectKeyIdentifier
	 * extension, it will be necessary to extract the value of the
	 * embedded KeyIdentifier OCTET STRING, then DER encode this
	 * OCTET STRING inside a SEQUENCE. For more details on
	 * SubjectKeyIdentifier, see
	 * {@link #setSubjectKeyIdentifier(byte[])  setSubjectKeyIdentifier(byte[] subjectKeyID }).<br />
	 * <br />
	 * Note also that the byte array supplied here is cloned to
	 * protect against subsequent modifications.
	 *
	 * @param authorityKeyID the authority key identifier (or <code>null</code>)
	 *
	 * @see #getAuthorityKeyIdentifier()
	 **/
    public void setAuthorityKeyIdentifier(byte[] authorityKeyID)
    {
        if (authorityKeyID == null)
        {
            this.authorityKeyID = null;
        }
        else
        {
            this.authorityKeyID = (byte[])authorityKeyID.clone();
        }
    }

	/**
	 * Sets the certificateValid criterion. The specified date
	 * must fall within the certificate validity period for the
	 * X509Certificate. If <code>null</code>, no certificateValid check will be
	 * done.<br />
	 * <br />
	 * Note that the Date supplied here is cloned to protect
	 * against subsequent modifications.
	 *
	 * @param certValid the Date to check (or <code>null</code>)
	 *
	 * @see #getCertificateValid()
	 **/
    public void setCertificateValid(Date certValid)
    {
        if (certValid == null)
        {
            this.certValid = null;
        }
        else
        {
            this.certValid = new Date(certValid.getTime());
        }
    }

	/**
	 * Sets the privateKeyValid criterion. The specified date must
	 * fall within the private key validity period for the
	 * X509Certificate. If <code>null</code>, no privateKeyValid check will be
	 * done.<br />
	 * <br />
	 * Note that the Date supplied here is cloned to protect
	 * against subsequent modifications.
	 *
	 * @param privateKeyValid the Date to check (or <code>null</code>)
	 *
	 * @see #getPrivateKeyValid()
	 **/
    public void setPrivateKeyValid(Date privateKeyValid)
    {
        if (privateKeyValid == null)
        {
            this.privateKeyValid = null;
        }
        else
        {
            this.privateKeyValid = new Date(privateKeyValid.getTime());
        }
    }

	/**
	 * Sets the subjectPublicKeyAlgID criterion. The
	 * X509Certificate must contain a subject public key with the
	 * specified algorithm. If <code>null</code>, no subjectPublicKeyAlgID
	 * check will be done.
	 *
	 * @param oid  The object identifier (OID) of the algorithm to
	 * check for (or <code>null</code>). An OID is represented by a set of
	 * nonnegative integers separated by periods.
	 *
	 * @exception IOException if the OID is invalid, such as the
	 * first component being not 0, 1 or 2 or the second component
	 * being greater than 39.
	 *
	 * @see #getSubjectPublicKeyAlgID()
	 **/
    public void setSubjectPublicKeyAlgID(
        String oid)
        throws IOException
    {
        CertUtil.parseOID(oid);
        subjectKeyAlgID = new DERObjectIdentifier(oid);
    }

	/**
	 * Sets the subjectPublicKey criterion. The X509Certificate
	 * must contain the specified subject public key. If null, no
	 * subjectPublicKey check will be done.
	 *
	 * @param key the subject public key to check for (or null)
	 *
	 * @see #getSubjectPublicKey()
	 **/
    public void setSubjectPublicKey(
        PublicKey key)
    {
        if (key == null)
        {
            subjectPublicKey = null;
            subjectPublicKeyByte = null;
        }
        else
        {
            subjectPublicKey = key;
            subjectPublicKeyByte = key.getEncoded();
        }
    }

	/**
	 * Sets the subjectPublicKey criterion. The <code>X509Certificate</code>
	 * must contain the specified subject public key. If <code>null</code>, no
	 * subjectPublicKey check will be done.<br />
	 * <br />
	 * Because this method allows the public key to be specified
	 * as a byte array, it may be used for unknown key types.<br />
	 * <br />
	 * If key is not <code>null</code>, it should contain a single DER encoded
	 * SubjectPublicKeyInfo structure, as defined in X.509. The
	 * ASN.1 notation for this structure is as follows.<br />
	 * <br />
	 * <pre>
	 *   SubjectPublicKeyInfo  ::=  SEQUENCE  {
	 *     algorithm            AlgorithmIdentifier,
	 *     subjectPublicKey     BIT STRING  }
	 *
	 *   AlgorithmIdentifier  ::=  SEQUENCE  {
	 *     algorithm               OBJECT IDENTIFIER,
	 *     parameters              ANY DEFINED BY algorithm OPTIONAL  }
	 *                               -- contains a value of the type
	 *                               -- registered for use with the
	 *                               -- algorithm object identifier value
	 * </pre><br />
	 * <br />
	 * Note that the byte array supplied here is cloned to protect
	 * against subsequent modifications.
	 *
	 * @param key a byte array containing the subject public key in ASN.1 DER form (or <code>null</code>)
	 *
	 * @exception IOException if an encoding error occurs (incorrect form for subject public key)
	 *
	 * @see #getSubjectPublicKey()
	 **/
    public void setSubjectPublicKey(
        byte[] key)
        throws IOException
    {
        if (key == null)
        {
            subjectPublicKey = null;
            subjectPublicKeyByte = null;
        }
        else
        {
            subjectPublicKey = null;
            subjectPublicKeyByte = (byte[])key.clone();
    // TODO
    // try to generyte PublicKey Object from subjectPublicKeyByte
        }
    }

	/**
	 * Sets the keyUsage criterion. The X509Certificate  must
	 * allow the specified keyUsage values. If null, no keyUsage
	 * check will be done. Note that an X509Certificate that has
	 * no keyUsage extension implicitly allows all keyUsage
	 * values.<br />
	 * <br />
	 * Note that the boolean array supplied here is cloned to
	 * protect against subsequent modifications.
	 *
	 * @param keyUsage a boolean array in the same format as the boolean array returned by X509Certificate.getKeyUsage(). Or <code>null</code>.
	 *
	 * @see #getKeyUsage()
	 **/
    public void setKeyUsage(boolean[] keyUsage)
	{
	    if (keyUsage == null)
        {
            this.keyUsage = null;
        }
	    else
        {
            this.keyUsage = (boolean[])keyUsage.clone();
        }
	}

	/**
	 * Sets the extendedKeyUsage criterion. The <code>X509Certificate</code>
	 * must allow the specified key purposes in its extended key
	 * usage extension. If <code>keyPurposeSet</code> is empty or <code>null</code>, no
	 * extendedKeyUsage check will be done. Note that an
	 * <code>X509Certificate</code> that has no extendedKeyUsage extension
	 * implicitly allows all key purposes.<br />
	 * <br />
	 * Note that the Set is cloned to protect against subsequent
	 * modifications.<br />
	 * <br />
	 * Uses {@link org.bouncycastle.asn1.x509.KeyPurposeId KeyPurposeId}
	 *
	 * @param keyPurposeSet a <code>Set</code> of key purpose OIDs in string
	 * format (or <code>null</code>). Each OID is represented by a set of
	 * nonnegative integers separated by periods.
	 *
	 * @exception IOException if the OID is invalid, such as the
	 * first component being not 0, 1 or 2 or the second component
	 * being greater than 39.
	 *
	 * @see #getExtendedKeyUsage()
	 **/
    public void setExtendedKeyUsage(
        Set keyPurposeSet)
        throws IOException
    {
        if ( keyPurposeSet == null || keyPurposeSet.isEmpty() )
        {
            this.keyPurposeSet = keyPurposeSet;
        }
        else
        {
            this.keyPurposeSet = new HashSet();
            Iterator iter = keyPurposeSet.iterator();
            Object obj;
            KeyPurposeId purposeID;
            while (iter.hasNext())
            {
                obj = iter.next();
                if ( obj != null && obj instanceof String )
                {
                    purposeID = (KeyPurposeId)keyPurposeIdMap.get((String)obj);
                    if ( purposeID == null )
                    {
                        throw new IOException("unknown purposeID " + (String)obj);
                    }
                    this.keyPurposeSet.add( purposeID );
                }
            }
        }
    }
    
	/**
	 * Enables/disables matching all of the
	 * subjectAlternativeNames specified in the
	 * {@link #setSubjectAlternativeNames setSubjectAlternativeNames} or
	 * {@link #addSubjectAlternativeName addSubjectAlternativeName}
	 * methods. If enabled, the <code>X509Certificate</code> must contain all
	 * of the specified subject alternative names. If disabled,
	 * the X509Certificate must contain at least one of the
	 * specified subject alternative names.<br />
	 * <br />
	 * The matchAllNames flag is <code>true</code> by default.
	 *
	 * @param matchAllNames if <code>true</code>, the flag is enabled; if <code>false</code>, the flag is disabled.
	 *
	 * @see #getMatchAllSubjectAltNames()
	 **/
    public void setMatchAllSubjectAltNames(boolean matchAllNames)
    {
        matchAllSubjectAltNames = matchAllNames;
    }

	/**
	 * Sets the subjectAlternativeNames criterion. The
	 * <code>X509Certificate</code> must contain all or at least one of the
	 * specified subjectAlternativeNames, depending on the value
	 * of the matchAllNames flag (see {@link #setMatchAllSubjectAltNames}).<br />
	 * <br />
	 * This method allows the caller to specify, with a single
	 * method call, the complete set of subject alternative names
	 * for the subjectAlternativeNames criterion. The specified
	 * value replaces the previous value for the
	 * subjectAlternativeNames criterion.<br />
	 * <br />
	 * The <code>names</code> parameter (if not <code>null</code>) is a <code>Collection</code> with one
	 * entry for each name to be included in the subject
	 * alternative name criterion. Each entry is a <code>List</code> whose
	 * first entry is an <code>Integer</code> (the name type, 0-8) and whose
	 * second entry is a <code>String</code> or a byte array (the name, in
	 * string or ASN.1 DER encoded form, respectively). There can
	 * be multiple names of the same type. If <code>null</code> is supplied as
	 * the value for this argument, no subjectAlternativeNames
	 * check will be performed.<br />
	 * <br />
	 * Each subject alternative name in the <code>Collection</code> may be
	 * specified either as a <code>String</code> or as an ASN.1 encoded byte
	 * array. For more details about the formats used, see
	 * {@link #addSubjectAlternativeName(int, String) addSubjectAlternativeName(int type, String name)} and
	 * {@link #addSubjectAlternativeName(int, byte[]) addSubjectAlternativeName(int type, byte [] name}).<br />
	 * <br />
	 * Note that the <code>names</code> parameter can contain duplicate names
	 * (same name and name type), but they may be removed from the
	 * <code>Collection</code> of names returned by the
	 * {@link #getSubjectAlternativeNames} method.<br />
	 * <br />
	 * Note that a deep copy is performed on the Collection to
	 * protect against subsequent modifications.
	 *
	 * @param names - a Collection of names (or null)
	 *
	 * @exception IOException if a parsing error occurs
	 *
	 * @see #getSubjectAlternativeNames()
	 **/
    public void setSubjectAlternativeNames(
        Collection names)
        throws IOException
    {
        try
        {
            if ( names == null && ! names.isEmpty() )
            {
                subjectAltNames = null;
                subjectAltNamesByte = null;
            }
            else
            {
                subjectAltNames = new HashSet();
                subjectAltNamesByte = new HashSet();
                Iterator iter = names.iterator();
                List item;
                int type;
                Object data;
                while ( iter.hasNext() ) {
                    item = (List)iter.next();
                    type = ((Integer)item.get(0)).intValue();
                    data = item.get(1);
                    if ( data instanceof String ) {
                    addSubjectAlternativeName( type, (String)data );
                    } else if ( data instanceof byte[] ) {
                    addSubjectAlternativeName( type, (byte[])data );
                    } else {
                    throw new IOException("parsing error: unknown data type");
                    }
                }
            }
        }
        catch (Exception ex)
        {
            throw new IOException("parsing exception:\n" + ex.toString());
        }
    }

	/**
	 * Adds a name to the subjectAlternativeNames criterion. The
	 * <code>X509Certificate</code> must contain all or at least one of the
	 * specified subjectAlternativeNames, depending on the value
	 * of the matchAllNames flag (see {@link #setMatchAllSubjectAltNames}).<br />
	 * <br />
	 * This method allows the caller to add a name to the set of
	 * subject alternative names. The specified name is added to
	 * any previous value for the subjectAlternativeNames
	 * criterion. If the specified name is a duplicate, it may be
	 * ignored.<br />
	 * <br />
	 * The name is provided in string format. RFC 822, DNS, and
	 * URI names use the well-established string formats for those
	 * types (subject to the restrictions included in RFC
	 * 2459). IPv4 address names are supplied using dotted quad
	 * notation. OID address names are represented as a series of
	 * nonnegative integers separated by periods. And directory
	 * names (distinguished names) are supplied in RFC 2253
	 * format. No standard string format is defined for
	 * otherNames, X.400 names, EDI party names, IPv6 address
	 * names, or any other type of names. They should be specified
	 * using the {@link #addSubjectAlternativeName(int, byte[]) addSubjectAlternativeName(int type, byte [] name)}
	 * method.
	 *
	 * @param type the name type (0-8, as specified in RFC 2459, section 4.2.1.7)
	 * @param name - the name in string form (not null)
	 *
	 * @exception IOException if a parsing error occurs
	 **/
    public void addSubjectAlternativeName(
        int     type,
        String  name)
        throws IOException
    {
	    //TODO full implementation of CertUtil.parseGeneralName
        byte[] encoded = CertUtil.parseGeneralName(type,name);
        List tmpList = new ArrayList();
        tmpList.add(new Integer(type));
        tmpList.add(new String(name));
        subjectAltNames.add(tmpList);
        tmpList.set(1,encoded);
        subjectAltNamesByte.add(tmpList);
    }

	/**
	 * Adds a name to the subjectAlternativeNames criterion. The
	 * <code>X509Certificate</code> must contain all or at least one of the
	 * specified subjectAlternativeNames, depending on the value
	 * of the matchAllNames flag (see {@link #setMatchAllSubjectAltNames}).<br />
	 * <br />
	 * This method allows the caller to add a name to the set of
	 * subject alternative names. The specified name is added to
	 * any previous value for the subjectAlternativeNames
	 * criterion. If the specified name is a duplicate, it may be
	 * ignored.<br />
	 * <br />
	 * The name is provided as a byte array. This byte array
	 * should contain the DER encoded name, as it would appear in
	 * the GeneralName structure defined in RFC 2459 and
	 * X.509. The encoded byte array should only contain the
	 * encoded value of the name, and should not include the tag
	 * associated with the name in the GeneralName structure. The
	 * ASN.1 definition of this structure appears below.<br />
	 * <br />
	 * <pre>
	 *   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 byte array supplied here is cloned to protect
	 * against subsequent modifications.<br />
	 * <br />
	 * <b>TODO: check encoded format</b>
	 *
	 * @param type the name type (0-8, as listed above)
	 * @param name a byte array containing the name in ASN.1 DER encoded form
	 *
	 * @exception IOException if a parsing error occurs
	 **/
    public void addSubjectAlternativeName(
        int     type,
        byte[]  name)
        throws IOException
    {
	    //TODO check encoded format
        List tmpList = new ArrayList();
        tmpList.add(new Integer(type));
        tmpList.add(name.clone());
        subjectAltNames.add(tmpList);
        subjectAltNamesByte.add(tmpList);
    }

	/**
	 * Sets the name constraints criterion. The <code>X509Certificate</code>
	 * must have subject and subject alternative names that meet
	 * the specified name constraints.<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 byte array supplied here is cloned to protect
	 * against subsequent modifications.<br />
	 * <br />
	 * <b>TODO: implement this</b>
	 *
	 * @param bytes 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. Can be <code>null</code>, in which case
	 * no name constraints check will be performed
	 *
	 * @exception IOException if a parsing error occurs
	 * @exception UnsupportedOperationException because this method
	 * is not supported
	 * @see #getNameConstraints()
	 **/
    public void setNameConstraints(
        byte[] bytes)
        throws IOException
    {
        throw new UnsupportedOperationException();
    }

        /**
	 * Sets the basic constraints constraint. If the value is
	 * greater than or equal to zero, <code>X509Certificates</code> must
	 * include a basicConstraints extension with a pathLen of at
	 * least this value. If the value is -2, only end-entity
	 * certificates are accepted. If the value is -1, no check is
	 * done.<br />
	 * <br />
	 * This constraint is useful when building a certification
	 * path forward (from the target toward the trust anchor. If a
	 * partial path has been built, any candidate certificate must
	 * have a maxPathLen value greater than or equal to the number
	 * of certificates in the partial path.
	 *
	 * @param minMaxPathLen the value for the basic constraints constraint
	 *
	 * @exception IllegalArgumentException if the value is less than -2
	 *
	 * @see #getBasicConstraints()
	 **/
    public void setBasicConstraints(int minMaxPathLen)
    {
        if (minMaxPathLen < -2)
            throw new IllegalArgumentException( "minMaxPathLen must be >= -2" );

        this.minMaxPathLen = minMaxPathLen;
    }

	/**
	 * Sets the policy constraint. The X509Certificate must
	 * include at least one of the specified policies in its
	 * certificate policies extension. If certPolicySet is empty,
	 * then the X509Certificate must include at least some
	 * specified policy in its certificate policies extension. If
	 * certPolicySet is null, no policy check will be performed.<br />
	 * <br />
	 * Note that the Set is cloned to protect against subsequent
	 * modifications.<br />
	 * <br />
	 * <b>TODO: implement match check for this</b>
	 *
	 * @param certPolicySet a Set of certificate policy OIDs in
	 * string format (or null). Each OID is represented by a set
	 * of nonnegative integers separated by periods.
	 *
	 * @exception IOException if a parsing error occurs on the OID
	 * such as the first component is not 0, 1 or 2 or the second
	 * component is greater than 39.
	 *
	 * @see #getPolicy()
	 **/
    public void setPolicy(
        Set certPolicySet)
        throws IOException
    {
        if (certPolicySet == null)
        {
            policy = null;
            policyOID = null;
        }
        else
        {
            policyOID = new HashSet();
            Iterator iter = certPolicySet.iterator();
            Object item;
            while (iter.hasNext())
            {
                item = iter.next();
                if ( item != null && item instanceof String )
                {
                    CertUtil.parseOID((String)item);
                    policyOID.add(new DERObjectIdentifier((String)item));
                }
                else
                {
                    throw new IOException("certPolicySet contains null values or non String objects");
                }
            }
            policy = new HashSet( certPolicySet );
        }
    }

	/**
	 * Sets the pathToNames criterion. The <code>X509Certificate</code> must
	 * not include name constraints that would prohibit building a
	 * path to the specified names.<br />
	 * <br />
	 * This method allows the caller to specify, with a single
	 * method call, the complete set of names which the
	 * <code>X509Certificates</code>'s name constraints must permit. The
	 * specified value replaces the previous value for the
	 * pathToNames criterion.<br />
	 * <br />
	 * This constraint is useful when building a certification
	 * path forward (from the target toward the trust anchor. If a
	 * partial path has been built, any candidate certificate must
	 * not include name constraints that would prohibit building a
	 * path to any of the names in the partial path.<br />
	 * <br />
	 * The names parameter (if not <code>null</code>) is a <code>Collection</code> with one
	 * entry for each name to be included in the pathToNames
	 * criterion. Each entry is a <code>List</code> whose first entry is an
	 * Integer (the name type, 0-8) and whose second entry is a
	 * <code>String</code> or a byte array (the name, in string or ASN.1 DER
	 * encoded form, respectively). There can be multiple names of
	 * the same type. If <code>null</code> is supplied as the value for this
	 * argument, no pathToNames check will be performed.<br />
	 * <br />
	 * Each name in the Collection may be specified either as a
	 * String or as an ASN.1 encoded byte array. For more details
	 * about the formats used, see 
	 * {@link #addPathToName(int, String) addPathToName(int type, String name)}
	 * and {@link #addPathToName(int, byte[]) addPathToName(int type, byte [] name)}.<br />
	 * <br />
	 * Note that the names parameter can contain duplicate names
	 * (same name and name type), but they may be removed from the
	 * Collection of names returned by the {@link #getPathToNames} method.<br />
	 * <br />
	 * Note that a deep copy is performed on the Collection to
	 * protect against subsequent modifications.<br />
	 * <br />
	 * <b>TODO: implement this match check for this</b>
	 *
	 * @param names a Collection with one entry per name (or <code>null</code>)
	 *
	 * @exception IOException if a parsing error occurs
	 * @exception UnsupportedOperationException because this method
	 * is not supported
	 *
	 * @see #getPathToNames()
	 **/
    public void setPathToNames(
        Collection names)
        throws IOException
    {
        try
        {
            if ( names == null && ! names.isEmpty() )
            {
                pathToNames = null;
                pathToNamesByte = null;
            }
            else
            {
                pathToNames = new HashSet();
                pathToNamesByte = new HashSet();
                Iterator iter = names.iterator();
                List item;
                int type;
                Object data;

                while ( iter.hasNext() ) {
                    item = (List)iter.next();
                    type = ((Integer)item.get(0)).intValue();
                    data = item.get(1);
                    if (data instanceof String)
                    {
                        addPathToName( type, (String)data );
                    }
                    else if (data instanceof byte[])
                    {
                        addPathToName( type, (byte[])data );
                    }
                    else
                    {
                        throw new IOException("parsing error: unknown data type");
                    }
                }
            }
        }
        catch (Exception ex)
        {
            throw new IOException("parsing exception:\n" + ex.toString());
        }
    }

	/**
	 * Adds a name to the pathToNames criterion. The
	 * <code>X509Certificate</code>  must not include name constraints that
	 * would prohibit building a path to the specified name.<br />
	 * <br />
	 * This method allows the caller to add a name to the set of
	 * names which the <code>X509Certificates</code>'s name constraints must
	 * permit. The specified name is added to any previous value
	 * for the pathToNames criterion. If the name is a duplicate,
	 * it may be ignored.<br />
	 * <br />
	 * The name is provided in string format. RFC 822, DNS, and
	 * URI names use the well-established string formats for those
	 * types (subject to the restrictions included in RFC
	 * 2459). IPv4 address names are supplied using dotted quad
	 * notation. OID address names are represented as a series of
	 * nonnegative integers separated by periods. And directory
	 * names (distinguished names) are supplied in RFC 2253
	 * format. No standard string format is defined for
	 * otherNames, X.400 names, EDI party names, IPv6 address
	 * names, or any other type of names. They should be specified
	 * using the {@link #addPathToName(int, byte[]) addPathToName(int type, byte [] name)} method.<br />
	 * <br />
	 * <b>TODO: implement this match check for this</b>
	 *
	 * @param type the name type (0-8, as specified in RFC 2459, section 4.2.1.7)
	 * @param name the name in string form
	 *
	 * @exceptrion IOException if a parsing error occurs
	 **/
    public void addPathToName(int type, String name)
	throws IOException
    {
            //TODO full implementation of CertUtil.parseGeneralName
        byte[] encoded = CertUtil.parseGeneralName(type,name);
        List tmpList = new ArrayList();
        tmpList.add(new Integer(type));
        tmpList.add(new String(name));
        pathToNames.add(tmpList);
        tmpList.set(1,encoded);
        pathToNamesByte.add(tmpList);
        throw new UnsupportedOperationException();
    }

	/**
	 * Adds a name to the pathToNames criterion. The
	 * <code>X509Certificate</code>  must not include name constraints that
	 * would prohibit building a path to the specified name.<br />
	 * <br />
	 * This method allows the caller to add a name to the set of
	 * names which the <code>X509Certificates</code>'s name constraints must
	 * permit. The specified name is added to any previous value
	 * for the pathToNames criterion. If the name is a duplicate,
	 * it may be ignored.<br />
	 * <br />
	 * The name is provided as a byte array. This byte array should
	 * contain the DER encoded name, as it would appear in the
	 * GeneralName structure defined in RFC 2459 and X.509. The
	 * ASN.1 definition of this structure appears in the
	 * documentation for {@link #addSubjectAlternativeName(int,byte[]) addSubjectAlternativeName(int type, byte[] name)}.<br />
	 * <br />
	 * Note that the byte array supplied here is cloned to protect
	 * against subsequent modifications.<br />
	 * <br />
	 * <b>TODO: implement this match check for this</b>
	 *
	 * @param type the name type (0-8, as specified in RFC 2459, section 4.2.1.7)
	 * @param name a byte array containing the name in ASN.1 DER encoded form
	 *
	 * @exception IOException if a parsing error occurs
	 **/
    public void addPathToName(
        int     type,
        byte[]  name)
        throws IOException
    {
	    //TODO check encoded format
        List tmpList = new ArrayList();
        tmpList.add(new Integer(type));
        tmpList.add(name.clone());
        pathToNames.add(tmpList);
        pathToNamesByte.add(tmpList);
    }
    

	/** 
	 * Returns the certificateEquals criterion. The specified
	 * <code>X509Certificate</code> must be equal to the <code>X509Certificate</code> passed
	 * to the match method. If <code>null</code>, this check is not applied.
	 * 
	 * @retrun the <code>X509Certificate</code> to match (or <code>null</code>)
	 *
	 * @see #setCertificate(java.security.cert.X509Certificate)
	 **/
    public X509Certificate getCertificate()
    {
        return x509Cert;
    }

	/**
	 * Returns the serialNumber criterion. The specified serial
	 * number must match the certificate serial number in the
	 * <code>X509Certificate</code>. If <code>null</code>, any certificate serial number
	 * will do.
	 *
	 * @return the certificate serial number to match (or <code>null</code>)
	 *
	 * @see #setSerialNumber(java.math.BigInteger)
	 **/
    public BigInteger getSerialNumber()
    {
        return serialNumber;
    }

	/**
	 * Returns the issuer criterion as a String. This
	 * distinguished name must match the issuer distinguished name
	 * in the <code>X509Certificate</code>. If <code>null</code>, the issuer criterion is
	 * disabled and any issuer distinguished name will do.<br />
	 * <br />
	 * If the value returned is not <code>null</code>, it is a distinguished
	 * name, in RFC 2253 format.<br />
	 * <br />
	 * Uses {@link org.bouncycastle.asn1.x509.X509Name X509Name}
	 * for formatiing byte[] issuerDN to String.
	 *
	 * @return the required issuer distinguished name in RFC 2253
	 * format (or <code>null</code>)
	 **/
    public String getIssuerAsString()
    {
        if (issuerDN instanceof String)
        {
            return new String((String)issuerDN);
        }
        else if (issuerDNX509 != null)
        {
            return issuerDNX509.toString();
        }

        return null;
    }

	/**
	 * Returns the issuer criterion as a byte array. This
	 * distinguished name must match the issuer distinguished name
	 * in the <code>X509Certificate</code>. If <code>null</code>, the issuer criterion is
	 * disabled and any issuer distinguished name will do.<br />
	 * <br />
	 * If the value returned is not <code>null</code>, it is a byte array
	 * containing a single DER encoded distinguished name, as
	 * defined in X.501. The ASN.1 notation for this structure is
	 * supplied in the documentation for
	 * {@link #setIssuer(byte[]) setIssuer(byte [] issuerDN)}.<br />
	 * <br />
	 * Note that the byte array returned is cloned to protect
	 * against subsequent modifications.<br />
	 * <br />
	 * Uses {@link org.bouncycastle.asn1.DEROutputStream DEROutputStream},
	 * {@link org.bouncycastle.asn1.x509.X509Name X509Name} to
	 * gnerate byte[] output  for String issuerDN.
	 *
	 * @return a byte array containing the required issuer
	 * distinguished name in ASN.1 DER format (or
	 * <code>null</code>)
	 *
	 * @exception IOException  if an encoding error occurs
	 **/
    public byte[] getIssuerAsBytes()
	throws IOException
    {
        if (issuerDN instanceof byte[])
        {
            return (byte[])((byte[])issuerDN).clone();
        }
        else if (issuerDNX509 != null)
        {
            ByteArrayOutputStream outStream = new ByteArrayOutputStream();
            DEROutputStream derOutStream = new DEROutputStream(outStream);

            derOutStream.writeObject(issuerDNX509.getDERObject());
            derOutStream.close();

            return outStream.toByteArray();
        }

        return null;
    }

	/**
	 * Returns the subject criterion as a String. This
	 * distinguished name must match the subject distinguished
	 * name in the <code>X509Certificate</code>. If <code>null</code>, the subject criterion
	 * is disabled and any subject distinguished name will do.<br />
	 * <br />
	 * If the value returned is not <code>null</code>, it is a distinguished
	 * name, in RFC 2253 format.<br />
	 * <br />
	 * Uses {@link org.bouncycastle.asn1.x509.X509Name X509Name}
	 * for formatiing byte[] subjectDN to String.
	 *
	 * @return the required subject distinguished name in RFC 2253 format (or <code>null</code>)
	 **/
    public String getSubjectAsString()
    {
        if (subjectDN instanceof String)
        {
            return new String((String)subjectDN);
        }
        else if (subjectDNX509 != null)
        {
            return subjectDNX509.toString();
        }

        return null;
    }

	/**
	 * Returns the subject criterion as a byte array. This distinguished name
	 * must match the subject distinguished name in the
	 * <code>X509Certificate</code>. If <code>null</code>, the subject criterion
	 * is disabled and any subject distinguished name will do.<br />
	 * <br />
	 * If the value returned is not <code>null</code>, it is a byte
	 * array containing a single DER encoded distinguished name, as defined in
	 * X.501. The ASN.1 notation for this structure is supplied in the
	 * documentation for
	 * {@link #setSubject(byte [] subjectDN) setSubject(byte [] subjectDN)}.<br />
	 * <br />
	 * Note that the byte array returned is cloned to protect against
	 * subsequent modifications.<br />
	 * <br />
	 * Uses {@link org.bouncycastle.asn1.DEROutputStream DEROutputStream},
	 * {@link org.bouncycastle.asn1.x509.X509Name X509Name} to
	 * gnerate byte[] output  for String subjectDN.
	 *
	 * @return a byte array containing the required subject distinguished name
	 *         in ASN.1 DER format (or <code>null</code>)
	 *
	 * @exception IOException if an encoding error occurs
	 */
    public byte[] getSubjectAsBytes()
	throws IOException
    {
        if (subjectDN instanceof byte[])
        {
            return (byte[])((byte[])subjectDN).clone();
        }
        else if (subjectDNX509 != null)
        {
                ByteArrayOutputStream outStream = new ByteArrayOutputStream();
                DEROutputStream derOutStream = new DEROutputStream(outStream);

                derOutStream.writeObject(subjectDNX509.getDERObject());
                derOutStream.close();

                return outStream.toByteArray();
        }

        return null;
    }

	/**
	 * Returns the subjectKeyIdentifier criterion. The
	 * <code>X509Certificate</code> must contain a SubjectKeyIdentifier
	 * extension with the specified value. If <code>null</code>, no
	 * subjectKeyIdentifier check will be done.<br />
	 * <br />
	 * Note that the byte array returned is cloned to protect against
	 * subsequent modifications.
	 *
	 * @return the key identifier (or <code>null</code>)
	 *
	 * @see #setSubjectKeyIdentifier
	 */
    public byte[] getSubjectKeyIdentifier()
    {
        if (subjectKeyID != null)
        {
            return (byte[])subjectKeyID.clone();
        }

        return null;
    }

	/**
	 * Returns the authorityKeyIdentifier criterion. The
	 * <code>X509Certificate</code> must contain a AuthorityKeyIdentifier
	 * extension with the specified value. If <code>null</code>, no
	 * authorityKeyIdentifier check will be done.<br />
	 * <br />
	 * Note that the byte array returned is cloned to protect against
	 * subsequent modifications.
	 *
	 * @return the key identifier (or <code>null</code>)
	 *
	 * @see #setAuthorityKeyIdentifier
	 */
    public byte[] getAuthorityKeyIdentifier()
    {
        if (authorityKeyID != null)
        {
            return (byte[])authorityKeyID.clone();
        }

        return null;
    }

	/**
	 * Returns the certificateValid criterion. The specified date must fall
	 * within the certificate validity period for the
	 * <code>X509Certificate</code>. If <code>null</code>, no certificateValid
	 * check will be done.<br />
	 * <br />
	 * Note that the <code>Date</code> returned is cloned to protect against
	 * subsequent modifications.
	 *
	 * @return the <code>Date</code> to check (or <code>null</code>)
	 *
	 * @see #setCertificateValid
	 */
    public Date getCertificateValid()
    {
        if (certValid != null)
        {
            return new Date(certValid.getTime());
        }

        return null;
    }

	/**
	 * Returns the privateKeyValid criterion. The specified date must fall
	 * within the private key validity period for the
	 * <code>X509Certificate</code>. If <code>null</code>, no privateKeyValid
	 * check will be done.<br />
	 * <br />
	 * Note that the <code>Date</code> returned is cloned to protect against
	 * subsequent modifications.
	 *
	 * @return the <code>Date</code> to check (or <code>null</code>)
	 *
	 * @see #setPrivateKeyValid
	 */
    public Date getPrivateKeyValid()
    {
        if (privateKeyValid != null)
        {
            return new Date(privateKeyValid.getTime());
        }

        return null;
    }

	/**
	 * Returns the subjectPublicKeyAlgID criterion. The
	 * <code>X509Certificate</code> must contain a subject public key
	 * with the specified algorithm. If <code>null</code>, no
	 * subjectPublicKeyAlgID check will be done.
	 * 
	 * @return the object identifier (OID) of the signature algorithm to check
	 *         for (or <code>null</code>). An OID is represented by a set of
	 *         nonnegative integers separated by periods.
	 *
	 * @see #setSubjectPublicKeyAlgID
	 */
    public String getSubjectPublicKeyAlgID()
    {
        if (subjectKeyAlgID != null)
        {
            return subjectKeyAlgID.toString();
        }

        return null;
    }

	/**
	 * Returns the subjectPublicKey criterion. The
	 * <code>X509Certificate</code> must contain the specified subject
	 * public key. If <code>null</code>, no subjectPublicKey check will be done.
	 * 
	 * @return the subject public key to check for (or <code>null</code>)
	 *
	 * @see #setSubjectPublicKey
	 */
    public PublicKey getSubjectPublicKey()
    {
        return subjectPublicKey;
    }

	/**
	 * Returns the keyUsage criterion. The <code>X509Certificate</code>
	 * must allow the specified keyUsage values. If null, no keyUsage
	 * check will be done.<br />
	 * <br />
	 * Note that the boolean array returned is cloned to protect against
	 * subsequent modifications.
	 *
	 * @return a boolean array in the same format as the boolean
	 *                 array returned by
	 * {@link X509Certificate#getKeyUsage() X509Certificate.getKeyUsage()}.
	 *                 Or <code>null</code>.
	 *
	 * @see #setKeyUsage
	 */
    public boolean[] getKeyUsage()
    {
        if (keyUsage != null)
        {
            return (boolean[])keyUsage.clone();
        }

        return null;
    }

	/**
	 * Returns the extendedKeyUsage criterion. The <code>X509Certificate</code>
	 * must allow the specified key purposes in its extended key usage
	 * extension. If the <code>keyPurposeSet</code> returned is empty or
	 * <code>null</code>, no extendedKeyUsage check will be done. Note that an 
	 * <code>X509Certificate</code> that has no extendedKeyUsage extension 
	 * implicitly allows all key purposes.
	 *
	 * @return an immutable <code>Set</code> of key purpose OIDs in string
	 * format (or <code>null</code>)
	 * @see #setExtendedKeyUsage
	 */
    public Set getExtendedKeyUsage()
    {
        if ( keyPurposeSet == null || keyPurposeSet.isEmpty() )
        {
            return keyPurposeSet;
        }

        Set returnSet = new HashSet();
        Iterator iter = keyPurposeSet.iterator();
        while ( iter.hasNext() )
        {
            returnSet.add( iter.next().toString() );
        }

        return Collections.unmodifiableSet(returnSet);
    }

	/**
	 * Indicates if the <code>X509Certificate</code> must contain all
	 * or at least one of the subjectAlternativeNames 
	 * specified in the {@link #setSubjectAlternativeNames
	 * setSubjectAlternativeNames} or {@link #addSubjectAlternativeName
	 * addSubjectAlternativeName} methods. If <code>true</code>, 
	 * the <code>X509Certificate</code> must contain all of the 
	 * specified subject alternative names. If <code>false</code>, the 
	 * <code>X509Certificate</code> must contain at least one of the 
	 * specified subject alternative names.
	 *
	 * @return <code>true</code> if the flag is enabled;
	 * <code>false</code> if the flag is disabled. The flag is
	 * <code>true</code> by default.
	 *
	 * @see #setMatchAllSubjectAltNames
	 */
    public boolean getMatchAllSubjectAltNames()
    {
        return matchAllSubjectAltNames;
    }

	/**
	 * Returns a copy of the subjectAlternativeNames criterion.
	 * The <code>X509Certificate</code> must contain all or at least one
	 * of the specified subjectAlternativeNames, depending on the value
	 * of the matchAllNames flag (see {@link #getMatchAllSubjectAltNames
	 * getMatchAllSubjectAltNames}). If the value returned is <code>null</code>, 
	 * no subjectAlternativeNames check will be performed.<br />
	 * <br />
	 * If the value returned is not <code>null</code>, it is a
	 * <code>Collection</code> with
	 * one entry for each name to be included in the subject alternative name
	 * criterion. Each entry is a <code>List</code> whose first entry is an
	 * <code>Integer</code> (the name type, 0-8) and whose second
	 * entry is a <code>String</code> or a byte array (the name, in
	 * string or ASN.1 DER encoded form, respectively).
	 * There can be multiple names of the same type.  Note that the
	 * <code>Collection</code> returned may contain duplicate names (same name
	 * and name type).<br />
	 * <br />
	 * Each subject alternative name in the <code>Collection</code>
	 * may be specified either as a <code>String</code> or as an ASN.1 encoded
	 * byte array. For more details about the formats used, see
	 * {@link #addSubjectAlternativeName(int type, String name) 
	 * addSubjectAlternativeName(int type, String name)} and
	 * {@link #addSubjectAlternativeName(int type, byte [] name) 
	 * addSubjectAlternativeName(int type, byte [] name)}.<br />
	 * <br />
	 * Note that a deep copy is performed on the <code>Collection</code> to
	 * protect against subsequent modifications.
	 *
	 * @return a <code>Collection</code> of names (or <code>null</code>)
	 *
	 * @see #setSubjectAlternativeNames
	 */
    public Collection getSubjectAlternativeNames()
    {
        if (subjectAltNames != null)
        {
            return null;
        }

        Set returnAltNames = new HashSet();
        List returnList;
        Iterator iter = subjectAltNames.iterator();
        List obj;
        while ( iter.hasNext() )
        {
            obj = (List)iter.next();
            returnList = new ArrayList();
            returnList.add(obj.get(0));
            if ( obj.get(1) instanceof byte[] )
            {
            returnList.add(((byte[])obj.get(1)).clone());
            }
            else
            {
            returnList.add(obj.get(1));
            }
            returnAltNames.add(returnList);
        }

        return returnAltNames;
    }

	/**
	 * Returns the name constraints criterion. The <code>X509Certificate</code>
	 * must have subject and subject alternative names that
	 * meet the specified name constraints.<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
	 * {@link #setNameConstraints(byte [] bytes) setNameConstraints(byte [] bytes)}.<br />
	 * <br />
	 * Note that the byte array returned is cloned to protect against
	 * subsequent modifications.<br />
	 * <br />
	 * <b>TODO: implement this</b>
	 *
	 * @return a byte array containing the ASN.1 DER encoding of
	 *         a NameConstraints extension used for checking name constraints.
	 *         <code>null</code> if no name constraints check will be performed.
	 *
	 * @exception UnsupportedOperationException because this method
	 * is not supported
	 *
	 * @see #setNameConstraints
	 */
    public byte[] getNameConstraints()
    {
        throw new UnsupportedOperationException();
    }

	/**
	 * Returns the basic constraints constraint. If the value is greater than
	 * or equal to zero, the <code>X509Certificates</code> must include a
	 * basicConstraints extension with a pathLen of at least this value.
	 * If the value is -2, only end-entity certificates are accepted. If
	 * the value is -1, no basicConstraints check is done.
	 *
	 * @return the value for the basic constraints constraint
	 *
	 * @see #setBasicConstraints
	 */
    public int getBasicConstraints()
    {
        return minMaxPathLen;
    }

	/**
	 * Returns the policy criterion. The <code>X509Certificate</code> must
	 * include at least one of the specified policies in its certificate policies
	 * extension. If the <code>Set</code> returned is empty, then the 
	 * <code>X509Certificate</code> must include at least some specified policy
	 * in its certificate policies extension. If the <code>Set</code> returned is
	 * <code>null</code>, no policy check will be performed.
	 *
	 * @return an immutable <code>Set</code> of certificate policy OIDs in
	 *         string format (or <code>null</code>)
	 *
	 * @see #setPolicy
	 */
    public Set getPolicy()
    {
        if (policy == null)
        {
            return null;
        }

        return Collections.unmodifiableSet(policy);
    }

	/**
	 * Returns a copy of the pathToNames criterion. The
	 * <code>X509Certificate</code> must not include name constraints that would
	 * prohibit building a path to the specified names. If the value
	 * returned is <code>null</code>, no pathToNames check will be performed.<br />
	 * <br />
	 * If the value returned is not <code>null</code>, it is a
	 * <code>Collection</code> with one
	 * entry for each name to be included in the pathToNames
	 * criterion. Each entry is a <code>List</code> whose first entry is an
	 * <code>Integer</code> (the name type, 0-8) and whose second
	 * entry is a <code>String</code> or a byte array (the name, in
	 * string or ASN.1 DER encoded form, respectively).
	 * There can be multiple names of the same type. Note that the
	 * <code>Collection</code> returned may contain duplicate names (same
	 * name and name type).<br />
	 * <br />
	 * Each name in the <code>Collection</code>
	 * may be specified either as a <code>String</code> or as an ASN.1 encoded
	 * byte array. For more details about the formats used, see
	 * {@link #addPathToName(int type, String name) 
	 * addPathToName(int type, String name)} and
	 * {@link #addPathToName(int type, byte [] name) 
	 * addPathToName(int type, byte [] name)}.<br />
	 * <br />
	 * Note that a deep copy is performed on the <code>Collection</code> to
	 * protect against subsequent modifications.
	 *
	 * @return a <code>Collection</code> of names (or <code>null</code>)
	 *
	 * @see #setPathToNames
	 */
    public Collection getPathToNames()
    {
        if (pathToNames == null)
        {
            return null;
        }

        Set returnPathToNames = new HashSet();
        List returnList;
        Iterator iter = pathToNames.iterator();
        List obj;

        while (iter.hasNext())
        {
            obj = (List)iter.next();
            returnList = new ArrayList();
            returnList.add(obj.get(0));
            if ( obj.get(1) instanceof byte[] )
            {
            returnList.add(((byte[])obj.get(1)).clone());
            }
            else
            {
            returnList.add(obj.get(1));
            }
            returnPathToNames.add(returnList);
        }

        return returnPathToNames;
    }

	/**
	 * Return a printable representation of the <code>CertSelector</code>.<br />
	 * <br />
	 * <b>TODO: implement output for currently unsupported options(name constraints)</b><br />
	 * <br />
	 * Uses {@link org.bouncycastle.asn1.DERInputStream DERInputStream},
	 * {@link org.bouncycastle.asn1.DERObject DERObject},
	 * {@link org.bouncycastle.asn1.x509.KeyPurposeId KeyPurposeId} and
	 * {@link org.bouncycastle.asn1.util.ASN1Dump#_dumpAsString _dumpAsString}
	 * to format output as String.
	 *
	 * @return a <code>String</code> describing the contents of the
	 *         <code>CertSelector</code>
	 */
    public String toString()
    {
        StringBuffer sb = new StringBuffer();
        sb.append("X509CertSelector: [\n");
        if (x509Cert != null)
            sb.append("  Certificate: " + x509Cert.toString() + "\n");
        if (serialNumber != null)
            sb.append("  Serial Number: " + serialNumber.toString() + "\n");
        if (issuerDN != null)
            sb.append("  Issuer: " + getIssuerAsString() + "\n");
        if (subjectDN != null)
            sb.append("  Subject: " + getSubjectAsString() + "\n");
        try {
            if ( subjectKeyID != null )
            {
            ByteArrayInputStream inStream = new ByteArrayInputStream(subjectKeyID);
            DERInputStream derInStream = new DERInputStream(inStream);
            DERObject derObject = derInStream.readObject();
            sb.append("  Subject Key Identifier: " + ASN1Dump.dumpAsString(derObject) + "\n");
            }
            if ( authorityKeyID != null )
            {
            ByteArrayInputStream inStream = new ByteArrayInputStream(authorityKeyID);
            DERInputStream derInStream = new DERInputStream(inStream);
            DERObject derObject = derInStream.readObject();
            sb.append("  Authority Key Identifier: " + ASN1Dump.dumpAsString(derObject) + "\n");
            }
        } catch ( IOException ex ) {
            sb.append( ex.getMessage() + "\n" );
        }
        if (certValid != null)
            sb.append("  Certificate Valid: " +  certValid.toString() + "\n");
        if (privateKeyValid != null)
            sb.append("  Private Key Valid: " + privateKeyValid.toString() + "\n");
        if (subjectKeyAlgID != null)
            sb.append("  Subject Public Key AlgID: " + subjectKeyAlgID.toString() + "\n");
        if (subjectPublicKey != null)
            sb.append("  Subject Public Key: " + subjectPublicKey.toString() + "\n");
        if (keyUsage != null)
            sb.append("  Key Usage: " + keyUsage + "\n");
        if (keyPurposeSet != null)
            sb.append("  Extended Key Usage: " + keyPurposeSet.toString() + "\n");
        if (policy != null)
            sb.append("  Policy: " + policy.toString() + "\n");
        sb.append("  matchAllSubjectAltNames flag: " 
              + String.valueOf(matchAllSubjectAltNames) + "\n");
        if ( subjectAltNamesByte != null )
        {
            sb.append("   SubjectAlternativNames: \n[");
            Iterator iter = subjectAltNamesByte.iterator();
            List obj;
            try {
            while ( iter.hasNext() )
            {
                obj = (List)iter.next();
                ByteArrayInputStream inStream = new ByteArrayInputStream((byte[])obj.get(1));
                DERInputStream derInStream = new DERInputStream(inStream);
                DERObject derObject = derInStream.readObject();
                sb.append("  Type: " + obj.get(0) + " Data: " + ASN1Dump.dumpAsString(derObject) + "\n");
            }
            } catch ( IOException ex ) {
            sb.append( ex.getMessage() + "\n" );
            }
            sb.append("]\n");
        }
        if ( pathToNamesByte != null )
        {
            sb.append("   PathToNamesNames: \n[");
            Iterator iter = pathToNamesByte.iterator();
            List obj;
            try {
            while ( iter.hasNext() )
            {
                obj = (List)iter.next();
                ByteArrayInputStream inStream = new ByteArrayInputStream((byte[])obj.get(1));
                DERInputStream derInStream = new DERInputStream(inStream);
                DERObject derObject = derInStream.readObject();
                sb.append("  Type: " + obj.get(0) + " Data: " + ASN1Dump.dumpAsString(derObject) + "\n");
            }
            } catch ( IOException ex ) {
            sb.append( ex.getMessage() + "\n" );
            }
            sb.append("]\n");
        }
        sb.append("]");
        return sb.toString();
    }

	/**
	 * Decides whether a <code>Certificate</code> should be selected.<br />
	 * <br />
	 * <b>TODO: implement missing tests (name constraints and path to names)</b><br />
	 * <br />
	 * Uses {@link org.bouncycastle.asn1.DERInputStream DERInputStream},
	 * {@link org.bouncycastle.asn1.ASN1Sequence ASN1Sequence},
	 * {@link org.bouncycastle.asn1.DERObjectIdentifier DERObjectIdentifier},
	 * {@link org.bouncycastle.asn1.DERObject DERObject},
	 * {@link org.bouncycastle.asn1.DERGeneralizedTime DERGeneralizedTime},
	 * {@link org.bouncycastle.asn1.x509.X509Name X509Name},
	 * {@link org.bouncycastle.asn1.x509.X509Extensions X509Extensions},
	 * {@link org.bouncycastle.asn1.x509.ExtendedKeyUsage ExtendedKeyUsage},
	 * {@link org.bouncycastle.asn1.x509.KeyPurposeId KeyPurposeId},
	 * {@link org.bouncycastle.asn1.x509.SubjectPublicKeyInfo SubjectPublicKeyInfo},
	 * {@link org.bouncycastle.asn1.x509.AlgorithmIdentifier AlgorithmIdentifier}
	 * to access X509 extensions
	 *
	 * @param cert the <code>Certificate</code> to be checked
	 *
	 * @return <code>true</code> if the <code>Certificate</code> should be
	 *         selected, <code>false</code> otherwise
	 */
    public boolean match(Certificate cert)
    {
        boolean[] booleanArray;
        List tempList;
        Iterator tempIter;

        if ( ! ( cert instanceof X509Certificate ) )
            return false;
        X509Certificate certX509 = (X509Certificate)cert;

        if ( x509Cert != null && ! x509Cert.equals(certX509) )
            return false;
        if ( serialNumber != null && ! serialNumber.equals(certX509.getSerialNumber() ) )
            return false;
        try {
            if ( issuerDNX509 != null )
            {
                if ( ! issuerDNX509.equals(X509Name.getInstance(certX509.getIssuerDN()), true))
                    return false;
            }
            if ( subjectDNX509 != null )
            {
                if ( ! subjectDNX509.equals(X509Name.getInstance(certX509.getSubjectDN()), true))
                    return false;
            }
        } catch ( IllegalArgumentException ex ) {
            ex.printStackTrace();
            return false;
        }
        if ( subjectKeyID != null )
        {
            byte[] data = certX509.getExtensionValue(X509Extensions.SubjectKeyIdentifier.getId());
            if ( data == null )
            return false;
            try {
            ByteArrayInputStream inStream = new ByteArrayInputStream(data);
            DERInputStream derInputStream = new DERInputStream(inStream);
            byte[] testData = ((ASN1OctetString)derInputStream.readObject()).getOctets();
            if ( ! Arrays.equals( subjectKeyID, testData ) )
                return false;
            } catch ( IOException ex ) {
            return false;
            }
        }
        if ( authorityKeyID != null )
        {
            byte[] data = certX509.getExtensionValue(X509Extensions.AuthorityKeyIdentifier.getId());
            if ( data == null )
            return false;
            try {
            ByteArrayInputStream inStream = new ByteArrayInputStream(data);
            DERInputStream derInputStream = new DERInputStream(inStream);
            byte[] testData = ((ASN1OctetString)derInputStream.readObject()).getOctets();
            if ( ! Arrays.equals( authorityKeyID, testData ) )
                return false;
            } catch ( IOException ex ) {
            return false;
            }
        }
        if ( certValid != null )
        {
            if ( certX509.getNotAfter() != null && certValid.after(certX509.getNotAfter()) )
            return false;
            if ( certX509.getNotBefore() != null && certValid.before(certX509.getNotBefore()) )
            return false;
        }
        if (privateKeyValid != null)
        {
            try {
            byte[] data = certX509.getExtensionValue(X509Extensions.PrivateKeyUsagePeriod.getId());
            if ( data != null )
            {
                ByteArrayInputStream inStream = new ByteArrayInputStream(data);
                DERInputStream derInputStream = new DERInputStream(inStream);
                inStream = new ByteArrayInputStream(((ASN1OctetString)derInputStream.readObject()).getOctets());
                derInputStream = new DERInputStream(inStream);
                //TODO fix this, Sequence contains tagged objects
                ASN1Sequence derObject = (ASN1Sequence)derInputStream.readObject();
                DERGeneralizedTime derDate = DERGeneralizedTime.getInstance( derObject.getObjectAt(0) );
                SimpleDateFormat dateF = new SimpleDateFormat("yyyyMMddHHmmssZ");
                if ( privateKeyValid.before(dateF.parse(derDate.getTime())) )
                return false;
                derDate = DERGeneralizedTime.getInstance(derObject.getObjectAt(1));
                if ( privateKeyValid.after(dateF.parse(derDate.getTime())) )
                return false;
            }
            } catch ( Exception ex ) {
            ex.printStackTrace();
            return false;
            }
        }
        if ( subjectKeyAlgID != null )
        {
            try {
            ByteArrayInputStream inStream = new ByteArrayInputStream(certX509.getPublicKey().getEncoded());
            DERInputStream derInputStream = new DERInputStream(inStream);
            SubjectPublicKeyInfo publicKeyInfo = new SubjectPublicKeyInfo((ASN1Sequence)derInputStream.readObject());
            AlgorithmIdentifier algInfo = publicKeyInfo.getAlgorithmId();
            if ( ! algInfo.getObjectId().equals(subjectKeyAlgID) )
                return false;
            } catch ( Exception ex ) {
            return false;
            }
        }
            if ( subjectPublicKeyByte != null ) {
            if ( ! Arrays.equals(subjectPublicKeyByte, certX509.getPublicKey().getEncoded()) )
                return false;
            }
            if ( subjectPublicKey != null ) { 
            if ( ! subjectPublicKey.equals(certX509.getPublicKey()) )
                return false;
            }
            if ( keyUsage != null ) {
            booleanArray = certX509.getKeyUsage();
            if ( booleanArray != null ) {
                for ( int i = 0; i < keyUsage.length; i++ ) {
                if ( keyUsage[i] && ( booleanArray.length <= i || ! booleanArray[i] ) )
                     return false;
                }
            }
            }
            if ( keyPurposeSet != null && ! keyPurposeSet.isEmpty() ) {
            try {
                byte[] data = certX509.getExtensionValue( X509Extensions.ExtendedKeyUsage.getId() );
                if ( data != null ) {
                ByteArrayInputStream inStream = new ByteArrayInputStream(data);
                DERInputStream derInputStream = new DERInputStream(inStream);
                ExtendedKeyUsage extendedKeyUsage = new ExtendedKeyUsage((ASN1Sequence)derInputStream.readObject());
                tempIter = keyPurposeSet.iterator();
                while( tempIter.hasNext() ) {
                    if ( ! extendedKeyUsage.hasKeyPurposeId((KeyPurposeId)tempIter.next()) )
                    return false;
                }
                }
            } catch ( Exception ex ) {
                ex.printStackTrace();
                return false;
            }
            }
            if ( minMaxPathLen != -1 ) {
            if ( minMaxPathLen == -2 && certX509.getBasicConstraints() != -1 )
                return false;
            if ( minMaxPathLen >= 0 && certX509.getBasicConstraints() < minMaxPathLen )
                return false;
            }
            if ( policyOID != null ) {
            try {
                byte[] data = certX509.getExtensionValue( X509Extensions.CertificatePolicies.getId() );
                if ( data == null )
                return false;
                if ( ! policyOID.isEmpty() )
                {
                ByteArrayInputStream inStream = new ByteArrayInputStream(data);
                DERInputStream derInputStream = new DERInputStream(inStream);
                inStream = new ByteArrayInputStream(((ASN1OctetString)derInputStream.readObject()).getOctets());
                derInputStream = new DERInputStream(inStream);
                Enumeration policySequence = ((ASN1Sequence)derInputStream.readObject()).getObjects();
                ASN1Sequence policyObject;
                boolean test = false;
                while ( policySequence.hasMoreElements() && ! test )
                {
                    policyObject = (ASN1Sequence)policySequence.nextElement();
                    if ( policyOID.contains(policyObject.getObjectAt(0)) )
                    test = true;
                }
                if ( ! test )
                    return false; 
                }
            } catch ( Exception ex ) {
                ex.printStackTrace();
                return false;
            }
            }
            if ( subjectAltNamesByte != null )
            {
                try
                {
                    byte[] data = certX509.getExtensionValue(X509Extensions.SubjectAlternativeName.getId());
                    if (data == null)
                    {
                        return false;
                    }
                    ByteArrayInputStream inStream = new ByteArrayInputStream(data);
                    DERInputStream derInputStream = new DERInputStream(inStream);
                    inStream = new ByteArrayInputStream(((ASN1OctetString)derInputStream.readObject()).getOctets());
                    derInputStream = new DERInputStream(inStream);
                    Enumeration altNamesSequence = ((ASN1Sequence)derInputStream.readObject()).getObjects();
                    ASN1TaggedObject altNameObject;
                    boolean test = false;
                    Set testSet = new HashSet( subjectAltNamesByte );
                    List testList;
                    DERObject derData;
                    ByteArrayOutputStream outStream;
                    DEROutputStream derOutStream;
                    while (altNamesSequence.hasMoreElements() && !test)
                    {
                        altNameObject = (ASN1TaggedObject)altNamesSequence.nextElement();
                        testList = new ArrayList(2);
                        testList.add(new Integer(altNameObject.getTagNo()));
                        derData = altNameObject.getObject();
                        outStream = new ByteArrayOutputStream();
                        derOutStream = new DEROutputStream(outStream);
                        derOutStream.writeObject( derData );
                        derOutStream.close();
                        testList.add(outStream.toByteArray());

                        if (testSet.remove(testList))
                        {
                            test = true;
                        }

                        if (matchAllSubjectAltNames && !testSet.isEmpty())
                        {
                            test = false;
                        }
                    }
                    if (!test)
                    {
                        return false;
                    }
                }
                catch ( Exception ex )
                {
                    ex.printStackTrace();
                    return false;
                }
            }

            return true;
    }

	/**
	 * Returns a copy of this object.
	 *
	 * @return the copy
	 */
    public Object clone()
    {
        try
        {
            X509CertSelector copy = (X509CertSelector)super.clone();
            if ( issuerDN != null && issuerDN instanceof byte[] )
            copy.issuerDN = ((byte[])issuerDN).clone();
            if ( subjectDN != null && subjectDN instanceof byte[] )
            copy.subjectDN = ((byte[])subjectDN).clone();
            if ( subjectKeyID != null )
            copy.subjectKeyID = (byte[])subjectKeyID.clone();
            if ( authorityKeyID != null )
            copy.authorityKeyID = (byte[])authorityKeyID.clone();
            if ( subjectPublicKeyByte != null )
            copy.subjectPublicKeyByte = (byte[])subjectPublicKeyByte.clone();
            if ( keyUsage != null )
            copy.keyUsage = (boolean[])keyUsage.clone();
            if ( keyPurposeSet != null )
            copy.keyPurposeSet = new HashSet( keyPurposeSet );
            if ( policy != null )
            {
            copy.policy = new HashSet(policy);
            copy.policyOID = new HashSet();
            Iterator iter = policyOID.iterator();
            while ( iter.hasNext() )
            {
                copy.policyOID.add(new DERObjectIdentifier(((DERObjectIdentifier)iter.next()).getId()));
            }
            }
            if ( subjectAltNames != null ) {
            copy.subjectAltNames = new HashSet(getSubjectAlternativeNames());
            Iterator iter = subjectAltNamesByte.iterator();
            List obj;
            List cloneObj;
            while ( iter.hasNext() )
            {
                obj = (List)iter.next();
                cloneObj = new ArrayList();
                cloneObj.add(obj.get(0));
                cloneObj.add(((byte[])obj.get(1)).clone());
                copy.subjectAltNamesByte.add(cloneObj);
            }
            }
            if ( pathToNames != null ) {
            copy.pathToNames = new HashSet(getPathToNames());
            Iterator iter = pathToNamesByte.iterator();
            List obj;
            List cloneObj;
            while ( iter.hasNext() )
            {
                obj = (List)iter.next();
                cloneObj = new ArrayList();
                cloneObj.add(obj.get(0));
                cloneObj.add(((byte[])obj.get(1)).clone());
                copy.pathToNamesByte.add(cloneObj);
            }
            }
            return copy;
        }
        catch (CloneNotSupportedException e)
        {
            /* Cannot happen */
            throw new InternalError(e.toString());
        }
    }
}
