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.security.cert.Certificate;
import java.security.cert.X509Certificate;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.List;
import java.util.Set;

import org.spongycastle.asn1.ASN1InputStream;
import org.spongycastle.asn1.ASN1Object;
import org.spongycastle.asn1.ASN1ObjectIdentifier;
import org.spongycastle.asn1.ASN1OctetString;
import org.spongycastle.asn1.ASN1Sequence;
import org.spongycastle.asn1.ASN1TaggedObject;
import org.spongycastle.asn1.DERGeneralizedTime;
import org.spongycastle.asn1.DEROutputStream;
import org.spongycastle.asn1.util.ASN1Dump;
import org.spongycastle.asn1.x509.AlgorithmIdentifier;
import org.spongycastle.asn1.x509.ExtendedKeyUsage;
import org.spongycastle.asn1.x509.KeyPurposeId;
import org.spongycastle.asn1.x509.SubjectPublicKeyInfo;
import org.spongycastle.asn1.x509.X509Extensions;
import org.spongycastle.asn1.x509.X509Name;
import org.spongycastle.jce.PrincipalUtil;
import org.spongycastle.util.Integers;

/**
 * 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.spongycastle.asn1.ASN1InputStream ASN1InputStream},
 * {@link org.spongycastle.asn1.ASN1Sequence ASN1Sequence},
 * {@link org.spongycastle.asn1.ASN1ObjectIdentifier ASN1ObjectIdentifier},
 * {@link org.spongycastle.asn1.DEROutputStream DEROutputStream},
 * {@link org.spongycastle.asn1.ASN1Object ASN1Object},
 * {@link org.spongycastle.asn1.OIDTokenizer OIDTokenizer},
 * {@link org.spongycastle.asn1.x509.X509Name X509Name},
 * {@link org.spongycastle.asn1.x509.X509Extensions X509Extensions},
 * {@link org.spongycastle.asn1.x509.ExtendedKeyUsage ExtendedKeyUsage},
 * {@link org.spongycastle.asn1.x509.KeyPurposeId KeyPurposeId},
 * {@link org.spongycastle.asn1.x509.SubjectPublicKeyInfo SubjectPublicKeyInfo},
 * {@link org.spongycastle.asn1.x509.AlgorithmIdentifier AlgorithmIdentifier}
 */
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 ASN1ObjectIdentifier 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.spongycastle.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 RDN
     * 
     *    RDN ::=
     *      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.spongycastle.asn1.ASN1InputStream ASN1InputStream},
     * {@link org.spongycastle.asn1.ASN1Object ASN1Object},
     * {@link org.spongycastle.asn1.ASN1Sequence ASN1Sequence},
     * {@link org.spongycastle.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);
            ASN1InputStream derInStream = new ASN1InputStream(inStream);
            ASN1Object obj = derInStream.readObject();
            if (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.spongycastle.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.spongycastle.asn1.ASN1InputStream ASN1InputStream},
     * {@link org.spongycastle.asn1.ASN1Object ASN1Object},
     * {@link org.spongycastle.asn1.ASN1Sequence ASN1Sequence},
     * {@link org.spongycastle.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);
            ASN1InputStream derInStream = new ASN1InputStream(inStream);
            ASN1Object obj = derInStream.readObject();

            if (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 ASN1ObjectIdentifier(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.spongycastle.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 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(Integers.valueOf(type));
        tmpList.add(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(Integers.valueOf(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 instanceof String)
                {
                    CertUtil.parseOID((String)item);
                    policyOID.add(new ASN1ObjectIdentifier((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(Integers.valueOf(type));
        tmpList.add(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(Integers.valueOf(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.spongycastle.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.spongycastle.asn1.DEROutputStream DEROutputStream},
     * {@link org.spongycastle.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.toASN1Primitive());
            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.spongycastle.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.spongycastle.asn1.DEROutputStream DEROutputStream},
     * {@link org.spongycastle.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.toASN1Primitive());
            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.spongycastle.asn1.ASN1InputStream ASN1InputStream},
     * {@link org.spongycastle.asn1.ASN1Object ASN1Object},
     * {@link org.spongycastle.asn1.x509.KeyPurposeId KeyPurposeId}
     * 
     * @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: ").append(x509Cert).append('\n');
        }
        if (serialNumber != null)
        {
            sb.append("  Serial Number: ").append(serialNumber).append('\n');
        }
        if (issuerDN != null)
        {
            sb.append("  Issuer: ").append(getIssuerAsString()).append('\n');
        }
        if (subjectDN != null)
        {
            sb.append("  Subject: ").append(getSubjectAsString()).append('\n');
        }
        try
        {
            if (subjectKeyID != null)
            {
                ByteArrayInputStream inStream = new ByteArrayInputStream(
                        subjectKeyID);
                ASN1InputStream derInStream = new ASN1InputStream(inStream);
                ASN1Object derObject = derInStream.readObject();
                sb.append("  Subject Key Identifier: ")
                       .append(ASN1Dump.dumpAsString(derObject)).append('\n');
            }
            if (authorityKeyID != null)
            {
                ByteArrayInputStream inStream = new ByteArrayInputStream(
                        authorityKeyID);
                ASN1InputStream derInStream = new ASN1InputStream(inStream);
                ASN1Object derObject = derInStream.readObject();
                sb.append("  Authority Key Identifier: ")
                       .append(ASN1Dump.dumpAsString(derObject)).append('\n');
            }
        }
        catch (IOException ex)
        {
            sb.append(ex.getMessage()).append('\n');
        }
        if (certValid != null)
        {
            sb.append("  Certificate Valid: ").append(certValid).append('\n');
        }
        if (privateKeyValid != null)
        {
            sb.append("  Private Key Valid: ").append(privateKeyValid)
                   .append('\n');
        }
        if (subjectKeyAlgID != null)
        {
            sb.append("  Subject Public Key AlgID: ")
                   .append(subjectKeyAlgID).append('\n');
        }
        if (subjectPublicKey != null)
        {
            sb.append("  Subject Public Key: ").append(subjectPublicKey)
                   .append('\n');
        }
        if (keyUsage != null)
        {
            sb.append("  Key Usage: ").append(keyUsage).append('\n');
        }
        if (keyPurposeSet != null)
        {
            sb.append("  Extended Key Usage: ").append(keyPurposeSet)
                   .append('\n');
        }
        if (policy != null)
        {
            sb.append("  Policy: ").append(policy).append('\n');
        }
        sb.append("  matchAllSubjectAltNames flag: ")
               .append(matchAllSubjectAltNames).append('\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));
                    ASN1InputStream derInStream = new ASN1InputStream(inStream);
                    ASN1Object derObject = derInStream.readObject();
                    sb.append("  Type: ").append(obj.get(0)).append(" Data: ")
                           .append(ASN1Dump.dumpAsString(derObject)).append('\n');
                }
            }
            catch (IOException ex)
            {
                sb.append(ex.getMessage()).append('\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));
                    ASN1InputStream derInStream = new ASN1InputStream(inStream);
                    ASN1Object derObject = derInStream.readObject();
                    sb.append("  Type: ").append(obj.get(0)).append(" Data: ")
                           .append(ASN1Dump.dumpAsString(derObject)).append('\n');
                }
            }
            catch (IOException ex)
            {
                sb.append(ex.getMessage()).append('\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.spongycastle.asn1.ASN1InputStream ASN1InputStream},
     * {@link org.spongycastle.asn1.ASN1Sequence ASN1Sequence},
     * {@link org.spongycastle.asn1.ASN1ObjectIdentifier ASN1ObjectIdentifier},
     * {@link org.spongycastle.asn1.ASN1Object ASN1Object},
     * {@link org.spongycastle.asn1.DERGeneralizedTime DERGeneralizedTime},
     * {@link org.spongycastle.asn1.x509.X509Name X509Name},
     * {@link org.spongycastle.asn1.x509.X509Extensions X509Extensions},
     * {@link org.spongycastle.asn1.x509.ExtendedKeyUsage ExtendedKeyUsage},
     * {@link org.spongycastle.asn1.x509.KeyPurposeId KeyPurposeId},
     * {@link org.spongycastle.asn1.x509.SubjectPublicKeyInfo SubjectPublicKeyInfo},
     * {@link org.spongycastle.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(PrincipalUtil
                        .getIssuerX509Principal(certX509), true))
                {
                    return false;
                }
            }
            if (subjectDNX509 != null)
            {
                if (!subjectDNX509.equals(PrincipalUtil
                        .getSubjectX509Principal(certX509), true))
                {
                    return false;
                }
            }
        }
        catch (Exception ex)
        {
            return false;
        }
        if (subjectKeyID != null)
        {
            byte[] data = certX509
                    .getExtensionValue(X509Extensions.SubjectKeyIdentifier
                            .getId());
            if (data == null)
            {
                return false;
            }
            try
            {
                ByteArrayInputStream inStream = new ByteArrayInputStream(data);
                ASN1InputStream derInputStream = new ASN1InputStream(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);
                ASN1InputStream derInputStream = new ASN1InputStream(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);
                    ASN1InputStream derInputStream = new ASN1InputStream(inStream);
                    inStream = new ByteArrayInputStream(
                            ((ASN1OctetString)derInputStream.readObject())
                                    .getOctets());
                    derInputStream = new ASN1InputStream(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)
            {
                return false;
            }
        }
        if (subjectKeyAlgID != null)
        {
            try
            {
                ByteArrayInputStream inStream = new ByteArrayInputStream(
                        certX509.getPublicKey().getEncoded());
                ASN1InputStream derInputStream = new ASN1InputStream(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);
                    ASN1InputStream derInputStream = new ASN1InputStream(inStream);
                    ExtendedKeyUsage extendedKeyUsage = ExtendedKeyUsage.getInstance(
                            (ASN1Sequence)derInputStream.readObject());
                    tempIter = keyPurposeSet.iterator();
                    while (tempIter.hasNext())
                    {
                        if (!extendedKeyUsage
                                .hasKeyPurposeId((KeyPurposeId)tempIter.next()))
                        {
                            return false;
                        }
                    }
                }
            }
            catch (Exception ex)
            {
                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);
                    ASN1InputStream derInputStream = new ASN1InputStream(inStream);
                    inStream = new ByteArrayInputStream(
                            ((ASN1OctetString)derInputStream.readObject())
                                    .getOctets());
                    derInputStream = new ASN1InputStream(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);
                ASN1InputStream derInputStream = new ASN1InputStream(inStream);
                inStream = new ByteArrayInputStream(
                        ((ASN1OctetString)derInputStream.readObject())
                                .getOctets());
                derInputStream = new ASN1InputStream(inStream);
                Enumeration altNamesSequence = ((ASN1Sequence)derInputStream
                        .readObject()).getObjects();
                ASN1TaggedObject altNameObject;
                boolean test = false;
                Set testSet = new HashSet(subjectAltNamesByte);
                List testList;
                ASN1Object derData;
                ByteArrayOutputStream outStream;
                DEROutputStream derOutStream;
                while (altNamesSequence.hasMoreElements() && !test)
                {
                    altNameObject = (ASN1TaggedObject)altNamesSequence
                            .nextElement();
                    testList = new ArrayList(2);
                    testList.add(Integers.valueOf(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 instanceof byte[])
            {
                copy.issuerDN = ((byte[])issuerDN).clone();
            }
            if (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 ASN1ObjectIdentifier(
                            ((ASN1ObjectIdentifier)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());
        }
    }
}
