/*
 * @(#)X509CertSelector.java	1.11 01/12/03
 *
 * Copyright 2002 Sun Microsystems, Inc. All rights reserved.
 * SUN PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
 */

package java.security.cert;

import java.io.IOException;
import java.math.BigInteger;
import java.security.PublicKey;
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.Iterator;
import java.util.List;
import java.util.Set;
import java.util.Vector;
import javax.security.auth.x500.X500Principal;
import sun.security.util.Debug;
import sun.misc.HexDumpEncoder;
import sun.security.util.DerInputStream;
import sun.security.util.DerValue;
import sun.security.util.ObjectIdentifier;
import sun.security.x509.AlgorithmId;
import sun.security.x509.CertificatePoliciesExtension;
import sun.security.x509.CertificatePolicyId;
import sun.security.x509.CertificatePolicySet;
import sun.security.x509.DNSName;
import sun.security.x509.EDIPartyName;
import sun.security.x509.ExtendedKeyUsageExtension;
import sun.security.x509.Extension;
import sun.security.x509.GeneralName;
import sun.security.x509.GeneralNameInterface;
import sun.security.x509.GeneralSubtree;
import sun.security.x509.GeneralSubtrees;
import sun.security.x509.IPAddressName;
import sun.security.x509.NameConstraintsExtension;
import sun.security.x509.OIDName;
import sun.security.x509.OtherName;
import sun.security.x509.PolicyInformation;
import sun.security.x509.PrivateKeyUsageExtension;
import sun.security.x509.RFC822Name;
import sun.security.x509.SubjectAlternativeNameExtension;
import sun.security.x509.URIName;
import sun.security.x509.X400Address;
import sun.security.x509.X500Name;
import sun.security.x509.X509CertImpl;
import sun.security.x509.X509Key;

/**
 * A <code>CertSelector</code> that selects <code>X509Certificates</code> that
 * match all specified criteria. This class is particularly useful when
 * selecting certificates from a <code>CertStore</code> to build a
 * PKIX-compliant certification path.
 * <p>
 * When first constructed, an <code>X509CertSelector</code> has no criteria
 * enabled and each of the <code>get</code> methods return a default value
 * (<code>null</code>, or <code>-1</code> for the {@link #getBasicConstraints
 * getBasicConstraints} method). Therefore, the {@link #match match} 
 * method would return <code>true</code> for any <code>X509Certificate</code>. 
 * Typically, several criteria are enabled (by calling 
 * {@link #setIssuer setIssuer} or
 * {@link #setKeyUsage setKeyUsage}, for instance) and then the
 * <code>X509CertSelector</code> is passed to
 * {@link CertStore#getCertificates CertStore.getCertificates} or some similar
 * method.
 * <p>
 * Several criteria can be enabled (by calling {@link #setIssuer setIssuer}
 * and {@link #setSerialNumber setSerialNumber},
 * for example) such that the <code>match</code> 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.
 * <p>
 * Please refer to RFC 2459 for definitions of the X.509 certificate
 * extensions mentioned below.
 * <p>
 * <b>Concurrent Access</b>
 * <p>
 * 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.
 *
 * @see CertSelector
 * @see X509Certificate
 *
 * @version 	1.11 12/03/01
 * @since	1.4
 * @author	Steve Hanna
 */
public class X509CertSelector
  implements CertSelector {

  private static final Debug debug = Debug.getInstance("certpath");

  private BigInteger serialNumber;
  private X500Name issuer;
  private X500Name subject;
  private byte [] subjectKeyID;
  private byte [] authorityKeyID;
  private Date certificateValid;
  private Date privateKeyValid;
  private ObjectIdentifier subjectPublicKeyAlgID;
  private PublicKey subjectPublicKey;
  private byte [] subjectPublicKeyBytes;
  private boolean [] keyUsage;
  private Set keyPurposeSet;
  private HashSet subjectAlternativeNames;
  private HashSet subjectAlternativeGeneralNames;
  private CertificatePolicySet policy;
  private Set policySet;
  private HashSet pathToNames;
  private HashSet pathToGeneralNames;
  private NameConstraintsExtension nc;
  private byte [] ncBytes;
  private int basicConstraints = -1;
  private X509Certificate x509Cert;
  private boolean matchAllSubjectAltNames = true;

  private static final Boolean falseBoolean = new Boolean(false);
  private static final int PRIVATE_KEY_USAGE_ID = 0;
  private static final int SUBJECT_ALT_NAME_ID = 1;
  private static final int NAME_CONSTRAINTS_ID = 2;  
  private static final int CERT_POLICIES_ID = 3; 
  private static final int EXTENDED_KEY_USAGE_ID = 4;
  private static final int NUM_OF_EXTENSIONS = 5;
  private static final String[] EXTENSION_OIDS = new String[NUM_OF_EXTENSIONS];
    static {
      EXTENSION_OIDS[PRIVATE_KEY_USAGE_ID]  = "2.5.29.16";
      EXTENSION_OIDS[SUBJECT_ALT_NAME_ID]   = "2.5.29.17";
      EXTENSION_OIDS[NAME_CONSTRAINTS_ID]   = "2.5.29.30";
      EXTENSION_OIDS[CERT_POLICIES_ID]      = "2.5.29.32";
      EXTENSION_OIDS[EXTENDED_KEY_USAGE_ID] = "2.5.29.37";
    };

  /* Constants representing the GeneralName types */
  static final int NAME_ANY = 0;
  static final int NAME_RFC822 = 1;
  static final int NAME_DNS = 2;
  static final int NAME_X400 = 3;
  static final int NAME_DIRECTORY = 4;
  static final int NAME_EDI = 5;
  static final int NAME_URI = 6;
  static final int NAME_IP = 7;
  static final int NAME_OID = 8;

  /**
   * 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 <code>match</code> method.
   * If <code>null</code>, then this check is not applied.
   *
   * <p>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 <code>X509Certificate</code> 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.
   * <p>
   * If <code>issuerDN</code> is not <code>null</code>, it should contain a
   * distinguished name, in RFC 2253 format.
   *
   * @param issuerDN a distinguished name in RFC 2253 format
   *                 (or <code>null</code>)
   * @throws IOException if a parsing error occurs (incorrect form for DN)
   */
  public void setIssuer(String issuerDN) throws IOException {
      issuer = (issuerDN == null ? null : new X500Name(issuerDN,"RFC2253"));
  }

  /**
   * Sets the issuer criterion. The specified distinguished name
   * must match the issuer distinguished name in the
   * <code>X509Certificate</code>. If <code>null</code> is specified,
   * the issuer criterion is disabled and any issuer distinguished name will
   * do.
   * <p>
   * 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.
   * <pre><code>
   * Name ::= CHOICE {
   *   RDNSequence }
   *
   * RDNSequence ::= SEQUENCE OF RelativeDistinguishedName
   *
   * RelativeDistinguishedName ::=
   *   SET SIZE (1 .. MAX) OF AttributeTypeAndValue
   *
   * AttributeTypeAndValue ::= SEQUENCE {
   *   type     AttributeType,
   *   value    AttributeValue }
   *
   * AttributeType ::= OBJECT IDENTIFIER
   *
   * AttributeValue ::= ANY DEFINED BY AttributeType
   * ....
   * DirectoryString ::= CHOICE {
   *       teletexString           TeletexString (SIZE (1..MAX)),
   *       printableString         PrintableString (SIZE (1..MAX)),
   *       universalString         UniversalString (SIZE (1..MAX)),
   *       utf8String              UTF8String (SIZE (1.. MAX)),
   *       bmpString               BMPString (SIZE (1..MAX)) }
   * </code></pre>
   * <p>
   * Note that the byte array specified here is cloned to protect against
   * subsequent modifications.
   *
   * @param issuerDN a byte array containing the distinguished name
   *                 in ASN.1 DER encoded form (or <code>null</code>)
   * @throws IOException if an encoding error occurs (incorrect form for DN)
   */
  public void setIssuer(byte [] issuerDN) throws IOException {
      issuer = (issuerDN == null ? null : new X500Name(issuerDN));
  }

  /**
   * Sets the subject criterion. The specified distinguished name
   * must match the subject distinguished name in the
   * <code>X509Certificate</code>. If <code>null</code>, any subject
   * distinguished name will do.
   * <p>
   * If <code>subjectDN</code> is not <code>null</code>, it should contain a
   * distinguished name, in RFC 2253 format.
   *
   * @param subjectDN a distinguished name in RFC 2253 format
   *                  (or <code>null</code>)
   * @throws IOException if a parsing error occurs (incorrect form for DN)
   */
  public void setSubject(String subjectDN) throws IOException {
      subject = (subjectDN == null ? null : new X500Name(subjectDN, "RFC2253"));
  }

  /**
   * Sets the subject criterion. The specified distinguished name
   * must match the subject distinguished name in the
   * <code>X509Certificate</code>. If <code>null</code>, any subject
   * distinguished name will do.
   * <p>
   * 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 [] issuerDN) setIssuer(byte [] issuerDN)}.
   *
   * @param subjectDN a byte array containing the distinguished name in
   *                  ASN.1 DER format (or <code>null</code>)
   * @throws IOException if an encoding error occurs (incorrect form for DN)
   */
  public void setSubject(byte [] subjectDN) throws IOException {
      subject = (subjectDN == null ? null : new X500Name(subjectDN));
  }

  /**
   * 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 <code>null</code>, no
   * subjectKeyIdentifier check will be done.
   * <p>
   * 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.
   * <p>
   * <pre><code>
   * SubjectKeyIdentifier ::= KeyIdentifier
   *
   * KeyIdentifier ::= OCTET STRING
   * </code></pre>
   * <p>
   * 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.
   * <p>
   * 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.
   * <p>
   * 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.
   * <p>
   * <pre><code>
   * AuthorityKeyIdentifier ::= SEQUENCE {
   *    keyIdentifier             [0] KeyIdentifier           OPTIONAL,
   *    authorityCertIssuer       [1] GeneralNames            OPTIONAL,
   *    authorityCertSerialNumber [2] CertificateSerialNumber OPTIONAL  }
   *
   * KeyIdentifier ::= OCTET STRING
   * </code></pre>
   * <p>
   * Authority key identifiers are not parsed by the
   * <code>X509CertSelector</code>.  Instead, the values are
   * compared using a byte-by-byte comparison.
   * <p>
   * When the <code>keyIdentifier</code> field of
   * <code>AuthorityKeyIdentifier</code> is populated, the value is
   * usually taken from the <code>SubjectKeyIdentifier</code> extension
   * in the issuer's certificate.  Note, however, that the result of
   * <code>X509Certificate.getExtensionValue(&lt;SubjectKeyIdentifier Object 
   * Identifier&gt;)</code> on the issuer's certificate may NOT be used 
   * directly as the input to <code>setAuthorityKeyIdentifier</code>.
   * 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
   * <code>SubjectKeyIdentifier</code>
   * extension, it will be necessary to extract the value of the embedded
   * <code>KeyIdentifier</code> OCTET STRING, then DER encode this OCTET
   * STRING inside a SEQUENCE.
   * For more details on SubjectKeyIdentifier, see
   * {@link #setSubjectKeyIdentifier(byte[] subjectKeyID)}.
   * <p>
   * 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
   * <code>X509Certificate</code>. If <code>null</code>, no certificateValid
   * check will be done.
   * <p>
   * Note that the <code>Date</code> supplied here is cloned to protect against
   * subsequent modifications.
   *
   * @param certValid the <code>Date</code> to check (or <code>null</code>)
   * @see #getCertificateValid
   */
  public void setCertificateValid(Date certValid) {
    if (certValid == null)
      certificateValid = null;
    else
      certificateValid = (Date) certValid.clone();
  }

  /**
   * Sets 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.
   * <p>
   * Note that the <code>Date</code> supplied here is cloned to protect against
   * subsequent modifications.
   *
   * @param privateKeyValid the <code>Date</code> to check (or
   *                        <code>null</code>)
   * @see #getPrivateKeyValid
   */
  public void setPrivateKeyValid(Date privateKeyValid) {
    if (privateKeyValid == null)
      this.privateKeyValid = null;
    else
      this.privateKeyValid = (Date) privateKeyValid.clone();
  }

  /**
   * Sets 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.
   * 
   * @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.
   * @throws 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 {
    if (oid == null)
       subjectPublicKeyAlgID = null;
    else
        subjectPublicKeyAlgID = new ObjectIdentifier(oid);
    }
    
  /**
   * 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.
   * 
   * @param key the subject public key to check for (or <code>null</code>)
   * @see #getSubjectPublicKey
   */
  public void setSubjectPublicKey(PublicKey key) {
    if (key == null) {
      subjectPublicKey = null;
      subjectPublicKeyBytes = null;
    } else {
      subjectPublicKey = key;
      subjectPublicKeyBytes = 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.
   * <p>
   * Because this method allows the public key to be specified as a byte
   * array, it may be used for unknown key types.
   * <p>
   * If <code>key</code> 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.
   * <pre><code>
   * 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
   * </code></pre>
   * <p>
   * 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>)
   * @throws 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;
      subjectPublicKeyBytes = null;
    } else {
      subjectPublicKeyBytes = (byte []) key.clone();
      subjectPublicKey = X509Key.parse(new DerValue(subjectPublicKeyBytes));
    }
  }

  /**
   * Sets the keyUsage criterion. The <code>X509Certificate</code>
   * must allow the specified keyUsage values. If <code>null</code>, no
   * keyUsage check will be done. Note that an <code>X509Certificate</code> 
   * that has no keyUsage extension implicitly allows all keyUsage values.
   * <p>
   * 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
   * {@link X509Certificate#getKeyUsage() 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.
   * <p>
   * Note that the <code>Set</code> is cloned to protect against
   * subsequent modifications.
   *
   * @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.
   * @throws 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 = null;
	} else {
	    this.keyPurposeSet = 
		Collections.unmodifiableSet(new HashSet(keyPurposeSet));
	}
    }

  /**
   * 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 
   * <code>X509Certificate</code> must contain at least one of the 
   * specified subject alternative names.
   *
   * <p>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) {
      this.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
   * setMatchAllSubjectAltNames}).
   * <p>
   * 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.
   * <p>
   * 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.
   * <p>
   * 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)}.
   * <p>
   * 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 getSubjectAlternativeNames} method.
   * <p>
   * Note that a deep copy is performed on the <code>Collection</code> to
   * protect against subsequent modifications.
   *
   * @param names a <code>Collection</code> of names (or <code>null</code>)
   * @throws IOException if a parsing error occurs
   * @see #getSubjectAlternativeNames
   */
  public void setSubjectAlternativeNames(Collection names) throws IOException {
    if (names == null) {
      subjectAlternativeNames = null;
      subjectAlternativeGeneralNames = null;
    } else {
      if (names.size() == 0) {
	subjectAlternativeNames = null;
	subjectAlternativeGeneralNames = null;
	return;
      }
      HashSet tempNames = cloneAndCheckNames(names);
      // Ensure that we either set both of these or neither
      subjectAlternativeGeneralNames =
	parseNames(tempNames);
      subjectAlternativeNames = tempNames;
    }
  }

  /**
   * 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
   * setMatchAllSubjectAltNames}).
   * <p>
   * 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.
   * <p>
   * 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 type, byte [] name) 
   * 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 <code>null</code>)
   * @throws IOException if a parsing error occurs
   */
  public void addSubjectAlternativeName(int type, String name)
    throws IOException {
    addSubjectAlternativeNameInternal(type, name);
  }

  /**
   * 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
   * setMatchAllSubjectAltNames}).
   * <p>
   * 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.
   * <p>
   * 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.
   * <pre><code>
   *  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}
   * </code></pre>
   * <p>
   * Note that the byte array supplied here is cloned to protect against
   * subsequent modifications.
   * 
   * @param type the name type (0-8, as listed above)
   * @param name a byte array containing the name in ASN.1 DER encoded form
   * @throws IOException if a parsing error occurs
   */
  public void addSubjectAlternativeName(int type, byte [] name)
    throws IOException {
    // clone because byte arrays are modifiable
    addSubjectAlternativeNameInternal(type, name.clone());
  }

  /**
   * A private method that adds a name (String or byte array) to the
   * subjectAlternativeNames criterion. The <code>X509Certificate</code>
   * must contain the specified subjectAlternativeName.
   *
   * @param type the name type (0-8, as specified in
   *             RFC 2459, section 4.2.1.7)
   * @param name the name in string or byte array form
   * @throws IOException if a parsing error occurs
   */
  private void addSubjectAlternativeNameInternal(int type, Object name)
    throws IOException {
    // First, ensure that the name parses
    GeneralNameInterface tempName =
      makeGeneralNameInterface(type, name);
    if (subjectAlternativeNames == null)
      subjectAlternativeNames = new HashSet();
    if (subjectAlternativeGeneralNames == null)
      subjectAlternativeGeneralNames = new HashSet();
    ArrayList list = new ArrayList();
    list.add(new Integer(type));
    list.add(name);
    subjectAlternativeNames.add(list);
    subjectAlternativeGeneralNames.add(tempName);
  }

  /**
   * Parse an argument of the form passed to setSubjectAlternativeNames,
   * returning a <code>Collection</code> of <code>GeneralNameInterface</code>s.
   * Throw an IllegalArgumentException or a ClassCastException
   * if the argument is malformed.
   *
   * @param names a Collection with one entry per name.
   *              Each entry is a <code>List</code> whose first entry
   *              is an Integer (the name type, 0-8) and whose second
   *              entry is a String or a byte array (the name, in
   *              string or ASN.1 DER encoded form, respectively).
   *              There can be multiple names of the same type. Null is
   *              not an acceptable value.
   * @return a HashSet of <code>GeneralNameInterface</code>s
   * @throws IOException if a parsing error occurs
   */
  private static HashSet parseNames(Collection names) throws IOException {
    HashSet genNames = new HashSet();
    Iterator i = names.iterator();
    while (i.hasNext()) {
      Object o = i.next();
      if (!(o instanceof List))
        throw new IOException("expected List");
      List nameList = (List) o;
      if (nameList.size() != 2)
        throw new IOException("name list size not 2");
      o =  nameList.get(0);
      if (!(o instanceof Integer))
        throw new IOException("expected an Integer");
      int nameType = ((Integer) o).intValue();
      o = nameList.get(1);
      genNames.add(makeGeneralNameInterface(nameType, o));
    }
    return(genNames);
  }

  /**
   * Compare for equality two objects of the form passed to
   * setSubjectAlternativeNames (or X509CRLSelector.setIssuerNames).
   * Throw an <code>IllegalArgumentException</code> or a
   * <code>ClassCastException</code> if one of the objects is malformed.
   *
   * @param object1 a Collection containing the first object to compare
   * @param object2 a Collection containing the second object to compare
   * @return true if the objects are equal, false otherwise
   */
  static boolean equalNames(Collection object1, Collection object2) {
    if ((object1 == null) && (object2 == null))
      return true;
    if ((object1 == null) || (object2 == null))
      return false;
    return object1.equals(object2);
  }

  /**
   * Make a <code>GeneralNameInterface</code> out of a name type (0-8) and an
   * Object that may be a byte array holding the ASN.1 DER encoded
   * name or a String form of the name.  Except for X.509
   * Distinguished Names, the String form of the name must not be the
   * result from calling toString on an existing GeneralNameInterface
   * implementing class.  The output of toString is not compatible
   * with the String constructors for names other than Distinguished
   * Names.
   *
   * @param type name type (0-8)
   * @param name name as ASN.1 Der-encoded byte array or String
   * @return a GeneralNameInterface name
   * @throws IOException if a parsing error occurs
   */
  static GeneralNameInterface makeGeneralNameInterface(int type, Object name)
    throws IOException {
    GeneralNameInterface result;
    if (debug != null)
        debug.println("X509CertSelector.makeGeneralNameInterface(" + type + ")...");

    if (name instanceof String) {
        if (debug != null)
            debug.println("X509CertSelector.makeGeneralNameInterface() name is "
		+ "String: " + (String)name);
        switch (type) {
        case NAME_RFC822:
          result = new RFC822Name((String)name);
          break;
        case NAME_DNS:
          result = new DNSName((String)name);
          break;
        case NAME_DIRECTORY:
          result = new X500Name((String)name, "RFC2253");
          break;
        case NAME_URI:
          result = new URIName((String)name);
          break;
        case NAME_IP:
          result = new IPAddressName((String)name);
          break;
        case NAME_OID:
          result = new OIDName((String)name);
          break;
        default:
          throw new IOException("unable to parse String names of type " + type);
        }
        if (debug != null)
            debug.println("X509CertSelector.makeGeneralNameInterface() result: "
		+ result.toString());
    } else if (name instanceof byte []) {
        DerValue val = new DerValue((byte []) name);
        if (debug != null)
            debug.println("X509CertSelector.makeGeneralNameInterface() is byte[]");

        switch (type) {
        case NAME_ANY:
          result = new OtherName(val);
          break;
        case NAME_RFC822:
          result = new RFC822Name(val);
          break;
        case NAME_DNS:
          result = new DNSName(val);
          break;
        case NAME_X400:
          result = new X400Address(val);
          break;
        case NAME_DIRECTORY:
          result = new X500Name(val);
          break;
        case NAME_EDI:
          result = new EDIPartyName(val);
          break;
        case NAME_URI:
          result = new URIName(val);
          break;
        case NAME_IP:
          result = new IPAddressName(val);
          break;
        case NAME_OID:
          result = new OIDName(val);
          break;
        default:
          throw new IOException("unable to parse byte array names of type " 
	      + type);
        }
        if (debug != null)
            debug.println("X509CertSelector.makeGeneralNameInterface() result: "
		+ result.toString());
    } else {
        if (debug != null)
            debug.println("X509CertSelector.makeGeneralName() input name not "
		+ "String or byte array");
        throw new IOException("name not String or byte array");
    }
    return result;
  }


  /**
   * Sets the name constraints criterion. The <code>X509Certificate</code>
   * must have subject and subject alternative names that
   * meet the specified name constraints.
   * <p>
   * 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.
   *
   * <pre><code>
   *  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}
   * </code></pre>
   * <p>
   * Note that the byte array supplied here is cloned to protect against
   * subsequent modifications.
   *
   * @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.
   * @throws IOException if a parsing error occurs
   * @see #getNameConstraints
   */
  public void setNameConstraints(byte [] bytes) throws IOException {
    if (bytes == null) {
      ncBytes = null;
      nc = null;
    } else {
      ncBytes = (byte []) bytes.clone();
      nc = new NameConstraintsExtension(falseBoolean, bytes);
    }
  }

  /**
   * 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.
   * <p>
   * 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
   * @throws IllegalArgumentException if the value is less than -2
   * @see #getBasicConstraints
   */
  public void setBasicConstraints(int minMaxPathLen) {
    if (minMaxPathLen < -2)
      throw new IllegalArgumentException("basic constraints less than -2");
    basicConstraints = minMaxPathLen;
  }

  /**
   * Sets the policy constraint. The <code>X509Certificate</code> must
   * include at least one of the specified policies in its certificate policies
   * extension. If <code>certPolicySet</code> is empty, then the 
   * <code>X509Certificate</code> must include at least some specified policy
   * in its certificate policies extension. If <code>certPolicySet</code> is
   * <code>null</code>, no policy check will be performed.
   * <p>
   * Note that the <code>Set</code> is cloned to protect against
   * subsequent modifications.
   *
   * @param certPolicySet a <code>Set</code> of certificate policy OIDs in
   *                      string format (or <code>null</code>). Each OID is 
   *                      represented by a set of nonnegative integers 
   *			  separated by periods.
   * @throws 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) {
      policySet = null;
      policy = null;
    } else {
      // Snapshot set and parse it
      Set tempSet = Collections.unmodifiableSet(
					    new HashSet(certPolicySet));
      /* Convert to Vector of ObjectIdentifiers */
      Iterator i = tempSet.iterator();
      Vector polIdVector = new Vector();
      while (i.hasNext()) {
	Object o = i.next();
	if (!(o instanceof String))
	  throw new IOException("non String in certPolicySet");
	polIdVector.add(new CertificatePolicyId(new ObjectIdentifier(
          (String) o)));
      }
      // If everything went OK, make the changes
      policySet = tempSet;
      policy = new CertificatePolicySet(polIdVector);
    }
  }

  /**
   * Sets the pathToNames criterion. The <code>X509Certificate</code> must
   * not include name constraints that would prohibit building a
   * path to the specified names.
   * <p>
   * 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.
   * <p>
   * 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.
   * <p>
   * 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 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. If <code>null</code>
   * is supplied as the value for this argument, no
   * pathToNames check will be performed.
   * <p>
   * 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)}.
   * <p>
   * 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 #getPathToNames getPathToNames} method.
   * <p>
   * Note that a deep copy is performed on the <code>Collection</code> to
   * protect against subsequent modifications.
   *
   * @param names a <code>Collection</code> with one entry per name
   *              (or <code>null</code>)
   * @throws IOException if a parsing error occurs
   * @see #getPathToNames
   */
  public void setPathToNames(Collection names) throws IOException {
    if (names == null) {
      pathToNames = null;
      pathToGeneralNames = null;
    } else {
      if (names.size() == 0) {
	pathToNames = null;
	pathToGeneralNames = null;
	return;
      }
      HashSet tempNames = cloneAndCheckNames(names);
      pathToGeneralNames = parseNames(tempNames);
      // Ensure that we either set both of these or neither
      pathToNames = tempNames;
    }
  }

  /**
   * 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.
   * <p>
   * 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.
   * <p>
   * 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 type, byte [] name) 
   * addPathToName(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
   * @throws IOException if a parsing error occurs
   */
  public void addPathToName(int type, String name) throws IOException {
    addPathToNameInternal(type, name);
  }

  /**
   * 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.
   * <p>
   * 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.
   * <p>
   * 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 type, byte [] name) 
   * addSubjectAlternativeName(int type, byte [] name)}.
   * <p>
   * Note that the byte array supplied here is cloned to protect against
   * subsequent modifications.
   *
   * @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
   * @throws IOException if a parsing error occurs
   */
  public void addPathToName(int type, byte [] name) throws IOException {
    // clone because byte arrays are modifiable
    addPathToNameInternal(type, name.clone());
  }

  /**
   * A private method that adds a name (String or byte array) to the
   * pathToNames criterion. The <code>X509Certificate</code> must contain
   * the specified pathToName.
   *
   * @param type the name type (0-8, as specified in
   *             RFC 2459, section 4.2.1.7)
   * @param name the name in string or byte array form
   * @throws IOException if an encoding error occurs (incorrect form for DN)
   */
  private void addPathToNameInternal(int type, Object name)
    throws IOException {
    // First, ensure that the name parses
    GeneralNameInterface tempName =
      makeGeneralNameInterface(type, name);
    if (pathToNames == null)
      pathToNames = new HashSet();
    if (pathToGeneralNames == null)
      pathToGeneralNames = new HashSet();
    ArrayList list = new ArrayList();
    list.add(new Integer(type));
    list.add(name);
    pathToNames.add(list);
    pathToGeneralNames.add(tempName);
  }

  /**
   * Returns the certificateEquals criterion. The specified
   * <code>X509Certificate</code> must be equal to the 
   * <code>X509Certificate</code> passed to the <code>match</code> method.
   * If <code>null</code>, this check is not applied.
   *
   * @return the <code>X509Certificate</code> to match (or <code>null</code>)
   * @see #setCertificate
   */
  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
   */
  public BigInteger getSerialNumber() {
    return serialNumber;
  }

  /**
   * Returns the issuer criterion as a <code>String</code>. 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.
   * <p>
   * If the value returned is not <code>null</code>, it is a
   * distinguished name, in RFC 2253 format.
   *
   * @return the required issuer distinguished name in RFC 2253 format
   *         (or <code>null</code>)
   */
  public String getIssuerAsString() {
    return (issuer == null ? null : issuer.getRFC2253Name());
  }

  /**
   * 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.
   * <p>
   * 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 [] issuerDN) setIssuer(byte [] issuerDN)}.
   * <p>
   * Note that the byte array returned is cloned to protect against
   * subsequent modifications.
   *
   * @return a byte array containing the required issuer distinguished name
   *         in ASN.1 DER format (or <code>null</code>)
   * @throws IOException if an encoding error occurs
   */
  public byte [] getIssuerAsBytes() throws IOException {
    return (issuer == null ? null: issuer.getEncoded()); 
  }

  /**
   * Returns the subject criterion as a <code>String</code>. 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.
   * <p>
   * If the value returned is not <code>null</code>, it is a
   * distinguished name, in RFC 2253 format.
   *
   * @return the required subject distinguished name in RFC 2253 format
   *         (or <code>null</code>)
   */
  public String getSubjectAsString() {
    return (subject == null ? null : subject.getRFC2253Name());
  }

  /**
   * 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.
   * <p>
   * 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)}.
   * <p>
   * Note that the byte array returned is cloned to protect against
   * subsequent modifications.
   *
   * @return a byte array containing the required subject distinguished name
   *         in ASN.1 DER format (or <code>null</code>)
   * @throws IOException if an encoding error occurs
   */
  public byte [] getSubjectAsBytes() throws IOException {
    return (subject == null ? null : subject.getEncoded());
  }

  /**
   * 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.
   * <p>
   * 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 null;
    return (byte[]) subjectKeyID.clone();
  }

  /**
   * 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.
   * <p>
   * 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 null;
    return (byte[]) authorityKeyID.clone();
  }

  /**
   * 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.
   * <p>
   * 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 (certificateValid == null)
      return null;
    return (Date) certificateValid.clone();
  }

  /**
   * 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.
   * <p>
   * 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 null;
    return (Date) privateKeyValid.clone();
  }

  /**
   * 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 (subjectPublicKeyAlgID == null)
      return null;
    return subjectPublicKeyAlgID.toString();
  }

  /**
   * 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.
   * <p>
   * 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 null;
     return (boolean []) keyUsage.clone();
   }

  /**
   * 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() {
      return keyPurposeSet;
  }

  /**
   * 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.
   * <p>
   * 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).
   * <p>
   * 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)}.
   * <p>
   * 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 (subjectAlternativeNames == null)
      return null;
    return(cloneNames(subjectAlternativeNames));
  }

  /**
   * Clone an object of the form passed to
   * setSubjectAlternativeNames and setPathToNames.
   * Throw a <code>RuntimeException</code> if the argument is malformed.
   * <p>
   * This method wraps cloneAndCheckNames, changing any
   * <code>IOException</code> into a <code>RuntimeException</code>. This
   * method should be used when the object being
   * cloned has already been checked, so there should never be any exceptions.
   *
   * @param names a <code>Collection</code> with one entry per name.
   *              Each entry is a <code>List</code> whose first entry
   *              is an Integer (the name type, 0-8) and whose second
   *              entry is a String or a byte array (the name, in
   *              string or ASN.1 DER encoded form, respectively).
   *              There can be multiple names of the same type. Null
   *              is not an acceptable value.
   * @return a deep copy of the specified <code>Collection</code>
   * @throws RuntimeException if a parsing error occurs
   */
  private static HashSet cloneNames(Collection names) {
    try {
      return cloneAndCheckNames(names);
    } catch (IOException e) {
      throw new RuntimeException("cloneNames encountered IOException: " +
                                 e.getMessage());
    }
  }

  /**
   * Clone and check an argument of the form passed to
   * setSubjectAlternativeNames and setPathToNames.
   * Throw an <code>IOException</code> if the argument is malformed.
   *
   * @param names a <code>Collection</code> with one entry per name.
   *              Each entry is a <code>List</code> whose first entry
   *              is an Integer (the name type, 0-8) and whose second
   *              entry is a String or a byte array (the name, in
   *              string or ASN.1 DER encoded form, respectively).
   *              There can be multiple names of the same type.
   *              <code>null</code> is not an acceptable value.
   * @return a deep copy of the specified <code>Collection</code>
   * @throws IOException if a parsing error occurs
   */
  private static HashSet cloneAndCheckNames(Collection names) throws IOException {
    // Copy the Lists and Collection
    HashSet namesCopy = new HashSet();
    Iterator i = names.iterator();
    while (i.hasNext()) {
      Object o = i.next();
      if (!(o instanceof List))
        throw new IOException("expected a List");
      namesCopy.add(new ArrayList((List) o));
    }

    // Check the contents of the Lists and clone any byte arrays
    i = namesCopy.iterator();
    while (i.hasNext()) {
      List nameList = (List) i.next();
      if (nameList.size() != 2)
        throw new IOException("name list size not 2");
      Object o = nameList.get(0);
      if (!(o instanceof Integer))
        throw new IOException("expected an Integer");
      int nameType = ((Integer) o).intValue();
      if ((nameType < 0) || (nameType > 8))
        throw new IOException("name type not 0-8");
      Object nameObject = nameList.get(1);
      if (!(nameObject instanceof byte []) &&
          !(nameObject instanceof String)) {
          if (debug != null)
              debug.println("X509CertSelector.cloneAndCheckNames() name not "
		  + "byte array");
        throw new IOException("name not byte array or String");
      }
      if (nameObject instanceof byte [])
        nameList.set(1, ((byte []) nameObject).clone());
    }
    return(namesCopy);
  }

  /**
   * Returns the name constraints criterion. The <code>X509Certificate</code>
   * must have subject and subject alternative names that
   * meet the specified name constraints.
   * <p>
   * 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)}.
   * <p>
   * Note that the byte array returned is cloned to protect against
   * subsequent modifications.
   *
   * @return a byte array containing the ASN.1 DER encoding of
   *         a NameConstraints extension used for checking name constraints.
   *         <code>null</code> if no name constraints check will be performed.
   * @see #setNameConstraints
   */
  public byte [] getNameConstraints() {
    if (ncBytes == null)
      return null;
    else
      return (byte []) ncBytes.clone();
  }

  /**
   * 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 basicConstraints;
  }

  /**
   * 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() {
    return policySet;
  }

  /**
   * 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.
   * <p>
   * 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).
   * <p>
   * 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)}.
   * <p>
   * 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;
    return(cloneNames(pathToNames));
  }

  /**
   * Return a printable representation of the <code>CertSelector</code>.
   *
   * @return a <code>String</code> describing the contents of the
   *         <code>CertSelector</code>
   */
  public String toString() {
    StringBuffer sb = new StringBuffer();
    sb.append("X509CertSelector: [\n");
    if (x509Cert != null)
        sb.append("  Certificate: " + x509Cert.toString() + "\n");
    if (serialNumber != null)
      sb.append("  Serial Number: " + 
		serialNumber.toString() + "\n");
    if (issuer != null)
      sb.append("  Issuer: " + 
		getIssuerAsString() + "\n");
    if (subject != null)
      sb.append("  Subject: " + 
		getSubjectAsString() + "\n");
    sb.append("  matchAllSubjectAltNames flag: " 
              + String.valueOf(matchAllSubjectAltNames) + "\n");
    if (subjectAlternativeNames != null) {
      sb.append("  SubjectAlternativeNames:\n");
      Iterator i = subjectAlternativeNames.iterator();
      while (i.hasNext()) {
	List list = (List) i.next();
	sb.append("    type " + list.get(0) +
		  ", name " + list.get(1) + "\n");
      }
    }
    if (subjectKeyID != null) {
      HexDumpEncoder enc = new HexDumpEncoder();
      sb.append("  Subject Key Identifier: " + 
		enc.encodeBuffer(subjectKeyID) + "\n");
    }
    if (authorityKeyID != null) {
      HexDumpEncoder enc = new HexDumpEncoder();
      sb.append("  Authority Key Identifier: " + 
		enc.encodeBuffer(authorityKeyID) + "\n");
    }
    if (certificateValid != null)
      sb.append("  Certificate Valid: " + 
		certificateValid.toString() + "\n");
    if (privateKeyValid != null)
      sb.append("  Private Key Valid: " + 
		privateKeyValid.toString() + "\n");
    if (subjectPublicKeyAlgID != null)
      sb.append("  Subject Public Key AlgID: " + 
		subjectPublicKeyAlgID.toString() + "\n");
    if (subjectPublicKey != null)
      sb.append("  Subject Public Key: " + 
		subjectPublicKey.toString() + "\n");
    if (keyUsage != null)
      sb.append("  Key Usage: " + keyUsageToString(keyUsage) + "\n");
    if (keyPurposeSet != null)
      sb.append("  Extended Key Usage: " + 
		keyPurposeSet.toString() + "\n");
    if (policy != null)
      sb.append("  Policy: " + policy.toString() + "\n");
    if (pathToNames != null) {
      sb.append("  Path to names:\n");
      Iterator i = pathToGeneralNames.iterator();
      while (i.hasNext())
	sb.append("    " + i.next() + "\n");
    }
    sb.append("]");
    return sb.toString();
  }

  // Copied from sun.security.x509.KeyUsageExtension
  // (without calling the superclass)
  /**
   * Returns a printable representation of the KeyUsage.
   */
  private static String keyUsageToString(boolean[] k) {
    String s = "KeyUsage [\n";
    try {
      if (k[0])
	s += "  DigitalSignature\n";
      if (k[1])
	s += "  Non_repudiation\n";
      if (k[2])
	s += "  Key_Encipherment\n";
      if (k[3])
	s += "  Data_Encipherment\n";
      if (k[4])
	s += "  Key_Agreement\n";
      if (k[5])
	s += "  Key_CertSign\n";
      if (k[6])
	s += "  Crl_Sign\n";
      if (k[7])
	s += "  Encipher_Only\n";
      if (k[8])
	s += "  Decipher_Only\n";
    } catch (ArrayIndexOutOfBoundsException ex) {}

    s += "]\n";

    return (s);
  }

  /**
   * Returns an Extension object given any X509Certificate and extension oid.
   * Throw an <code>IOException</code> if the extension byte value is 
   * malformed.
   *
   * @param cert a <code>X509Certificate</code> 
   * @param extId an <code>integer</code> which specifies the extension index. 
   * Currently, the supported extensions are as follows:
   * index 0 - PrivateKeyUsageExtension
   * index 1 - SubjectAlternativeNameExtension
   * index 2 - NameConstraintsExtension   
   * index 3 - CertificatePoliciesExtension
   * index 4 - ExtendedKeyUsageExtension
   * @return an <code>Extension</code> object whose real type is as specified
   * by the extension oid.
   * @throws IOException if cannot construct the <code>Extension</code> 
   * object with the extension encoding retrieved from the passed in 
   * <code>X509Certificate</code>.
   */
  private static Extension getExtensionObject(X509Certificate cert, int extId)
      throws IOException {
    Extension ext = null;
    if (cert instanceof X509CertImpl) {
	switch (extId) {
	case PRIVATE_KEY_USAGE_ID:
	    ext = ((X509CertImpl)cert).getPrivateKeyUsageExtension();
	    break;
	case SUBJECT_ALT_NAME_ID:
	    ext = ((X509CertImpl)cert).getSubjectAlternativeNameExtension();
	    break;
	case NAME_CONSTRAINTS_ID:
	    ext = ((X509CertImpl)cert).getNameConstraintsExtension();
	    break;
	case CERT_POLICIES_ID:
	    ext = ((X509CertImpl)cert).getCertificatePoliciesExtension();
	    break;
	case EXTENDED_KEY_USAGE_ID:
	    ext = ((X509CertImpl)cert).getExtendedKeyUsageExtension();
	    break;
	}
    } else {
      byte[] rawExtVal = cert.getExtensionValue(EXTENSION_OIDS[extId]);
      if (rawExtVal != null) {
	DerInputStream in = new DerInputStream(rawExtVal);
        byte[] encoded = in.getOctetString();
	switch (extId) {
	case PRIVATE_KEY_USAGE_ID:
	    try {
		ext = new PrivateKeyUsageExtension(Boolean.FALSE, encoded);
	    } catch (CertificateException ex) {
		throw new IOException(ex.getMessage());
	    }
	    break;
	case SUBJECT_ALT_NAME_ID:
	    ext = new SubjectAlternativeNameExtension(Boolean.FALSE, encoded);
	    break;
	case NAME_CONSTRAINTS_ID:
	    ext = new NameConstraintsExtension(Boolean.FALSE, encoded);
	    break;
	case CERT_POLICIES_ID:
	    ext = new CertificatePoliciesExtension(Boolean.FALSE, encoded);
	    break;
	case EXTENDED_KEY_USAGE_ID:
	    ext = new ExtendedKeyUsageExtension(Boolean.FALSE, encoded);
	    break;
	}
      }
    }
    return ext;
  }

  /**
   * Decides whether a <code>Certificate</code> should be selected.
   *
   * @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) {
    if (!(cert instanceof X509Certificate))
      return(false);
    X509Certificate xcert = (X509Certificate) cert;

    if (debug != null)
    	debug.println("X509CertSelector.match(SN: " 
	    + (xcert.getSerialNumber()).toString(16) + "\n  Issuer: " 
	    + xcert.getIssuerDN() + "\n  Subject: " + xcert.getSubjectDN() 
	    + ")");

    /* match on X509Certificate */
    if (x509Cert != null) {
        if (!x509Cert.equals(xcert)) {
	    if (debug != null)
	        debug.println("X509CertSelector.match: certs don't match");
	    return false;
	}
    }
    
    /* match on serial number */
    if (serialNumber != null) {
      if (!serialNumber.equals(xcert.getSerialNumber())) {
	if (debug != null)
	    debug.println("X509CertSelector.match: serial numbers don't match");
	return false;
      }
    }

    /* match on issuer name */
    if (issuer != null) {
      if (!issuer.equals(xcert.getIssuerX500Principal())) {
	if (debug != null)
	    debug.println("X509CertSelector.match: issuer DNs don't match");
	return false;
      }
    }

    /* match on subject name */
    if (subject != null) {
      if (!subject.equals(xcert.getSubjectX500Principal())) {
	if (debug != null)
	    debug.println("X509CertSelector.match: subject DNs don't match");
	return false;
      }
    }

    /* match on subject key identifier extension value */
    if (subjectKeyID != null) {
      try {
	byte[] extVal = xcert.getExtensionValue("2.5.29.14");
	if (extVal == null) {
	  if (debug != null)
	      debug.println("X509CertSelector.match: no subject key ID extension");
	  return false;
	}
	DerInputStream in = new DerInputStream(extVal);
	byte[] certSubjectKeyID = in.getOctetString();
	if (certSubjectKeyID == null ||
	  !Arrays.equals(subjectKeyID, certSubjectKeyID)) {
	  if (debug != null)
  	      debug.println("X509CertSelector.match: subject key IDs don't match");
	  return false;
	}
      } catch (IOException ex) {
	if (debug != null)
	    debug.println("X509CertSelector.match: exception in subject key ID "
		+ "check");
	return false;
      }
    }

    /* match on authority key identifier extension value */
    if (authorityKeyID != null) {
      try {
	byte[] extVal = xcert.getExtensionValue("2.5.29.35");
	if (extVal == null) {
	  if (debug != null)
  	      debug.println("X509CertSelector.match: no authority key ID "
		  + "extension");
	  return false;
	}
	DerInputStream in = new DerInputStream(extVal);
	byte[] certAuthKeyID = in.getOctetString();
	if (certAuthKeyID == null ||
	    !Arrays.equals(authorityKeyID, certAuthKeyID)) {
	  if (debug != null)
  	      debug.println("X509CertSelector.match: authority key IDs don't "
		  + "match");
	  return false;
	}
      } catch (IOException ex) {
	if (debug != null)
	    debug.println("X509CertSelector.match: exception in authority key "
		+ "ID check");
	return false;
      }
    }

    /* match on certificate validity range */
    if (certificateValid != null) {
      try {
	xcert.checkValidity(certificateValid);
      } catch (CertificateExpiredException e1) {
	if (debug != null)
	    debug.println("X509CertSelector.match: certificate not within "
		+ "validity date");
	return false;
      } catch (CertificateNotYetValidException e2) {
	if (debug != null)
	    debug.println("X509CertSelector.match: certificate not within "
		+ "validity date");
	return false;
      } 
    }

    /* match on private key usage range */
    if (privateKeyValid != null) {
      PrivateKeyUsageExtension ext = null;
      try {
        ext = (PrivateKeyUsageExtension) 
	    getExtensionObject(xcert, PRIVATE_KEY_USAGE_ID);
        if (ext != null) {
	  ext.valid(privateKeyValid);
	}
      } catch (CertificateExpiredException e1) {
	if (debug != null) {
	  String time = "n/a";
	  try {
	    Date notAfter = (Date) ext.get(PrivateKeyUsageExtension.NOT_AFTER);
	    time = notAfter.toString();
	  }
	  catch (CertificateException ex) {
	    // not able to retrieve notAfter value
	  }
	  debug.println("X509CertSelector.match: private key usage not "
	      + "within validity date; ext.NOT_After: " 
	      + time + "; X509CertSelector: " 
	      + this.toString());
	  e1.printStackTrace();
	}
	return false;
      } catch (CertificateNotYetValidException e2) {
	if (debug != null) {
	  String time = "n/a";
	  try {
	    Date notBefore = (Date) 
		ext.get(PrivateKeyUsageExtension.NOT_BEFORE);
	    time = notBefore.toString();
	  } catch (CertificateException ex) {
	    // not able to retrieve notBefore value
	  }
	  debug.println("X509CertSelector.match: private key usage not "
	      + "within validity date; ext.NOT_BEFORE: " 
	      + time + "; X509CertSelector: " 
	      + this.toString());
	  e2.printStackTrace();
	}
	return false;
      } catch (CertificateException e3) {
	if (debug != null) {
	  debug.println("X509CertSelector.match: CertificateException in "
	      + "private key usage check; X509CertSelector: " 
	      + this.toString());
	  e3.printStackTrace();
	}
	return false;
      } catch (IOException e4) {
	if (debug != null) {
	  debug.println("X509CertSelector.match: IOException in "
	      + "private key usage check; X509CertSelector: " 
	      + this.toString());
	  e4.printStackTrace();
	}
	return false;
      }
    }

    /* match on subject public key algorithm OID */
    if (subjectPublicKeyAlgID != null) {
      try {
	byte [] encodedKey = xcert.getPublicKey().getEncoded();
	DerValue val = new DerValue(encodedKey);
	if (val.tag != DerValue.tag_Sequence)
	  throw new IOException("invalid key format");

	AlgorithmId algID = AlgorithmId.parse(val.data.getDerValue());
	if (debug != null)
	    debug.println("X509CertSelector.match: subjectPublicKeyAlgID = " 
		+ subjectPublicKeyAlgID + ", xcert subjectPublicKeyAlgID = " 
		+ algID.getOID()); 
	if (!subjectPublicKeyAlgID.equals(algID.getOID())) {
	  if (debug != null)
  	      debug.println("X509CertSelector.match: subject public key alg "
		  + "IDs don't match");
	  return false;
	}
      } catch (IOException e5) {
	if (debug != null)
	    debug.println("X509CertSelector.match: IOException in subject "
		+ "public key algorithm OID check");
	return false;
      } 
    }	

    /* match on subject public key */
    if (subjectPublicKeyBytes != null) {
      byte [] certKey = xcert.getPublicKey().getEncoded();
      if (!Arrays.equals(subjectPublicKeyBytes, certKey)) {
	if (debug != null)
	    debug.println("X509CertSelector.match: subject public keys don't match");
	return false;
      }
    }

    /* match on key usage extension value */
    if (keyUsage != null) {
      boolean[] certKeyUsage = xcert.getKeyUsage();
      if (certKeyUsage != null) {
	for (int keyBit = 0; keyBit < keyUsage.length; keyBit++) {
	  if (keyUsage[keyBit] &&
	      ((keyBit >= certKeyUsage.length) || !certKeyUsage[keyBit])) {
	    if (debug != null)
	    	debug.println("X509CertSelector.match: key usage bits don't match");
	    return false;
	  }
	}
      }
    }

    /* match on extended key usage purpose OIDs */
    if (keyPurposeSet != null && !keyPurposeSet.isEmpty()) {
      try {
        ExtendedKeyUsageExtension ext = 
	    (ExtendedKeyUsageExtension) getExtensionObject(xcert, 
					    EXTENDED_KEY_USAGE_ID);
        if (ext != null) {
	  Vector certKeyPurposeVector = 
	      (Vector) ext.get(ExtendedKeyUsageExtension.USAGES);
	  /*
	   * Convert Set of key purpose String OIDs to a Vector of
 	   * ObjectIdentifiers for easier comparision.
	   */
          Set keyPurposeOIDs = new HashSet();
          Iterator iter = keyPurposeSet.iterator();
          while (iter.hasNext()) {
            keyPurposeOIDs.add(new ObjectIdentifier((String)iter.next()));
          }
          if (!certKeyPurposeVector.containsAll(keyPurposeOIDs)) {
	    if (debug != null)
	        debug.println("X509CertSelector.match: cert failed "
		    + "extendedKeyUsage criterion");
	    return false;
          }
        }
      } catch (IOException ex) {
	if (debug != null)
	  debug.println("X509CertSelector.match: IOException in extended "
	      + "key usage check");
	return false;
      }
    }

    /* match on subject alternative name extension names */
    if ((subjectAlternativeNames != null) &&
	(subjectAlternativeNames.size() != 0)) {
      try {
        SubjectAlternativeNameExtension sanExt = 
	    (SubjectAlternativeNameExtension) getExtensionObject(xcert, 
						  SUBJECT_ALT_NAME_ID); 
        if (sanExt == null) {
	  if (debug != null)
	    debug.println("X509CertSelector.match: no subject alternative " + 
		"name extension");
	  return false;
        }
	Vector certNames = (Vector) 
	  sanExt.get(SubjectAlternativeNameExtension.SUBJECT_NAME);
	Iterator i = subjectAlternativeGeneralNames.iterator();
        while (i.hasNext()) {
          GeneralNameInterface matchName = (GeneralNameInterface) i.next();
	  boolean found = false;
	  for (Enumeration e = certNames.elements();
	       e.hasMoreElements() && !found;) {
	    GeneralNameInterface certName =
	      ((GeneralName) e.nextElement()).getName();
	    found = certName.equals(matchName);
	  }
	  if (!found && (matchAllSubjectAltNames || !i.hasNext())) {
	    if (debug != null)
	      debug.println("X509CertSelector.match: subject alternative "
	          + "name " + matchName + " not found"); 
	    return false;
	  } else if (found && !matchAllSubjectAltNames) {
	    break;
	  }
	}
      } catch (IOException ex) {
	if (debug != null)
	    debug.println("X509CertSelector.match: IOException in subject "
		+ "alternative name check");
	return false;
      }
    }

    /* match on name constraints */
    if (nc != null) {
      try {
	if (!nc.verify(xcert)) {
	  if (debug != null)
	      debug.println("X509CertSelector.match: name constraints not "
		  + "satisfied");
	  return false;
	}
      } catch (IOException e) { 
	if (debug != null)
	    debug.println("X509CertSelector.match: IOException in name "
		+ "constraints check");
	return false; 
      }
    }

    /* match on policy OIDs */
    if (policy != null) {
      try {
        CertificatePoliciesExtension ext = 
	    (CertificatePoliciesExtension) getExtensionObject(xcert, 
					       CERT_POLICIES_ID);
        if (ext == null) {
	  if (debug != null)
	    debug.println("X509CertSelector.match: no certificate policy "
		+ "extension");
	  return false;
        }
	Vector policies = (Vector) ext.get(CertificatePoliciesExtension.POLICIES);
	/*
	 * Convert the Vector of PolicyInformation to a Vector 
	 * of CertificatePolicyIds for easier comparison.
	 */
	Vector policyOIDs = new Vector(policies.size());
	Enumeration e = policies.elements();
	while (e.hasMoreElements()) {
	  policyOIDs.addElement(
		((PolicyInformation) e.nextElement()).getPolicyIdentifier());
	}
	if (policy != null) {
	  boolean foundOne = false;
	  /*
	   * if the user passes in an empty policy Set, then
	   * we just want to make sure that the candidate certificate
	   * has some policy OID in its CertPoliciesExtension
	   */
	  if (policy.getCertPolicyIds().isEmpty()) {
	      if (policyOIDs.isEmpty()) {
		  if (debug != null)
	  	      debug.println("X509CertSelector.match: cert failed "
			  + "policyAny criterion");
		  return false;
	      }
	  } else {
	      for (Iterator e2 = policy.getCertPolicyIds().iterator();
		   e2.hasNext() && !foundOne;)
		  if (policyOIDs.contains(e2.next()))
		      foundOne = true;
	      if (!foundOne) {
		  if (debug != null)
		      debug.println("X509CertSelector.match: cert failed "
			  + "policyAny criterion");
		  return false;
	      }
	  }
	}
      } catch (IOException ex) {
	if (debug != null)
	  debug.println("X509CertSelector.match: IOException in certificate "
	      + "policy ID check");
	return false;
      }
    }

    /* match on pathToNames */
    try {
      NameConstraintsExtension ext = 
	  (NameConstraintsExtension) getExtensionObject(xcert, 
					 NAME_CONSTRAINTS_ID);
      if ((pathToNames != null) && (ext != null)) {
	if (debug.isOn("certpath")) {
	  if (debug != null)
	  	debug.println("X509CertSelector.match pathToNames:\n");
	  Iterator i = pathToGeneralNames.iterator();
	  while (i.hasNext())
	    if (debug != null)
	    	debug.println("    " + i.next() + "\n");
	}

	GeneralSubtrees permitted = (GeneralSubtrees) 
	  ext.get(NameConstraintsExtension.PERMITTED_SUBTREES);
	GeneralSubtrees excluded = (GeneralSubtrees) 
	  ext.get(NameConstraintsExtension.EXCLUDED_SUBTREES);
	if (excluded != null) {
	  /* 
	   * Enumerate through excluded and compare each entry
	   * to all pathToNames. If any pathToName is within any of the
	   * subtrees listed in excluded, return false.
	   */
	  Enumeration trees = excluded.elements();
	  while (trees.hasMoreElements()) {
	    GeneralSubtree tree = (GeneralSubtree) trees.nextElement();
	    GeneralNameInterface excludedName = tree.getName().getName();
	    Iterator i = pathToGeneralNames.iterator();
	    while (i.hasNext()) {
	      GeneralNameInterface pathToName = (GeneralNameInterface) i.next();
	      if (excludedName.getType() == pathToName.getType()) {
		switch (pathToName.constrains(excludedName)) {
		case GeneralNameInterface.NAME_WIDENS:
		case GeneralNameInterface.NAME_MATCH:
		  if (debug != null)
	  	      debug.println("X509CertSelector.match: name constraints "
			  + "inhibit path to specified name");
		  if (debug != null)
		      debug.println("X509CertSelector.match: excluded name: " +
			  pathToName);
		  return false;
		default:
		}
	      }
	    }
	  }
	}
	if (permitted != null) {
	  /*
	   * Enumerate through pathToNames, checking that each pathToName
	   * is in at least one of the subtrees listed in permitted.
	   * If not, return false. However, if no subtrees of a given type
	   * are listed, all names of that type are permitted.
	   */
	  Iterator i = pathToGeneralNames.iterator();
	  while (i.hasNext()) {
	    GeneralNameInterface pathToName = (GeneralNameInterface) i.next();
	    Enumeration trees = permitted.elements();
	    boolean permittedNameFound = false;
	    boolean nameTypeFound = false;
	    String names = "";
	    while (trees.hasMoreElements() && !permittedNameFound) {
	      GeneralSubtree tree = (GeneralSubtree) trees.nextElement();
	      GeneralNameInterface permittedName = tree.getName().getName();
	      if (permittedName.getType() == pathToName.getType()) {
		nameTypeFound = true;
		names = names + "  " + permittedName;
		switch (pathToName.constrains(permittedName)) {
		case GeneralNameInterface.NAME_WIDENS:
		case GeneralNameInterface.NAME_MATCH:
		  permittedNameFound=true;
		  break;
		default:
		}
	      }
	    }
	    if (!permittedNameFound && nameTypeFound) {
	      if (debug != null)
	      	debug.println("X509CertSelector.match: " + 
			  "name constraints inhibit path to specified name; " +
			  "permitted names of type " + pathToName.getType() +
			  ": " + names);
	      return false;
	    }
	  }
	}
      }
    } catch (IOException ex) {
      if (debug != null)
	debug.println("X509CertSelector.match: IOException in name "
	    + "constraints check");
      return false;
    }

    /* match on basic constraints */
    if (basicConstraints != -1) {
      int maxPathLen = xcert.getBasicConstraints();
      if (basicConstraints == -2) {
	if (maxPathLen != -1) {
	  if (debug != null)
	  	debug.println("X509CertSelector.match: not an EE cert");
	  return false;
	}
      } else {
	if (maxPathLen < basicConstraints) {
	  if (debug != null)
  	      debug.println("X509CertSelector.match: maxPathLen too small (" 
		  + maxPathLen + " < " + basicConstraints + ")"); 
	  return false;
	}
      }
    }

    if (debug != null)
    	debug.println("X509CertSelector.match returning: true");
    return true;
  }

  /**
   * Returns a copy of this object.
   *
   * @return the copy
   */
  public Object clone() {
    try {
      Object copy = super.clone();
      // Must clone these because addPathToName et al. modify them
      if (subjectAlternativeNames != null) {
	subjectAlternativeNames = new HashSet(subjectAlternativeNames);
	subjectAlternativeGeneralNames =
	  new HashSet(subjectAlternativeGeneralNames);
      }
      if (pathToNames != null) {
	pathToNames = new HashSet(pathToNames);
	pathToGeneralNames = new HashSet(pathToGeneralNames);
      }
      return(copy);
    } catch (CloneNotSupportedException e) {
      /* Cannot happen */
      throw new InternalError(e.toString());
    }
  }
}
