/* * @(#)X509CertSelector.java 1.20 04/06/08 * * Copyright 2004 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.*; import javax.security.auth.x500.X500Principal; import sun.misc.HexDumpEncoder; import sun.security.util.Debug; import sun.security.util.DerInputStream; import sun.security.util.DerValue; import sun.security.util.ObjectIdentifier; import sun.security.x509.*; /** * A CertSelector that selects X509Certificates that * match all specified criteria. This class is particularly useful when * selecting certificates from a CertStore to build a * PKIX-compliant certification path. *

* When first constructed, an X509CertSelector has no criteria * enabled and each of the get methods return a default value * (null, or -1 for the {@link #getBasicConstraints * getBasicConstraints} method). Therefore, the {@link #match match} * method would return true for any X509Certificate. * Typically, several criteria are enabled (by calling * {@link #setIssuer setIssuer} or * {@link #setKeyUsage setKeyUsage}, for instance) and then the * X509CertSelector is passed to * {@link CertStore#getCertificates CertStore.getCertificates} or some similar * method. *

* Several criteria can be enabled (by calling {@link #setIssuer setIssuer} * and {@link #setSerialNumber setSerialNumber}, * for example) such that the match method * usually uniquely matches a single X509Certificate. 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. *

* Please refer to RFC 2459 for definitions of the X.509 certificate * extensions mentioned below. *

* Concurrent Access *

* 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.20, 06/08/04 * @since 1.4 * @author Steve Hanna */ public class X509CertSelector implements CertSelector { private static final Debug debug = Debug.getInstance("certpath"); private final static ObjectIdentifier ANY_EXTENDED_KEY_USAGE = ObjectIdentifier.newInternal(new int[] {2, 5, 29, 37, 0}); static { CertPathHelperImpl.initialize(); } private BigInteger serialNumber; private X500Principal issuer; private X500Principal 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 Set keyPurposeOIDSet; private Set> subjectAlternativeNames; private Set subjectAlternativeGeneralNames; private CertificatePolicySet policy; private Set policySet; private Set> pathToNames; private Set pathToGeneralNames; private NameConstraintsExtension nc; private byte[] ncBytes; private int basicConstraints = -1; private X509Certificate x509Cert; private boolean matchAllSubjectAltNames = true; private static final Boolean FALSE = 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 X509CertSelector. Initially, no criteria are set * so any X509Certificate will match. */ public X509CertSelector() { // empty } /** * Sets the certificateEquals criterion. The specified * X509Certificate must be equal to the * X509Certificate passed to the match method. * If null, then this check is not applied. * *

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 * null) * @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 * X509Certificate. If null, any certificate * serial number will do. * * @param serial the certificate serial number to match * (or null) * @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 * X509Certificate. If null, any issuer * distinguished name will do. * * @param issuer a distinguished name as X500Principal * (or null) * @since 1.5 */ public void setIssuer(X500Principal issuer) { this.issuer = issuer; } /** * Denigrated, use {@linkplain #setIssuer(X500Principal)} * or {@linkplain #setIssuer(byte[])} instead. This method should not be * relied on as it can fail to match some certificates because of a loss of * encoding information in the RFC 2253 String form of some distinguished * names. *

* Sets the issuer criterion. The specified distinguished name * must match the issuer distinguished name in the * X509Certificate. If null, any issuer * distinguished name will do. *

* If issuerDN is not null, it should contain a * distinguished name, in RFC 2253 format. * * @param issuerDN a distinguished name in RFC 2253 format * (or null) * @throws IOException if a parsing error occurs (incorrect form for DN) */ public void setIssuer(String issuerDN) throws IOException { if (issuerDN == null) { issuer = null; } else { issuer = new X500Name(issuerDN).asX500Principal(); } } /** * Sets the issuer criterion. The specified distinguished name * must match the issuer distinguished name in the * X509Certificate. If null is specified, * the issuer criterion is disabled and any issuer distinguished name will * do. *

* If issuerDN is not null, it should contain a * single DER encoded distinguished name, as defined in X.501. The ASN.1 * notation for this structure is as follows. *


     * 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)) }
     * 
*

* 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 null) * @throws IOException if an encoding error occurs (incorrect form for DN) */ public void setIssuer(byte[] issuerDN) throws IOException { try { issuer = (issuerDN == null ? null : new X500Principal(issuerDN)); } catch (IllegalArgumentException e) { throw (IOException)new IOException("Invalid name").initCause(e); } } /** * Sets the subject criterion. The specified distinguished name * must match the subject distinguished name in the * X509Certificate. If null, any subject * distinguished name will do. * * @param subject a distinguished name as X500Principal * (or null) * @since 1.5 */ public void setSubject(X500Principal subject) { this.subject = subject; } /** * Denigrated, use {@linkplain #setSubject(X500Principal)} * or {@linkplain #setSubject(byte[])} instead. This method should not be * relied on as it can fail to match some certificates because of a loss of * encoding information in the RFC 2253 String form of some distinguished * names. *

* Sets the subject criterion. The specified distinguished name * must match the subject distinguished name in the * X509Certificate. If null, any subject * distinguished name will do. *

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

* If subjectDN is not null, 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 null) * @throws IOException if an encoding error occurs (incorrect form for DN) */ public void setSubject(byte[] subjectDN) throws IOException { try { subject = (subjectDN == null ? null : new X500Principal(subjectDN)); } catch (IllegalArgumentException e) { throw (IOException)new IOException("Invalid name").initCause(e); } } /** * Sets the subjectKeyIdentifier criterion. The * X509Certificate 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. *

* If subjectKeyID is not null, 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. *

*


     * SubjectKeyIdentifier ::= KeyIdentifier
     *
     * KeyIdentifier ::= OCTET STRING
     * 
*

* Since the format of subject key identifiers is not mandated by * any standard, subject key identifiers are not parsed by the * X509CertSelector. Instead, the values are compared using * a byte-by-byte comparison. *

* Note that the byte array supplied here is cloned to protect against * subsequent modifications. * * @param subjectKeyID the subject key identifier (or null) * @see #getSubjectKeyIdentifier */ public void setSubjectKeyIdentifier(byte[] subjectKeyID) { if (subjectKeyID == null) { this.subjectKeyID = null; } else { this.subjectKeyID = (byte[])subjectKeyID.clone(); } } /** * Sets the authorityKeyIdentifier criterion. The * X509Certificate must contain an * AuthorityKeyIdentifier extension for which the contents of the * extension value matches the specified criterion value. * If the criterion value is null, no * authorityKeyIdentifier check will be done. *

* If authorityKeyID is not null, 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. *

*


     * AuthorityKeyIdentifier ::= SEQUENCE {
     *    keyIdentifier             [0] KeyIdentifier           OPTIONAL,
     *    authorityCertIssuer       [1] GeneralNames            OPTIONAL,
     *    authorityCertSerialNumber [2] CertificateSerialNumber OPTIONAL  }
     *
     * KeyIdentifier ::= OCTET STRING
     * 
*

* Authority key identifiers are not parsed by the * X509CertSelector. Instead, the values are * compared using a byte-by-byte comparison. *

* When the keyIdentifier field of * AuthorityKeyIdentifier 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[] subjectKeyID)}. *

* Note also that the byte array supplied here is cloned to protect against * subsequent modifications. * * @param authorityKeyID the authority key identifier * (or null) * @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 null, no certificateValid * check will be done. *

* Note that the Date supplied here is cloned to protect * against subsequent modifications. * * @param certValid the Date to check (or null) * @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 * X509Certificate. If null, no privateKeyValid * check will be done. *

* Note that the Date supplied here is cloned to protect * against subsequent modifications. * * @param privateKeyValid the Date to check (or * null) * @see #getPrivateKeyValid */ public void setPrivateKeyValid(Date privateKeyValid) { if (privateKeyValid == null) { this.privateKeyValid = null; } else { this.privateKeyValid = (Date)privateKeyValid.clone(); } } /** * Sets the subjectPublicKeyAlgID criterion. The * X509Certificate must contain a subject public key * with the specified algorithm. If null, no * subjectPublicKeyAlgID check will be done. * * @param oid The object identifier (OID) of the algorithm to check * for (or null). 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 * 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; subjectPublicKeyBytes = null; } else { subjectPublicKey = key; subjectPublicKeyBytes = key.getEncoded(); } } /** * Sets the subjectPublicKey criterion. The X509Certificate * must contain the specified subject public key. If null, * no subjectPublicKey check will be done. *

* Because this method allows the public key to be specified as a byte * array, it may be used for unknown key types. *

* If key is not null, it should contain a * single DER encoded SubjectPublicKeyInfo structure, as defined in X.509. * The ASN.1 notation for this structure is as follows. *


     * 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
     * 
*

* 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 null) * @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 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. *

* 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 null. * @see #getKeyUsage */ public void setKeyUsage(boolean[] keyUsage) { if (keyUsage == null) { this.keyUsage = null; } else { this.keyUsage = (boolean[])keyUsage.clone(); } } /** * Sets the extendedKeyUsage criterion. The X509Certificate * must allow the specified key purposes in its extended key usage * extension. If keyPurposeSet is empty or null, * no extendedKeyUsage check will be done. Note that an * X509Certificate that has no extendedKeyUsage extension * implicitly allows all key purposes. *

* Note that the Set is cloned to protect against * subsequent modifications. * * @param keyPurposeSet a Set of key purpose OIDs in string * format (or null). 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.isEmpty()) { this.keyPurposeSet = null; keyPurposeOIDSet = null; } else { this.keyPurposeSet = Collections.unmodifiableSet(new HashSet(keyPurposeSet)); keyPurposeOIDSet = new HashSet(); for (String s : this.keyPurposeSet) { keyPurposeOIDSet.add(new ObjectIdentifier(s)); } } } /** * Enables/disables matching all of the subjectAlternativeNames * specified in the {@link #setSubjectAlternativeNames * setSubjectAlternativeNames} or {@link #addSubjectAlternativeName * addSubjectAlternativeName} methods. If enabled, * the X509Certificate must contain all of the * specified subject alternative names. If disabled, the * X509Certificate must contain at least one of the * specified subject alternative names. * *

The matchAllNames flag is true by default. * * @param matchAllNames if true, the flag is enabled; * if false, the flag is disabled. * @see #getMatchAllSubjectAltNames */ public void setMatchAllSubjectAltNames(boolean matchAllNames) { this.matchAllSubjectAltNames = matchAllNames; } /** * Sets the subjectAlternativeNames criterion. The * X509Certificate must contain all or at least one of the * specified subjectAlternativeNames, depending on the value of * the matchAllNames flag (see {@link #setMatchAllSubjectAltNames * setMatchAllSubjectAltNames}). *

* 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. *

* The names parameter (if not null) is a * Collection with one * entry for each name to be included in the subject alternative name * criterion. Each entry is a List 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. If null * is supplied as the value for this argument, no * subjectAlternativeNames check will be performed. *

* Each subject alternative 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 #addSubjectAlternativeName(int type, String name) * addSubjectAlternativeName(int type, String name)} and * {@link #addSubjectAlternativeName(int type, byte [] name) * addSubjectAlternativeName(int type, byte [] name)}. *

* Note: for distinguished names, specify the byte * array form instead of the String form. See the note in * {@link #addSubjectAlternativeName(int, String)} for more information. *

* 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 #getSubjectAlternativeNames getSubjectAlternativeNames} method. *

* Note that a deep copy is performed on the Collection to * protect against subsequent modifications. * * @param names a Collection of names (or null) * @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.isEmpty()) { subjectAlternativeNames = null; subjectAlternativeGeneralNames = null; return; } Set> 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 * X509Certificate must contain all or at least one * of the specified subjectAlternativeNames, depending on the value of * the matchAllNames flag (see {@link #setMatchAllSubjectAltNames * setMatchAllSubjectAltNames}). *

* 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. *

* 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. *

* Note: for distinguished names, use * {@linkplain #addSubjectAlternativeName(int, byte[])} instead. * This method should not be relied on as it can fail to match some * certificates because of a loss of encoding information in the RFC 2253 * String form of some distinguished names. * * @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) * @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 * X509Certificate must contain all or at least one * of the specified subjectAlternativeNames, depending on the value of * the matchAllNames flag (see {@link #setMatchAllSubjectAltNames * setMatchAllSubjectAltNames}). *

* 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. *

* 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. *


     *  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}
     * 
*

* 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 X509Certificate * 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(); } List list = new ArrayList(2); list.add(Integer.valueOf(type)); list.add(name); subjectAlternativeNames.add(list); subjectAlternativeGeneralNames.add(tempName); } /** * Parse an argument of the form passed to setSubjectAlternativeNames, * returning a Collection of * GeneralNameInterfaces. * Throw an IllegalArgumentException or a ClassCastException * if the argument is malformed. * * @param names a Collection with one entry per name. * Each entry is a List 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 Set of GeneralNameInterfaces * @throws IOException if a parsing error occurs */ private static Set parseNames(Collection> names) throws IOException { Set 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 IllegalArgumentException or a * ClassCastException 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 object1 == object2; } return object1.equals(object2); } /** * Make a GeneralNameInterface 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: " + 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); 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 X509Certificate * must have subject and subject alternative names that * meet the specified name constraints. *

* 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. * *


     *  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}
     * 
*

* 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 * null, * 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(FALSE, bytes); } } /** * Sets the basic constraints constraint. If the value is greater than or * equal to zero, X509Certificates 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. *

* 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 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. *

* Note that the Set is cloned to protect against * subsequent modifications. * * @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. * @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 X509Certificate must * not include name constraints that would prohibit building a * path to the specified names. *

* This method allows the caller to specify, with a single method call, * the complete set of names which the X509Certificates's * name constraints must permit. The specified value replaces * the previous value for the pathToNames criterion. *

* 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. *

* The names parameter (if not null) is a * Collection with one * entry for each name to be included in the pathToNames * criterion. Each entry is a List 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. If null * is supplied as the value for this argument, no * pathToNames check will be performed. *

* 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 type, String name) * addPathToName(int type, String name)} and * {@link #addPathToName(int type, byte [] name) * addPathToName(int type, byte [] name)}. *

* Note: for distinguished names, specify the byte * array form instead of the String form. See the note in * {@link #addPathToName(int, String)} for more information. *

* 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 getPathToNames} method. *

* Note that a deep copy is performed on the Collection to * protect against subsequent modifications. * * @param names a Collection with one entry per name * (or null) * @throws IOException if a parsing error occurs * @see #getPathToNames */ public void setPathToNames(Collection> names) throws IOException { if ((names == null) || names.isEmpty()) { pathToNames = null; pathToGeneralNames = null; } else { Set> tempNames = cloneAndCheckNames(names); pathToGeneralNames = parseNames(tempNames); // Ensure that we either set both of these or neither pathToNames = tempNames; } } // called from CertPathHelper void setPathToNamesInternal(Set names) { // set names to non-null dummy value // this breaks getPathToNames() pathToNames = Collections.>emptySet(); pathToGeneralNames = names; } /** * Adds a name to the pathToNames criterion. The X509Certificate * must not include name constraints that would prohibit building a * path to the specified name. *

* This method allows the caller to add a name to the set of names which * the X509Certificates'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. *

* 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. *

* Note: for distinguished names, use * {@linkplain #addPathToName(int, byte[])} instead. * This method should not be relied on as it can fail to match some * certificates because of a loss of encoding information in the RFC 2253 * String form of some distinguished names. * * @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 X509Certificate * must not include name constraints that would prohibit building a * path to the specified name. *

* This method allows the caller to add a name to the set of names which * the X509Certificates'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. *

* 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)}. *

* 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 X509Certificate 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 (pathToGeneralNames == null) { pathToNames = new HashSet>(); pathToGeneralNames = new HashSet(); } List list = new ArrayList(2); list.add(Integer.valueOf(type)); list.add(name); pathToNames.add(list); pathToGeneralNames.add(tempName); } /** * Returns the certificateEquals criterion. The specified * X509Certificate must be equal to the * X509Certificate passed to the match method. * If null, this check is not applied. * * @return the X509Certificate to match (or null) * @see #setCertificate */ public X509Certificate getCertificate() { return x509Cert; } /** * Returns the serialNumber criterion. The specified serial number * must match the certificate serial number in the * X509Certificate. If null, any certificate * serial number will do. * * @return the certificate serial number to match * (or null) * @see #setSerialNumber */ public BigInteger getSerialNumber() { return serialNumber; } /** * Returns the issuer criterion as an X500Principal. This * distinguished name must match the issuer distinguished name in the * X509Certificate. If null, the issuer criterion * is disabled and any issuer distinguished name will do. * * @return the required issuer distinguished name as X500Principal * (or null) * @since 1.5 */ public X500Principal getIssuer() { return issuer; } /** * Denigrated, use {@linkplain #getIssuer()} or * {@linkplain #getIssuerAsBytes()} instead. This method should not be * relied on as it can fail to match some certificates because of a loss of * encoding information in the RFC 2253 String form of some distinguished * names. *

* Returns the issuer criterion as a String. This * distinguished name must match the issuer distinguished name in the * X509Certificate. If null, the issuer criterion * is disabled and any issuer distinguished name will do. *

* If the value returned is not null, it is a * distinguished name, in RFC 2253 format. * * @return the required issuer distinguished name in RFC 2253 format * (or null) */ public String getIssuerAsString() { return (issuer == null ? null : issuer.getName()); } /** * Returns the issuer criterion as a byte array. This distinguished name * must match the issuer distinguished name in the * X509Certificate. If null, the issuer criterion * is disabled and any issuer distinguished name will do. *

* If the value returned is not null, 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)}. *

* 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 null) * @throws IOException if an encoding error occurs */ public byte[] getIssuerAsBytes() throws IOException { return (issuer == null ? null: issuer.getEncoded()); } /** * Returns the subject criterion as an X500Principal. This * distinguished name must match the subject distinguished name in the * X509Certificate. If null, the subject criterion * is disabled and any subject distinguished name will do. * * @return the required subject distinguished name as X500Principal * (or null) * @since 1.5 */ public X500Principal getSubject() { return subject; } /** * Denigrated, use {@linkplain #getSubject()} or * {@linkplain #getSubjectAsBytes()} instead. This method should not be * relied on as it can fail to match some certificates because of a loss of * encoding information in the RFC 2253 String form of some distinguished * names. *

* Returns the subject criterion as a String. This * distinguished name must match the subject distinguished name in the * X509Certificate. If null, the subject criterion * is disabled and any subject distinguished name will do. *

* If the value returned is not null, it is a * distinguished name, in RFC 2253 format. * * @return the required subject distinguished name in RFC 2253 format * (or null) */ public String getSubjectAsString() { return (subject == null ? null : subject.getName()); } /** * Returns the subject criterion as a byte array. This distinguished name * must match the subject distinguished name in the * X509Certificate. If null, the subject criterion * is disabled and any subject distinguished name will do. *

* If the value returned is not null, 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)}. *

* 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 null) * @throws IOException if an encoding error occurs */ public byte[] getSubjectAsBytes() throws IOException { return (subject == null ? null : subject.getEncoded()); } /** * Returns the subjectKeyIdentifier criterion. The * X509Certificate must contain a SubjectKeyIdentifier * extension with the specified value. If null, no * subjectKeyIdentifier check will be done. *

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

* Note that the byte array returned is cloned to protect against * subsequent modifications. * * @return the key identifier (or null) * @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 * X509Certificate. If null, no certificateValid * check will be done. *

* Note that the Date returned is cloned to protect against * subsequent modifications. * * @return the Date to check (or null) * @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 * X509Certificate. If null, no privateKeyValid * check will be done. *

* Note that the Date returned is cloned to protect against * subsequent modifications. * * @return the Date to check (or null) * @see #setPrivateKeyValid */ public Date getPrivateKeyValid() { if (privateKeyValid == null) { return null; } return (Date)privateKeyValid.clone(); } /** * Returns the subjectPublicKeyAlgID criterion. The * X509Certificate must contain a subject public key * with the specified algorithm. If null, no * subjectPublicKeyAlgID check will be done. * * @return the object identifier (OID) of the signature algorithm to check * for (or null). 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 * X509Certificate must contain the specified subject * public key. If null, no subjectPublicKey check will be done. * * @return the subject public key to check for (or null) * @see #setSubjectPublicKey */ public PublicKey getSubjectPublicKey() { return subjectPublicKey; } /** * Returns the keyUsage criterion. The X509Certificate * must allow the specified keyUsage values. If null, no keyUsage * check will be done. *

* 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 null. * @see #setKeyUsage */ public boolean[] getKeyUsage() { if (keyUsage == null) { return null; } return (boolean[])keyUsage.clone(); } /** * Returns the extendedKeyUsage criterion. The X509Certificate * must allow the specified key purposes in its extended key usage * extension. If the keyPurposeSet returned is empty or * null, no extendedKeyUsage check will be done. Note that an * X509Certificate that has no extendedKeyUsage extension * implicitly allows all key purposes. * * @return an immutable Set of key purpose OIDs in string * format (or null) * @see #setExtendedKeyUsage */ public Set getExtendedKeyUsage() { return keyPurposeSet; } /** * Indicates if the X509Certificate must contain all * or at least one of the subjectAlternativeNames * specified in the {@link #setSubjectAlternativeNames * setSubjectAlternativeNames} or {@link #addSubjectAlternativeName * addSubjectAlternativeName} methods. If true, * the X509Certificate must contain all of the * specified subject alternative names. If false, the * X509Certificate must contain at least one of the * specified subject alternative names. * * @return true if the flag is enabled; * false if the flag is disabled. The flag is * true by default. * @see #setMatchAllSubjectAltNames */ public boolean getMatchAllSubjectAltNames() { return matchAllSubjectAltNames; } /** * Returns a copy of the subjectAlternativeNames criterion. * The X509Certificate 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 * null, no subjectAlternativeNames check will be performed. *

* If the value returned is not null, it is a * Collection with * one entry for each name to be included in the subject alternative name * criterion. Each entry is a List 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. Note that the * Collection returned may contain duplicate names (same name * and name type). *

* Each subject alternative 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 #addSubjectAlternativeName(int type, String name) * addSubjectAlternativeName(int type, String name)} and * {@link #addSubjectAlternativeName(int type, byte [] name) * addSubjectAlternativeName(int type, byte [] name)}. *

* Note that a deep copy is performed on the Collection to * protect against subsequent modifications. * * @return a Collection of names (or null) * @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 RuntimeException if the argument is malformed. *

* This method wraps cloneAndCheckNames, changing any * IOException into a RuntimeException. This * method should be used when the object being * cloned has already been checked, so there should never be any exceptions. * * @param names a Collection with one entry per name. * Each entry is a List 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 Collection * @throws RuntimeException if a parsing error occurs */ private static Set> 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 IOException if the argument is malformed. * * @param names a Collection with one entry per name. * Each entry is a List 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 Collection * @throws IOException if a parsing error occurs */ private static Set> cloneAndCheckNames(Collection> names) throws IOException { // Copy the Lists and Collection Set> 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 X509Certificate * must have subject and subject alternative names that * meet the specified name constraints. *

* 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)}. *

* 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. * null 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 X509Certificates 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 X509Certificate must * include at least one of the specified policies in its certificate policies * extension. If the Set returned is empty, then the * X509Certificate must include at least some specified policy * in its certificate policies extension. If the Set returned is * null, no policy check will be performed. * * @return an immutable Set of certificate policy OIDs in * string format (or null) * @see #setPolicy */ public Set getPolicy() { return policySet; } /** * Returns a copy of the pathToNames criterion. The * X509Certificate must not include name constraints that would * prohibit building a path to the specified names. If the value * returned is null, no pathToNames check will be performed. *

* If the value returned is not null, it is a * Collection with one * entry for each name to be included in the pathToNames * criterion. Each entry is a List 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. Note that the * Collection returned may contain duplicate names (same * name and name type). *

* 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 type, String name) * addPathToName(int type, String name)} and * {@link #addPathToName(int type, byte [] name) * addPathToName(int type, byte [] name)}. *

* Note that a deep copy is performed on the Collection to * protect against subsequent modifications. * * @return a Collection of names (or null) * @see #setPathToNames */ public Collection> getPathToNames() { if (pathToNames == null) { return null; } return cloneNames(pathToNames); } /** * Return a printable representation of the CertSelector. * * @return a String describing the contents of the * CertSelector */ 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 (pathToGeneralNames != 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 IOException if the extension byte value is * malformed. * * @param cert a X509Certificate * @param extId an integer 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 Extension object whose real type is as specified * by the extension oid. * @throws IOException if cannot construct the Extension * object with the extension encoding retrieved from the passed in * X509Certificate. */ private static Extension getExtensionObject(X509Certificate cert, int extId) throws IOException { if (cert instanceof X509CertImpl) { X509CertImpl impl = (X509CertImpl)cert; switch (extId) { case PRIVATE_KEY_USAGE_ID: return impl.getPrivateKeyUsageExtension(); case SUBJECT_ALT_NAME_ID: return impl.getSubjectAlternativeNameExtension(); case NAME_CONSTRAINTS_ID: return impl.getNameConstraintsExtension(); case CERT_POLICIES_ID: return impl.getCertificatePoliciesExtension(); case EXTENDED_KEY_USAGE_ID: return impl.getExtendedKeyUsageExtension(); default: return null; } } byte[] rawExtVal = cert.getExtensionValue(EXTENSION_OIDS[extId]); if (rawExtVal == null) { return null; } DerInputStream in = new DerInputStream(rawExtVal); byte[] encoded = in.getOctetString(); switch (extId) { case PRIVATE_KEY_USAGE_ID: try { return new PrivateKeyUsageExtension(FALSE, encoded); } catch (CertificateException ex) { throw new IOException(ex.getMessage()); } case SUBJECT_ALT_NAME_ID: return new SubjectAlternativeNameExtension(FALSE, encoded); case NAME_CONSTRAINTS_ID: return new NameConstraintsExtension(FALSE, encoded); case CERT_POLICIES_ID: return new CertificatePoliciesExtension(FALSE, encoded); case EXTENDED_KEY_USAGE_ID: return new ExtendedKeyUsageExtension(FALSE, encoded); default: return null; } } /** * Decides whether a Certificate should be selected. * * @param cert the Certificate to be checked * @return true if the Certificate should be * selected, false 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 certificate validity range */ if (certificateValid != null) { try { xcert.checkValidity(certificateValid); } catch (CertificateException e) { if (debug != null) { debug.println("X509CertSelector.match: " + "certificate not within validity period"); } 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; } } boolean result = matchBasicConstraints(xcert) && matchKeyUsage(xcert) && matchExtendedKeyUsage(xcert) && matchSubjectKeyID(xcert) && matchAuthorityKeyID(xcert) && matchPrivateKeyValid(xcert) && matchSubjectPublicKeyAlgID(xcert) && matchPolicy(xcert) && matchSubjectAlternativeNames(xcert) && matchPathToNames(xcert) && matchNameConstraints(xcert); if (result && (debug != null)) { debug.println("X509CertSelector.match returning: true"); } return result; } /* match on subject key identifier extension value */ private boolean matchSubjectKeyID(X509Certificate xcert) { if (subjectKeyID == null) { return true; } 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; } return true; } /* match on authority key identifier extension value */ private boolean matchAuthorityKeyID(X509Certificate xcert) { if (authorityKeyID == null) { return true; } 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; } return true; } /* match on private key usage range */ private boolean matchPrivateKeyValid(X509Certificate xcert) { if (privateKeyValid == null) { return true; } 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; } return true; } /* match on subject public key algorithm OID */ private boolean matchSubjectPublicKeyAlgID(X509Certificate xcert) { if (subjectPublicKeyAlgID == null) { return true; } 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; } return true; } /* match on key usage extension value */ private boolean matchKeyUsage(X509Certificate xcert) { if (keyUsage == null) { return true; } 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; } } } return true; } /* match on extended key usage purpose OIDs */ private boolean matchExtendedKeyUsage(X509Certificate xcert) { if ((keyPurposeSet == null) || keyPurposeSet.isEmpty()) { return true; } try { ExtendedKeyUsageExtension ext = (ExtendedKeyUsageExtension)getExtensionObject(xcert, EXTENDED_KEY_USAGE_ID); if (ext != null) { Vector certKeyPurposeVector = (Vector)ext.get(ExtendedKeyUsageExtension.USAGES); if (!certKeyPurposeVector.contains(ANY_EXTENDED_KEY_USAGE) && !certKeyPurposeVector.containsAll(keyPurposeOIDSet)) { 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; } return true; } /* match on subject alternative name extension names */ private boolean matchSubjectAlternativeNames(X509Certificate xcert) { if ((subjectAlternativeNames == null) || subjectAlternativeNames.isEmpty()) { return true; } 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; } GeneralNames certNames = (GeneralNames) sanExt.get(SubjectAlternativeNameExtension.SUBJECT_NAME); Iterator i = subjectAlternativeGeneralNames.iterator(); while (i.hasNext()) { GeneralNameInterface matchName = (GeneralNameInterface) i.next(); boolean found = false; for (Iterator t = certNames.iterator(); t.hasNext() && !found; ) { GeneralNameInterface certName = ((GeneralName)t.next()).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; } return true; } /* match on name constraints */ private boolean matchNameConstraints(X509Certificate xcert) { if (nc == null) { return true; } 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; } return true; } /* match on policy OIDs */ private boolean matchPolicy(X509Certificate xcert) { if (policy == null) { return true; } 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; } List policies = (List)ext.get(CertificatePoliciesExtension.POLICIES); /* * Convert the Vector of PolicyInformation to a Vector * of CertificatePolicyIds for easier comparison. */ List policyIDs = new ArrayList(policies.size()); for (PolicyInformation info : policies) { policyIDs.add(info.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 (policyIDs.isEmpty()) { if (debug != null) { debug.println("X509CertSelector.match: " + "cert failed policyAny criterion"); } return false; } } else { for (CertificatePolicyId id : policy.getCertPolicyIds()) { if (policyIDs.contains(id)) { foundOne = true; break; } } 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; } return true; } /* match on pathToNames */ private boolean matchPathToNames(X509Certificate xcert) { if (pathToGeneralNames == null) { return true; } try { NameConstraintsExtension ext = (NameConstraintsExtension) getExtensionObject(xcert, NAME_CONSTRAINTS_ID); if (ext == null) { return true; } if ((debug != null) && debug.isOn("certpath")) { debug.println("X509CertSelector.match pathToNames:\n"); Iterator i = pathToGeneralNames.iterator(); while (i.hasNext()) { debug.println(" " + i.next() + "\n"); } } GeneralSubtrees permitted = (GeneralSubtrees) ext.get(NameConstraintsExtension.PERMITTED_SUBTREES); GeneralSubtrees excluded = (GeneralSubtrees) ext.get(NameConstraintsExtension.EXCLUDED_SUBTREES); if (excluded != null) { if (matchExcluded(excluded) == false) { return false; } } if (permitted != null) { if (matchPermitted(permitted) == false) { return false; } } } catch (IOException ex) { if (debug != null) { debug.println("X509CertSelector.match: " + "IOException in name constraints check"); } return false; } return true; } private boolean matchExcluded(GeneralSubtrees excluded) { /* * Enumerate through excluded and compare each entry * to all pathToNames. If any pathToName is within any of the * subtrees listed in excluded, return false. */ for (Iterator t = excluded.iterator(); t.hasNext(); ) { GeneralSubtree tree = (GeneralSubtree)t.next(); 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"); debug.println("X509CertSelector.match: excluded name: " + pathToName); } return false; default: } } } } return true; } private boolean matchPermitted(GeneralSubtrees permitted) { /* * 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(); Iterator t = permitted.iterator(); boolean permittedNameFound = false; boolean nameTypeFound = false; String names = ""; while (t.hasNext() && !permittedNameFound) { GeneralSubtree tree = (GeneralSubtree)t.next(); 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; } } return true; } /* match on basic constraints */ private boolean matchBasicConstraints(X509Certificate xcert) { if (basicConstraints == -1) { return true; } 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; } } return true; } private static Set cloneSet(Set set) { if (set instanceof HashSet) { Object clone = ((HashSet)set).clone(); return (Set)clone; } else { return new HashSet(set); } } /** * Returns a copy of this object. * * @return the copy */ public Object clone() { try { X509CertSelector copy = (X509CertSelector)super.clone(); // Must clone these because addPathToName et al. modify them if (subjectAlternativeNames != null) { copy.subjectAlternativeNames = (Set>)cloneSet(subjectAlternativeNames); copy.subjectAlternativeGeneralNames = (Set)cloneSet (subjectAlternativeGeneralNames); } if (pathToGeneralNames != null) { copy.pathToNames = (Set>)cloneSet(pathToNames); copy.pathToGeneralNames = (Set)cloneSet (pathToGeneralNames); } return copy; } catch (CloneNotSupportedException e) { /* Cannot happen */ throw new InternalError(e.toString()); } } }