0001: /*
0002: * @(#)X500Name.java 1.54 06/10/10
0003: *
0004: * Copyright 1990-2006 Sun Microsystems, Inc. All Rights Reserved.
0005: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER
0006: *
0007: * This program is free software; you can redistribute it and/or
0008: * modify it under the terms of the GNU General Public License version
0009: * 2 only, as published by the Free Software Foundation.
0010: *
0011: * This program is distributed in the hope that it will be useful, but
0012: * WITHOUT ANY WARRANTY; without even the implied warranty of
0013: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
0014: * General Public License version 2 for more details (a copy is
0015: * included at /legal/license.txt).
0016: *
0017: * You should have received a copy of the GNU General Public License
0018: * version 2 along with this work; if not, write to the Free Software
0019: * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
0020: * 02110-1301 USA
0021: *
0022: * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa
0023: * Clara, CA 95054 or visit www.sun.com if you need additional
0024: * information or have any questions.
0025: *
0026: */
0027:
0028: package sun.security.x509;
0029:
0030: import java.lang.reflect.*;
0031: import java.io.IOException;
0032: import java.io.StringReader;
0033: import java.security.PrivilegedExceptionAction;
0034: import java.security.AccessController;
0035: import java.security.Principal;
0036: import java.util.*;
0037:
0038: import sun.security.util.*;
0039: import sun.security.pkcs.PKCS9Attribute;
0040: import javax.security.auth.x500.X500Principal;
0041:
0042: /**
0043: * Note: As of 1.4, the public class,
0044: * javax.security.auth.x500.X500Principal,
0045: * should be used when parsing, generating, and comparing X.500 DNs.
0046: * This class contains other useful methods for checking name constraints
0047: * and retrieving DNs by keyword.
0048: *
0049: * <p> X.500 names are used to identify entities, such as those which are
0050: * identified by X.509 certificates. They are world-wide, hierarchical,
0051: * and descriptive. Entities can be identified by attributes, and in
0052: * some systems can be searched for according to those attributes.
0053: * <p>
0054: * The ASN.1 for this is:
0055: * <pre>
0056: * GeneralName ::= CHOICE {
0057: * ....
0058: * directoryName [4] Name,
0059: * ....
0060: * Name ::= CHOICE {
0061: * RDNSequence }
0062: *
0063: * RDNSequence ::= SEQUENCE OF RelativeDistinguishedName
0064: *
0065: * RelativeDistinguishedName ::=
0066: * SET OF AttributeTypeAndValue
0067: *
0068: * AttributeTypeAndValue ::= SEQUENCE {
0069: * type AttributeType,
0070: * value AttributeValue }
0071: *
0072: * AttributeType ::= OBJECT IDENTIFIER
0073: *
0074: * AttributeValue ::= ANY DEFINED BY AttributeType
0075: * ....
0076: * DirectoryString ::= CHOICE {
0077: * teletexString TeletexString (SIZE (1..MAX)),
0078: * printableString PrintableString (SIZE (1..MAX)),
0079: * universalString UniversalString (SIZE (1..MAX)),
0080: * utf8String UTF8String (SIZE (1.. MAX)),
0081: * bmpString BMPString (SIZE (1..MAX)) }
0082: * </pre>
0083: * <p>
0084: * This specification requires only a subset of the name comparison
0085: * functionality specified in the X.500 series of specifications. The
0086: * requirements for conforming implementations are as follows:
0087: * <ol TYPE=a>
0088: * <li>attribute values encoded in different types (e.g.,
0089: * PrintableString and BMPString) may be assumed to represent
0090: * different strings;
0091: * <p>
0092: * <li>attribute values in types other than PrintableString are case
0093: * sensitive (this permits matching of attribute values as binary
0094: * objects);
0095: * <p>
0096: * <li>attribute values in PrintableString are not case sensitive
0097: * (e.g., "Marianne Swanson" is the same as "MARIANNE SWANSON"); and
0098: * <p>
0099: * <li>attribute values in PrintableString are compared after
0100: * removing leading and trailing white space and converting internal
0101: * substrings of one or more consecutive white space characters to a
0102: * single space.
0103: * </ol>
0104: * <p>
0105: * These name comparison rules permit a certificate user to validate
0106: * certificates issued using languages or encodings unfamiliar to the
0107: * certificate user.
0108: * <p>
0109: * In addition, implementations of this specification MAY use these
0110: * comparison rules to process unfamiliar attribute types for name
0111: * chaining. This allows implementations to process certificates with
0112: * unfamiliar attributes in the issuer name.
0113: * <p>
0114: * Note that the comparison rules defined in the X.500 series of
0115: * specifications indicate that the character sets used to encode data
0116: * in distinguished names are irrelevant. The characters themselves are
0117: * compared without regard to encoding. Implementations of the profile
0118: * are permitted to use the comparison algorithm defined in the X.500
0119: * series. Such an implementation will recognize a superset of name
0120: * matches recognized by the algorithm specified above.
0121: * <p>
0122: * Note that instances of this class are immutable.
0123: *
0124: * @author David Brownell
0125: * @author Amit Kapoor
0126: * @author Hemma Prafullchandra
0127: * @version 1.47
0128: * @see GeneralName
0129: * @see GeneralNames
0130: * @see GeneralNameInterface
0131: */
0132:
0133: public class X500Name implements GeneralNameInterface, Principal {
0134:
0135: private String dn; // roughly RFC 1779 DN, or null
0136: private String rfc1779Dn; // RFC 1779 compliant DN, or null
0137: private String rfc2253Dn; // RFC 2253 DN, or null
0138: private String canonicalDn; // canonical RFC 2253 DN or null
0139: private RDN[] names; // RDNs (never null)
0140: private X500Principal x500Principal;
0141: private byte[] encoded;
0142:
0143: // cached immutable list of the RDNs and all the AVAs
0144: private volatile List rdnList, allAvaList;
0145:
0146: /**
0147: * Constructs a name from a conventionally formatted string, such
0148: * as "CN=Dave, OU=JavaSoft, O=Sun Microsystems, C=US".
0149: * (RFC 1779 or RFC 2253 style).
0150: *
0151: * @param DN X.500 Distinguished Name
0152: */
0153: public X500Name(String dname) throws IOException {
0154: parseDN(dname);
0155: }
0156:
0157: /**
0158: * Constructs a name from a string formatted according to format.
0159: * Currently, the formats DEFAULT and RFC2253 are supported.
0160: * DEFAULT is the default format used by the X500Name(String)
0161: * constructor. RFC2253 is format strictly according to RFC2253
0162: * without extensions.
0163: *
0164: * @param DN X.500 Distinguished Name
0165: */
0166: public X500Name(String dname, String format) throws IOException {
0167: if (dname == null) {
0168: throw new NullPointerException("Name must not be null");
0169: }
0170: if (format.equalsIgnoreCase("RFC2253")) {
0171: parseRFC2253DN(dname);
0172: } else if (format.equalsIgnoreCase("DEFAULT")) {
0173: parseDN(dname);
0174: } else {
0175: throw new IOException("Unsupported format " + format);
0176: }
0177: }
0178:
0179: /**
0180: * Constructs a name from fields common in enterprise application
0181: * environments.
0182: *
0183: * <P><EM><STRONG>NOTE:</STRONG> The behaviour when any of
0184: * these strings contain characters outside the ASCII range
0185: * is unspecified in currently relevant standards.</EM>
0186: *
0187: * @param commonName common name of a person, e.g. "Vivette Davis"
0188: * @param organizationUnit small organization name, e.g. "Purchasing"
0189: * @param organizationName large organization name, e.g. "Onizuka, Inc."
0190: * @param country two letter country code, e.g. "CH"
0191: */
0192: public X500Name(String commonName, String organizationUnit,
0193: String organizationName, String country) throws IOException {
0194: names = new RDN[4];
0195: /*
0196: * NOTE: it's only on output that little-endian
0197: * ordering is used.
0198: */
0199: names[3] = new RDN(1);
0200: names[3].assertion[0] = new AVA(commonName_oid, new DerValue(
0201: commonName));
0202: names[2] = new RDN(1);
0203: names[2].assertion[0] = new AVA(orgUnitName_oid, new DerValue(
0204: organizationUnit));
0205: names[1] = new RDN(1);
0206: names[1].assertion[0] = new AVA(orgName_oid, new DerValue(
0207: organizationName));
0208: names[0] = new RDN(1);
0209: names[0].assertion[0] = new AVA(countryName_oid, new DerValue(
0210: country));
0211: }
0212:
0213: /**
0214: * Constructs a name from fields common in Internet application
0215: * environments.
0216: *
0217: * <P><EM><STRONG>NOTE:</STRONG> The behaviour when any of
0218: * these strings contain characters outside the ASCII range
0219: * is unspecified in currently relevant standards.</EM>
0220: *
0221: * @param commonName common name of a person, e.g. "Vivette Davis"
0222: * @param organizationUnit small organization name, e.g. "Purchasing"
0223: * @param organizationName large organization name, e.g. "Onizuka, Inc."
0224: * @param localityName locality (city) name, e.g. "Palo Alto"
0225: * @param stateName state name, e.g. "California"
0226: * @param country two letter country code, e.g. "CH"
0227: */
0228: public X500Name(String commonName, String organizationUnit,
0229: String organizationName, String localityName,
0230: String stateName, String country) throws IOException {
0231: names = new RDN[6];
0232: /*
0233: * NOTE: it's only on output that little-endian
0234: * ordering is used.
0235: */
0236: names[5] = new RDN(1);
0237: names[5].assertion[0] = new AVA(commonName_oid, new DerValue(
0238: commonName));
0239: names[4] = new RDN(1);
0240: names[4].assertion[0] = new AVA(orgUnitName_oid, new DerValue(
0241: organizationUnit));
0242: names[3] = new RDN(1);
0243: names[3].assertion[0] = new AVA(orgName_oid, new DerValue(
0244: organizationName));
0245: names[2] = new RDN(1);
0246: names[2].assertion[0] = new AVA(localityName_oid, new DerValue(
0247: localityName));
0248: names[1] = new RDN(1);
0249: names[1].assertion[0] = new AVA(stateName_oid, new DerValue(
0250: stateName));
0251: names[0] = new RDN(1);
0252: names[0].assertion[0] = new AVA(countryName_oid, new DerValue(
0253: country));
0254: }
0255:
0256: /**
0257: * Constructs a name from an array of relative distinguished names
0258: *
0259: * @param rdnArray array of relative distinguished names
0260: * @throws IOException on error
0261: */
0262: public X500Name(RDN[] rdnArray) throws IOException {
0263: if (rdnArray == null) {
0264: names = new RDN[0];
0265: } else {
0266: names = (RDN[]) rdnArray.clone();
0267: for (int i = 0; i < names.length; i++) {
0268: if (names[i] == null) {
0269: throw new IOException("Cannot create an X500Name");
0270: }
0271: }
0272: }
0273: }
0274:
0275: /**
0276: * Constructs a name from an ASN.1 encoded value. The encoding
0277: * of the name in the stream uses DER (a BER/1 subset).
0278: *
0279: * @param value a DER-encoded value holding an X.500 name.
0280: */
0281: public X500Name(DerValue value) throws IOException {
0282: //Note that toDerInputStream uses only the buffer (data) and not
0283: //the tag, so an empty SEQUENCE (OF) will yield an empty DerInputStream
0284: this (value.toDerInputStream());
0285: }
0286:
0287: /**
0288: * Constructs a name from an ASN.1 encoded input stream. The encoding
0289: * of the name in the stream uses DER (a BER/1 subset).
0290: *
0291: * @param in DER-encoded data holding an X.500 name.
0292: */
0293: public X500Name(DerInputStream in) throws IOException {
0294: parseDER(in);
0295: }
0296:
0297: /**
0298: * Constructs a name from an ASN.1 encoded byte array.
0299: *
0300: * @param name DER-encoded byte array holding an X.500 name.
0301: */
0302: public X500Name(byte[] name) throws IOException {
0303: DerInputStream in = new DerInputStream(name);
0304: parseDER(in);
0305: }
0306:
0307: /**
0308: * Return an immutable List of all RDNs in this X500Name.
0309: */
0310: public List rdns() {
0311: List list = rdnList;
0312: if (list == null) {
0313: list = Collections.unmodifiableList(Arrays.asList(names));
0314: rdnList = list;
0315: }
0316: return list;
0317: }
0318:
0319: /**
0320: * Return the number of RDNs in this X500Name.
0321: */
0322: public int size() {
0323: return names.length;
0324: }
0325:
0326: /**
0327: * Return an immutable List of the the AVAs contained in all the
0328: * RDNs of this X500Name.
0329: */
0330: public List allAvas() {
0331: List list = allAvaList;
0332: if (list == null) {
0333: list = new ArrayList();
0334: for (int i = 0; i < names.length; i++) {
0335: list.addAll(names[i].avas());
0336: }
0337: }
0338: return list;
0339: }
0340:
0341: /**
0342: * Return the total number of AVAs contained in all the RDNs of
0343: * this X500Name.
0344: */
0345: public int avaSize() {
0346: return allAvas().size();
0347: }
0348:
0349: /**
0350: * Return whether this X500Name is empty. An X500Name is not empty
0351: * if it has at least one RDN containing at least one AVA.
0352: */
0353: public boolean isEmpty() {
0354: int n = names.length;
0355: if (n == 0) {
0356: return true;
0357: }
0358: for (int i = 0; i < n; i++) {
0359: if (names[i].assertion.length != 0) {
0360: return false;
0361: }
0362: }
0363: return true;
0364: }
0365:
0366: /**
0367: * Calculates a hash code value for the object. Objects
0368: * which are equal will also have the same hashcode.
0369: */
0370: public int hashCode() {
0371: return getRFC2253CanonicalName().hashCode();
0372: }
0373:
0374: /**
0375: * Compares this name with another, for equality.
0376: *
0377: * @return true iff the names are identical.
0378: */
0379: public boolean equals(Object obj) {
0380: if (this == obj) {
0381: return true;
0382: }
0383: if (obj instanceof X500Name == false) {
0384: return false;
0385: }
0386: X500Name other = (X500Name) obj;
0387: // if we already have the canonical forms, compare now
0388: if ((this .canonicalDn != null) && (other.canonicalDn != null)) {
0389: return this .canonicalDn.equals(other.canonicalDn);
0390: }
0391: // quick check that number of RDNs and AVAs match before canonicalizing
0392: int n = this .names.length;
0393: if (n != other.names.length) {
0394: return false;
0395: }
0396: for (int i = 0; i < n; i++) {
0397: RDN r1 = this .names[i];
0398: RDN r2 = other.names[i];
0399: if (r1.assertion.length != r2.assertion.length) {
0400: return false;
0401: }
0402: }
0403: // definite check via canonical form
0404: String this Canonical = this .getRFC2253CanonicalName();
0405: String otherCanonical = other.getRFC2253CanonicalName();
0406: return this Canonical.equals(otherCanonical);
0407: }
0408:
0409: /*
0410: * Returns the name component as a Java string, regardless of its
0411: * encoding restrictions.
0412: */
0413: private String getString(DerValue attribute) throws IOException {
0414: if (attribute == null)
0415: return null;
0416: String value = attribute.getAsString();
0417:
0418: if (value == null)
0419: throw new IOException("not a DER string encoding, "
0420: + attribute.tag);
0421: else
0422: return value;
0423: }
0424:
0425: /**
0426: * Return type of GeneralName.
0427: */
0428: public int getType() {
0429: return (GeneralNameInterface.NAME_DIRECTORY);
0430: }
0431:
0432: /**
0433: * Returns a "Country" name component. If more than one
0434: * such attribute exists, the topmost one is returned.
0435: *
0436: * @return "C=" component of the name, if any.
0437: */
0438: public String getCountry() throws IOException {
0439: DerValue attr = findAttribute(countryName_oid);
0440:
0441: return getString(attr);
0442: }
0443:
0444: /**
0445: * Returns an "Organization" name component. If more than
0446: * one such attribute exists, the topmost one is returned.
0447: *
0448: * @return "O=" component of the name, if any.
0449: */
0450: public String getOrganization() throws IOException {
0451: DerValue attr = findAttribute(orgName_oid);
0452:
0453: return getString(attr);
0454: }
0455:
0456: /**
0457: * Returns an "Organizational Unit" name component. If more
0458: * than one such attribute exists, the topmost one is returned.
0459: *
0460: * @return "OU=" component of the name, if any.
0461: */
0462: public String getOrganizationalUnit() throws IOException {
0463: DerValue attr = findAttribute(orgUnitName_oid);
0464:
0465: return getString(attr);
0466: }
0467:
0468: /**
0469: * Returns a "Common Name" component. If more than one such
0470: * attribute exists, the topmost one is returned.
0471: *
0472: * @return "CN=" component of the name, if any.
0473: */
0474: public String getCommonName() throws IOException {
0475: DerValue attr = findAttribute(commonName_oid);
0476:
0477: return getString(attr);
0478: }
0479:
0480: /**
0481: * Returns a "Locality" name component. If more than one
0482: * such component exists, the topmost one is returned.
0483: *
0484: * @return "L=" component of the name, if any.
0485: */
0486: public String getLocality() throws IOException {
0487: DerValue attr = findAttribute(localityName_oid);
0488:
0489: return getString(attr);
0490: }
0491:
0492: /**
0493: * Returns a "State" name component. If more than one
0494: * such component exists, the topmost one is returned.
0495: *
0496: * @return "S=" component of the name, if any.
0497: */
0498: public String getState() throws IOException {
0499: DerValue attr = findAttribute(stateName_oid);
0500:
0501: return getString(attr);
0502: }
0503:
0504: /**
0505: * Returns a "Domain" name component. If more than one
0506: * such component exists, the topmost one is returned.
0507: *
0508: * @return "DC=" component of the name, if any.
0509: */
0510: public String getDomain() throws IOException {
0511: DerValue attr = findAttribute(DOMAIN_COMPONENT_OID);
0512:
0513: return getString(attr);
0514: }
0515:
0516: /**
0517: * Returns a "DN Qualifier" name component. If more than one
0518: * such component exists, the topmost one is returned.
0519: *
0520: * @return "DNQ=" component of the name, if any.
0521: */
0522: public String getDNQualifier() throws IOException {
0523: DerValue attr = findAttribute(DNQUALIFIER_OID);
0524:
0525: return getString(attr);
0526: }
0527:
0528: /**
0529: * Returns a "Surname" name component. If more than one
0530: * such component exists, the topmost one is returned.
0531: *
0532: * @return "SURNAME=" component of the name, if any.
0533: */
0534: public String getSurname() throws IOException {
0535: DerValue attr = findAttribute(SURNAME_OID);
0536:
0537: return getString(attr);
0538: }
0539:
0540: /**
0541: * Returns a "Given Name" name component. If more than one
0542: * such component exists, the topmost one is returned.
0543: *
0544: * @return "GIVENNAME=" component of the name, if any.
0545: */
0546: public String getGivenName() throws IOException {
0547: DerValue attr = findAttribute(GIVENNAME_OID);
0548:
0549: return getString(attr);
0550: }
0551:
0552: /**
0553: * Returns an "Initials" name component. If more than one
0554: * such component exists, the topmost one is returned.
0555: *
0556: * @return "INITIALS=" component of the name, if any.
0557: */
0558: public String getInitials() throws IOException {
0559: DerValue attr = findAttribute(INITIALS_OID);
0560:
0561: return getString(attr);
0562: }
0563:
0564: /**
0565: * Returns a "Generation Qualifier" name component. If more than one
0566: * such component exists, the topmost one is returned.
0567: *
0568: * @return "GENERATION=" component of the name, if any.
0569: */
0570: public String getGeneration() throws IOException {
0571: DerValue attr = findAttribute(GENERATIONQUALIFIER_OID);
0572:
0573: return getString(attr);
0574: }
0575:
0576: /**
0577: * Returns an "IP address" name component. If more than one
0578: * such component exists, the topmost one is returned.
0579: *
0580: * @return "IP=" component of the name, if any.
0581: */
0582: public String getIP() throws IOException {
0583: DerValue attr = findAttribute(ipAddress_oid);
0584:
0585: return getString(attr);
0586: }
0587:
0588: /**
0589: * Returns a string form of the X.500 distinguished name.
0590: * The format of the string is from RFC 1779. The returned string
0591: * may contain non-standardised keywords for more readability
0592: * (keywords from 1779, 2253, and 2459).
0593: */
0594: public String toString() {
0595: if (dn == null) {
0596: generateDN();
0597: }
0598: return dn;
0599: }
0600:
0601: /**
0602: * Returns a string form of the X.500 distinguished name
0603: * using the algorithm defined in RFC 1779. Only standard attribute type
0604: * keywords defined in RFC 1779 are emitted.
0605: */
0606: public String getRFC1779Name() {
0607: if (rfc1779Dn == null) {
0608: generateRFC1779DN();
0609: }
0610: return rfc1779Dn;
0611: }
0612:
0613: /**
0614: * Returns a string form of the X.500 distinguished name
0615: * using the algorithm defined in RFC 2253. Only standard attribute type
0616: * keywords defined in RFC 2253 are emitted.
0617: */
0618: public String getRFC2253Name() {
0619: /* check for and return cached name */
0620: if (rfc2253Dn != null) {
0621: return rfc2253Dn;
0622: }
0623: /*
0624: * Section 2.1 : if the RDNSequence is an empty sequence
0625: * the result is the empty or zero length string.
0626: */
0627: if (names.length == 0) {
0628: rfc2253Dn = "";
0629: return rfc2253Dn;
0630: }
0631:
0632: /*
0633: * 2.1 (continued) : Otherwise, the output consists of the string
0634: * encodings of each RelativeDistinguishedName in the RDNSequence
0635: * (according to 2.2), starting with the last element of the sequence
0636: * and moving backwards toward the first.
0637: *
0638: * The encodings of adjoining RelativeDistinguishedNames are separated
0639: * by a comma character (',' ASCII 44).
0640: */
0641: StringBuffer fullname = new StringBuffer(48);
0642: for (int i = names.length - 1; i >= 0; i--) {
0643: if (i < names.length - 1) {
0644: fullname.append(',');
0645: }
0646: fullname.append(names[i].toRFC2253String());
0647: }
0648: rfc2253Dn = new String(fullname);
0649: return rfc2253Dn;
0650: }
0651:
0652: public String getRFC2253CanonicalName() {
0653: /* check for and return cached name */
0654: if (canonicalDn != null) {
0655: return canonicalDn;
0656: }
0657: /*
0658: * Section 2.1 : if the RDNSequence is an empty sequence
0659: * the result is the empty or zero length string.
0660: */
0661: if (names.length == 0) {
0662: canonicalDn = "";
0663: return canonicalDn;
0664: }
0665:
0666: /*
0667: * 2.1 (continued) : Otherwise, the output consists of the string
0668: * encodings of each RelativeDistinguishedName in the RDNSequence
0669: * (according to 2.2), starting with the last element of the sequence
0670: * and moving backwards toward the first.
0671: *
0672: * The encodings of adjoining RelativeDistinguishedNames are separated
0673: * by a comma character (',' ASCII 44).
0674: */
0675: StringBuffer fullname = new StringBuffer(48);
0676: for (int i = names.length - 1; i >= 0; i--) {
0677: if (i < names.length - 1) {
0678: fullname.append(',');
0679: }
0680: fullname.append(names[i].toRFC2253String(true));
0681: }
0682: canonicalDn = new String(fullname);
0683: return canonicalDn;
0684: }
0685:
0686: /**
0687: * Returns the value of toString(). This call is needed to
0688: * implement the java.security.Principal interface.
0689: */
0690: public String getName() {
0691: return toString();
0692: }
0693:
0694: /**
0695: * Find the first instance of this attribute in a "top down"
0696: * search of all the attributes in the name.
0697: */
0698: private DerValue findAttribute(ObjectIdentifier attribute) {
0699: if (names != null) {
0700: for (int i = 0; i < names.length; i++) {
0701: DerValue value = names[i].findAttribute(attribute);
0702: if (value != null) {
0703: return value;
0704: }
0705: }
0706: }
0707: return null;
0708: }
0709:
0710: /**
0711: * Find the most specific ("last") attribute of the given
0712: * type.
0713: */
0714: public DerValue findMostSpecificAttribute(ObjectIdentifier attribute) {
0715: if (names != null) {
0716: for (int i = names.length - 1; i >= 0; i--) {
0717: DerValue value = names[i].findAttribute(attribute);
0718: if (value != null) {
0719: return value;
0720: }
0721: }
0722: }
0723: return null;
0724: }
0725:
0726: /****************************************************************/
0727:
0728: private void parseDER(DerInputStream in) throws IOException {
0729: //
0730: // X.500 names are a "SEQUENCE OF" RDNs, which means zero or
0731: // more and order matters. We scan them in order, which
0732: // conventionally is big-endian.
0733: //
0734: DerValue[] nameseq = null;
0735: byte[] derBytes = in.toByteArray();
0736:
0737: try {
0738: nameseq = in.getSequence(5);
0739: } catch (IOException ioe) {
0740: if (derBytes == null) {
0741: nameseq = null;
0742: } else {
0743: DerValue derVal = new DerValue(DerValue.tag_Sequence,
0744: derBytes);
0745: derBytes = derVal.toByteArray();
0746: nameseq = new DerInputStream(derBytes).getSequence(5);
0747: }
0748: }
0749:
0750: if (nameseq == null) {
0751: names = new RDN[0];
0752: } else {
0753: names = new RDN[nameseq.length];
0754: for (int i = 0; i < nameseq.length; i++) {
0755: names[i] = new RDN(nameseq[i]);
0756: }
0757: }
0758: }
0759:
0760: /**
0761: * Encodes the name in DER-encoded form.
0762: *
0763: * @deprecated Use encode() instead
0764: * @param out where to put the DER-encoded X.500 name
0765: */
0766: public void emit(DerOutputStream out) throws IOException {
0767: encode(out);
0768: }
0769:
0770: /**
0771: * Encodes the name in DER-encoded form.
0772: *
0773: * @param out where to put the DER-encoded X.500 name
0774: */
0775: public void encode(DerOutputStream out) throws IOException {
0776: DerOutputStream tmp = new DerOutputStream();
0777: for (int i = 0; i < names.length; i++) {
0778: names[i].encode(tmp);
0779: }
0780: out.write(DerValue.tag_Sequence, tmp);
0781: }
0782:
0783: /**
0784: * Returned the encoding as an uncloned byte array. Callers must
0785: * guarantee that they neither modify it not expose it to untrusted
0786: * code.
0787: */
0788: public byte[] getEncodedInternal() throws IOException {
0789: if (encoded == null) {
0790: DerOutputStream out = new DerOutputStream();
0791: DerOutputStream tmp = new DerOutputStream();
0792: for (int i = 0; i < names.length; i++) {
0793: names[i].encode(tmp);
0794: }
0795: out.write(DerValue.tag_Sequence, tmp);
0796: encoded = out.toByteArray();
0797: }
0798: return encoded;
0799: }
0800:
0801: /**
0802: * Gets the name in DER-encoded form.
0803: *
0804: * @return the DER encoded byte array of this name.
0805: */
0806: public byte[] getEncoded() throws IOException {
0807: return (byte[]) getEncodedInternal().clone();
0808: }
0809:
0810: /*
0811: * Parses a Distinguished Name (DN) in printable representation.
0812: *
0813: * According to RFC 1779, RDNs in a DN are separated by comma.
0814: * The following examples show both methods of quoting a comma, so that it
0815: * is not considered a separator:
0816: *
0817: * O="Sue, Grabbit and Runn" or
0818: * O=Sue\, Grabbit and Runn
0819: *
0820: * This method can parse 1779 or 2253 DNs and non-standard 2459 keywords.
0821: */
0822: private void parseDN(String input) throws IOException {
0823: if (input == null || input.length() == 0) {
0824: names = new RDN[0];
0825: return;
0826: }
0827:
0828: Vector dnVector = new Vector();
0829: int dnOffset = 0;
0830: int rdnEnd;
0831: String rdnString;
0832: int quoteCount = 0;
0833:
0834: String dnString = input;
0835:
0836: int searchOffset = 0;
0837: int nextComma = dnString.indexOf(',');
0838: int nextSemiColon = dnString.indexOf(';');
0839: while (nextComma >= 0 || nextSemiColon >= 0) {
0840:
0841: if (nextSemiColon < 0) {
0842: rdnEnd = nextComma;
0843: } else if (nextComma < 0) {
0844: rdnEnd = nextSemiColon;
0845: } else {
0846: rdnEnd = Math.min(nextComma, nextSemiColon);
0847: }
0848: quoteCount += countQuotes(dnString, searchOffset, rdnEnd);
0849:
0850: /*
0851: * We have encountered an RDN delimiter (comma or a semicolon).
0852: * If the comma or semicolon in the RDN under consideration is
0853: * preceded by a backslash (escape), or by a double quote, it
0854: * is part of the RDN. Otherwise, it is used as a separator, to
0855: * delimit the RDN under consideration from any subsequent RDNs.
0856: */
0857: if (rdnEnd >= 0 && quoteCount != 1
0858: && !escaped(rdnEnd, searchOffset, dnString)) {
0859:
0860: /*
0861: * Comma/semicolon is a separator
0862: */
0863: rdnString = dnString.substring(dnOffset, rdnEnd);
0864:
0865: // Parse RDN, and store it in vector
0866: RDN rdn = new RDN(rdnString);
0867: dnVector.addElement(rdn);
0868:
0869: // Increase the offset
0870: dnOffset = rdnEnd + 1;
0871:
0872: // Set quote counter back to zero
0873: quoteCount = 0;
0874: }
0875:
0876: searchOffset = rdnEnd + 1;
0877: nextComma = dnString.indexOf(',', searchOffset);
0878: nextSemiColon = dnString.indexOf(';', searchOffset);
0879: }
0880:
0881: // Parse last or only RDN, and store it in vector
0882: rdnString = dnString.substring(dnOffset);
0883: RDN rdn = new RDN(rdnString);
0884: dnVector.addElement(rdn);
0885:
0886: /*
0887: * Store the vector elements as an array of RDNs
0888: * NOTE: It's only on output that little-endian ordering is used.
0889: */
0890: int numElem = dnVector.size();
0891: names = new RDN[numElem];
0892: Enumeration e = dnVector.elements();
0893: int i = numElem - 1;
0894:
0895: while (e.hasMoreElements()) {
0896: names[i--] = (RDN) e.nextElement();
0897: }
0898: }
0899:
0900: private void parseRFC2253DN(String dnString) throws IOException {
0901: if (dnString.length() == 0) {
0902: names = new RDN[0];
0903: return;
0904: }
0905:
0906: Vector dnVector = new Vector();
0907: int dnOffset = 0;
0908: String rdnString;
0909:
0910: int searchOffset = 0;
0911: int rdnEnd = dnString.indexOf(',');
0912: while (rdnEnd >= 0) {
0913: /*
0914: * We have encountered an RDN delimiter (comma).
0915: * If the comma in the RDN under consideration is
0916: * preceded by a backslash (escape), it
0917: * is part of the RDN. Otherwise, it is used as a separator, to
0918: * delimit the RDN under consideration from any subsequent RDNs.
0919: */
0920: if (rdnEnd > 0 && !escaped(rdnEnd, searchOffset, dnString)) {
0921:
0922: /*
0923: * Comma is a separator
0924: */
0925: rdnString = dnString.substring(dnOffset, rdnEnd);
0926:
0927: // Parse RDN, and store it in vector
0928: RDN rdn = new RDN(rdnString, "RFC2253");
0929: dnVector.addElement(rdn);
0930:
0931: // Increase the offset
0932: dnOffset = rdnEnd + 1;
0933: }
0934:
0935: searchOffset = rdnEnd + 1;
0936: rdnEnd = dnString.indexOf(',', searchOffset);
0937: }
0938:
0939: // Parse last or only RDN, and store it in vector
0940: rdnString = dnString.substring(dnOffset);
0941: RDN rdn = new RDN(rdnString, "RFC2253");
0942: dnVector.addElement(rdn);
0943:
0944: /*
0945: * Store the vector elements as an array of RDNs
0946: * NOTE: It's only on output that little-endian ordering is used.
0947: */
0948: int numElem = dnVector.size();
0949: names = new RDN[numElem];
0950: Enumeration e = dnVector.elements();
0951: int i = numElem - 1;
0952:
0953: while (e.hasMoreElements()) {
0954: names[i--] = (RDN) e.nextElement();
0955: }
0956: }
0957:
0958: /*
0959: * Counts double quotes in string.
0960: * Escaped quotes are ignored.
0961: */
0962: static int countQuotes(String string, int from, int to) {
0963: int count = 0;
0964:
0965: for (int i = from; i < to; i++) {
0966: if ((string.charAt(i) == '"' && i == from)
0967: || (string.charAt(i) == '"' && string.charAt(i - 1) != '\\')) {
0968: count++;
0969: }
0970: }
0971:
0972: return count;
0973: }
0974:
0975: private static boolean escaped(int rdnEnd, int searchOffset,
0976: String dnString) {
0977:
0978: if (rdnEnd == 1 && dnString.charAt(rdnEnd - 1) == '\\') {
0979:
0980: // case 1:
0981: // \,
0982:
0983: return true;
0984:
0985: } else if (rdnEnd > 1 && dnString.charAt(rdnEnd - 1) == '\\'
0986: && dnString.charAt(rdnEnd - 2) != '\\') {
0987:
0988: // case 2:
0989: // foo\,
0990:
0991: return true;
0992:
0993: } else if (rdnEnd > 1 && dnString.charAt(rdnEnd - 1) == '\\'
0994: && dnString.charAt(rdnEnd - 2) == '\\') {
0995:
0996: // case 3:
0997: // foo\\\\\,
0998:
0999: int count = 0;
1000: rdnEnd--; // back up to last backSlash
1001: while (rdnEnd >= searchOffset) {
1002: if (dnString.charAt(rdnEnd) == '\\') {
1003: count++; // count consecutive backslashes
1004: }
1005: rdnEnd--;
1006: }
1007:
1008: // if count is odd, then rdnEnd is escaped
1009: return (count % 2) != 0 ? true : false;
1010:
1011: } else {
1012: return false;
1013: }
1014: }
1015:
1016: /*
1017: * Dump the printable form of a distinguished name. Each relative
1018: * name is separated from the next by a ",", and assertions in the
1019: * relative names have "label=value" syntax.
1020: *
1021: * Uses RFC 1779 syntax (i.e. little-endian, comma separators)
1022: */
1023: private void generateDN() {
1024: if (names.length == 1) {
1025: dn = names[0].toString();
1026: return;
1027: }
1028:
1029: StringBuffer sb = new StringBuffer(48);
1030: if (names != null) {
1031: for (int i = names.length - 1; i >= 0; i--) {
1032: if (i != names.length - 1) {
1033: sb.append(", ");
1034: }
1035: sb.append(names[i].toString());
1036: }
1037: }
1038: dn = sb.toString();
1039: }
1040:
1041: /*
1042: * Dump the printable form of a distinguished name. Each relative
1043: * name is separated from the next by a ",", and assertions in the
1044: * relative names have "label=value" syntax.
1045: *
1046: * Uses RFC 1779 syntax (i.e. little-endian, comma separators)
1047: * Only valid keywords from RFC 1779 are used.
1048: */
1049: private void generateRFC1779DN() {
1050: if (names.length == 1) {
1051: rfc1779Dn = names[0].toRFC1779String();
1052: return;
1053: }
1054:
1055: StringBuffer sb = new StringBuffer(48);
1056: if (names != null) {
1057: for (int i = names.length - 1; i >= 0; i--) {
1058: if (i != names.length - 1) {
1059: sb.append(", ");
1060: }
1061: sb.append(names[i].toRFC1779String());
1062: }
1063: }
1064: rfc1779Dn = sb.toString();
1065: }
1066:
1067: /****************************************************************/
1068:
1069: /*
1070: * Maybe return a preallocated OID, to reduce storage costs
1071: * and speed recognition of common X.500 attributes.
1072: */
1073: static ObjectIdentifier intern(ObjectIdentifier oid) {
1074: ObjectIdentifier interned = (ObjectIdentifier) internedOIDs
1075: .get(oid);
1076: if (interned != null) {
1077: return interned;
1078: }
1079: internedOIDs.put(oid, oid);
1080: return oid;
1081: }
1082:
1083: private static final Map internedOIDs = new HashMap();
1084:
1085: /*
1086: * Selected OIDs from X.520
1087: * Includes all those specified in RFC2459 as MUST or SHOULD
1088: * be recognized
1089: */
1090: private static final int commonName_data[] = { 2, 5, 4, 3 };
1091: private static final int SURNAME_DATA[] = { 2, 5, 4, 4 };
1092: private static final int SERIALNUMBER_DATA[] = { 2, 5, 4, 5 };
1093: private static final int countryName_data[] = { 2, 5, 4, 6 };
1094: private static final int localityName_data[] = { 2, 5, 4, 7 };
1095: private static final int stateName_data[] = { 2, 5, 4, 8 };
1096: private static final int streetAddress_data[] = { 2, 5, 4, 9 };
1097: private static final int orgName_data[] = { 2, 5, 4, 10 };
1098: private static final int orgUnitName_data[] = { 2, 5, 4, 11 };
1099: private static final int title_data[] = { 2, 5, 4, 12 };
1100: private static final int GIVENNAME_DATA[] = { 2, 5, 4, 42 };
1101: private static final int INITIALS_DATA[] = { 2, 5, 4, 43 };
1102: private static final int GENERATIONQUALIFIER_DATA[] = { 2, 5, 4, 44 };
1103: private static final int DNQUALIFIER_DATA[] = { 2, 5, 4, 46 };
1104:
1105: private static final int ipAddress_data[] = { 1, 3, 6, 1, 4, 1, 42,
1106: 2, 11, 2, 1 };
1107: private static final int DOMAIN_COMPONENT_DATA[] = { 0, 9, 2342,
1108: 19200300, 100, 1, 25 };
1109: private static final int userid_data[] = { 0, 9, 2342, 19200300,
1110: 100, 1, 1 };
1111:
1112: public static final ObjectIdentifier commonName_oid;
1113: public static final ObjectIdentifier countryName_oid;
1114: public static final ObjectIdentifier localityName_oid;
1115: public static final ObjectIdentifier orgName_oid;
1116: public static final ObjectIdentifier orgUnitName_oid;
1117: public static final ObjectIdentifier stateName_oid;
1118: public static final ObjectIdentifier streetAddress_oid;
1119: public static final ObjectIdentifier title_oid;
1120: public static final ObjectIdentifier DNQUALIFIER_OID;
1121: public static final ObjectIdentifier SURNAME_OID;
1122: public static final ObjectIdentifier GIVENNAME_OID;
1123: public static final ObjectIdentifier INITIALS_OID;
1124: public static final ObjectIdentifier GENERATIONQUALIFIER_OID;
1125: public static final ObjectIdentifier ipAddress_oid;
1126: public static final ObjectIdentifier DOMAIN_COMPONENT_OID;
1127: public static final ObjectIdentifier userid_oid;
1128: public static final ObjectIdentifier SERIALNUMBER_OID;
1129:
1130: static {
1131: /** OID for the "CN=" attribute, denoting a person's common name. */
1132: commonName_oid = intern(ObjectIdentifier
1133: .newInternal(commonName_data));
1134:
1135: /** OID for the "SERIALNUMBER=" attribute, denoting a serial number for.
1136: a name. Do not confuse with PKCS#9 issuerAndSerialNumber or the
1137: certificate serial number. */
1138: SERIALNUMBER_OID = intern(ObjectIdentifier
1139: .newInternal(SERIALNUMBER_DATA));
1140:
1141: /** OID for the "C=" attribute, denoting a country. */
1142: countryName_oid = intern(ObjectIdentifier
1143: .newInternal(countryName_data));
1144:
1145: /** OID for the "L=" attribute, denoting a locality (such as a city) */
1146: localityName_oid = intern(ObjectIdentifier
1147: .newInternal(localityName_data));
1148:
1149: /** OID for the "O=" attribute, denoting an organization name */
1150: orgName_oid = intern(ObjectIdentifier.newInternal(orgName_data));
1151:
1152: /** OID for the "OU=" attribute, denoting an organizational unit name */
1153: orgUnitName_oid = intern(ObjectIdentifier
1154: .newInternal(orgUnitName_data));
1155:
1156: /** OID for the "S=" attribute, denoting a state (such as Delaware) */
1157: stateName_oid = intern(ObjectIdentifier
1158: .newInternal(stateName_data));
1159:
1160: /** OID for the "STREET=" attribute, denoting a street address. */
1161: streetAddress_oid = intern(ObjectIdentifier
1162: .newInternal(streetAddress_data));
1163:
1164: /** OID for the "T=" attribute, denoting a person's title. */
1165: title_oid = intern(ObjectIdentifier.newInternal(title_data));
1166:
1167: /** OID for the "DNQUALIFIER=" or "DNQ=" attribute, denoting DN
1168: disambiguating information.*/
1169: DNQUALIFIER_OID = intern(ObjectIdentifier
1170: .newInternal(DNQUALIFIER_DATA));
1171:
1172: /** OID for the "SURNAME=" attribute, denoting a person's surname.*/
1173: SURNAME_OID = intern(ObjectIdentifier.newInternal(SURNAME_DATA));
1174:
1175: /** OID for the "GIVENNAME=" attribute, denoting a person's given name.*/
1176: GIVENNAME_OID = intern(ObjectIdentifier
1177: .newInternal(GIVENNAME_DATA));
1178:
1179: /** OID for the "INITIALS=" attribute, denoting a person's initials.*/
1180: INITIALS_OID = intern(ObjectIdentifier
1181: .newInternal(INITIALS_DATA));
1182:
1183: /** OID for the "GENERATION=" attribute, denoting Jr., II, etc.*/
1184: GENERATIONQUALIFIER_OID = intern(ObjectIdentifier
1185: .newInternal(GENERATIONQUALIFIER_DATA));
1186:
1187: /*
1188: * OIDs from other sources which show up in X.500 names we
1189: * expect to deal with often
1190: */
1191: /** OID for "IP=" IP address attributes, used with SKIP. */
1192: ipAddress_oid = intern(ObjectIdentifier
1193: .newInternal(ipAddress_data));
1194:
1195: /*
1196: * Domain component OID from RFC 1274, RFC 2247, RFC 2459
1197: */
1198:
1199: /*
1200: * OID for "DC=" domain component attributes, used with DNS names in DN
1201: * format
1202: */
1203: DOMAIN_COMPONENT_OID = intern(ObjectIdentifier
1204: .newInternal(DOMAIN_COMPONENT_DATA));
1205:
1206: /** OID for "UID=" denoting a user id, defined in RFCs 1274 & 2798. */
1207: userid_oid = intern(ObjectIdentifier.newInternal(userid_data));
1208: }
1209:
1210: /**
1211: * Return constraint type:<ul>
1212: * <li>NAME_DIFF_TYPE = -1: input name is different type from this name
1213: * (i.e. does not constrain)
1214: * <li>NAME_MATCH = 0: input name matches this name
1215: * <li>NAME_NARROWS = 1: input name narrows this name
1216: * <li>NAME_WIDENS = 2: input name widens this name
1217: * <li>NAME_SAME_TYPE = 3: input name does not match or narrow this name,
1218: & but is same type
1219: * </ul>. These results are used in checking NameConstraints during
1220: * certification path verification.
1221: *
1222: * @param inputName to be checked for being constrained
1223: * @returns constraint type above
1224: * @throws UnsupportedOperationException if name is not exact match, but
1225: * narrowing and widening are not supported for this name type.
1226: */
1227: public int constrains(GeneralNameInterface inputName)
1228: throws UnsupportedOperationException {
1229: int constraintType;
1230: if (inputName == null) {
1231: constraintType = NAME_DIFF_TYPE;
1232: } else if (inputName.getType() != NAME_DIRECTORY) {
1233: constraintType = NAME_DIFF_TYPE;
1234: } else { // type == NAME_DIRECTORY
1235: X500Name inputX500 = (X500Name) inputName;
1236: if (inputX500.equals(this )) {
1237: constraintType = NAME_MATCH;
1238: } else if (inputX500.names.length == 0) {
1239: constraintType = NAME_WIDENS;
1240: } else if (this .names.length == 0) {
1241: constraintType = NAME_NARROWS;
1242: } else if (inputX500.isWithinSubtree(this )) {
1243: constraintType = NAME_NARROWS;
1244: } else if (isWithinSubtree(inputX500)) {
1245: constraintType = NAME_WIDENS;
1246: } else {
1247: constraintType = NAME_SAME_TYPE;
1248: }
1249: }
1250: return constraintType;
1251: }
1252:
1253: /**
1254: * Compares this name with another and determines if
1255: * it is within the subtree of the other. Useful for
1256: * checking against the name constraints extension.
1257: *
1258: * @return true iff this name is within the subtree of other.
1259: */
1260: private boolean isWithinSubtree(X500Name other) {
1261: if (this == other) {
1262: return true;
1263: }
1264: if (other == null) {
1265: return false;
1266: }
1267: if (other.names.length == 0) {
1268: return true;
1269: }
1270: if (this .names.length == 0) {
1271: return false;
1272: }
1273: if (names.length < other.names.length) {
1274: return false;
1275: }
1276: for (int i = 0; i < other.names.length; i++) {
1277: if (!names[i].equals(other.names[i])) {
1278: return false;
1279: }
1280: }
1281: return true;
1282: }
1283:
1284: /**
1285: * Return subtree depth of this name for purposes of determining
1286: * NameConstraints minimum and maximum bounds and for calculating
1287: * path lengths in name subtrees.
1288: *
1289: * @returns distance of name from root
1290: * @throws UnsupportedOperationException if not supported for this name type
1291: */
1292: public int subtreeDepth() throws UnsupportedOperationException {
1293: return names.length;
1294: }
1295:
1296: /**
1297: * Return lowest common ancestor of this name and other name
1298: *
1299: * @param other another X500Name
1300: * @return X500Name of lowest common ancestor; null if none
1301: */
1302: public X500Name commonAncestor(X500Name other) {
1303:
1304: if (other == null) {
1305: return null;
1306: }
1307: int otherLen = other.names.length;
1308: int this Len = this .names.length;
1309: if (this Len == 0 || otherLen == 0) {
1310: return null;
1311: }
1312: int minLen = (this Len < otherLen) ? this Len : otherLen;
1313:
1314: //Compare names from highest RDN down the naming tree
1315: //Note that these are stored in RDN[0]...
1316: int i = 0;
1317: for (; i < minLen; i++) {
1318: if (!names[i].equals(other.names[i])) {
1319: if (i == 0) {
1320: return null;
1321: } else {
1322: break;
1323: }
1324: }
1325: }
1326:
1327: //Copy matching RDNs into new RDN array
1328: RDN[] ancestor = new RDN[i];
1329: for (int j = 0; j < i; j++) {
1330: ancestor[j] = names[j];
1331: }
1332:
1333: X500Name commonAncestor = null;
1334: try {
1335: commonAncestor = new X500Name(ancestor);
1336: } catch (IOException ioe) {
1337: return null;
1338: }
1339: return commonAncestor;
1340: }
1341:
1342: /**
1343: * Constructor object for use by asX500Principal().
1344: */
1345: private static final Constructor principalConstructor;
1346:
1347: /**
1348: * Field object for use by asX500Name().
1349: */
1350: private static final Field principalField;
1351:
1352: /**
1353: * Retrieve the Constructor and Field we need for reflective access
1354: * and make them accessible.
1355: */
1356: static {
1357: PrivilegedExceptionAction pa = new PrivilegedExceptionAction() {
1358: public Object run() throws Exception {
1359: Class pClass = X500Principal.class;
1360: Class[] args = new Class[] { X500Name.class };
1361: Constructor cons = pClass.getDeclaredConstructor(args);
1362: cons.setAccessible(true);
1363: Field field = pClass.getDeclaredField("thisX500Name");
1364: field.setAccessible(true);
1365: return new Object[] { cons, field };
1366: }
1367: };
1368: try {
1369: Object[] result = (Object[]) AccessController
1370: .doPrivileged(pa);
1371: principalConstructor = (Constructor) result[0];
1372: principalField = (Field) result[1];
1373: } catch (Exception e) {
1374: throw (InternalError) new InternalError("Could not obtain "
1375: + "X500Principal access").initCause(e);
1376: }
1377: }
1378:
1379: /**
1380: * Get an X500Principal backed by this X500Name.
1381: *
1382: * Note that we are using privileged reflection to access the hidden
1383: * package private constructor in X500Principal.
1384: */
1385: public X500Principal asX500Principal() {
1386: if (x500Principal == null) {
1387: try {
1388: Object[] args = new Object[] { this };
1389: x500Principal = (X500Principal) principalConstructor
1390: .newInstance(args);
1391: } catch (Exception e) {
1392: throw new RuntimeException("Unexpected exception", e);
1393: }
1394: }
1395: return x500Principal;
1396: }
1397:
1398: /**
1399: * Get the X500Name contained in the given X500Principal.
1400: *
1401: * Note that the X500Name is retrieved using reflection.
1402: */
1403: public static X500Name asX500Name(X500Principal p) {
1404: try {
1405: X500Name name = (X500Name) principalField.get(p);
1406: name.x500Principal = p;
1407: return name;
1408: } catch (Exception e) {
1409: throw new RuntimeException("Unexpected exception", e);
1410: }
1411: }
1412:
1413: }
|