001: /*
002: * @(#)URIName.java 1.17 06/10/10
003: *
004: * Copyright 1990-2006 Sun Microsystems, Inc. All Rights Reserved.
005: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER
006: *
007: * This program is free software; you can redistribute it and/or
008: * modify it under the terms of the GNU General Public License version
009: * 2 only, as published by the Free Software Foundation.
010: *
011: * This program is distributed in the hope that it will be useful, but
012: * WITHOUT ANY WARRANTY; without even the implied warranty of
013: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
014: * General Public License version 2 for more details (a copy is
015: * included at /legal/license.txt).
016: *
017: * You should have received a copy of the GNU General Public License
018: * version 2 along with this work; if not, write to the Free Software
019: * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
020: * 02110-1301 USA
021: *
022: * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa
023: * Clara, CA 95054 or visit www.sun.com if you need additional
024: * information or have any questions.
025: *
026: */
027:
028: package sun.security.x509;
029:
030: import java.io.IOException;
031:
032: import sun.security.util.*;
033:
034: /**
035: * This class implements the URIName as required by the GeneralNames
036: * ASN.1 object.
037: * <p>
038: * [RFC2459] When the subjectAltName extension contains a URI, the name MUST be
039: * stored in the uniformResourceIdentifier (an IA5String). The name MUST
040: * be a non-relative URL, and MUST follow the URL syntax and encoding
041: * rules specified in [RFC 1738]. The name must include both a scheme
042: * (e.g., "http" or "ftp") and a scheme-specific-part. The scheme-
043: * specific-part must include a fully qualified domain name or IP
044: * address as the host.
045: * <p>
046: * As specified in [RFC 1738], the scheme name is not case-sensitive
047: * (e.g., "http" is equivalent to "HTTP"). The host part is also not
048: * case-sensitive, but other components of the scheme-specific-part may
049: * be case-sensitive. When comparing URIs, conforming implementations
050: * MUST compare the scheme and host without regard to case, but assume
051: * the remainder of the scheme-specific-part is case sensitive.
052: * <p>
053: * [RFC2459] Conforming implementations generating new certificates with
054: * electronic mail addresses MUST use the rfc822Name in the subject
055: * alternative name field (see sec. 4.2.1.7) to describe such
056: * identities. Simultaneous inclusion of the EmailAddress attribute in
057: * the subject distinguished name to support legacy implementations is
058: * deprecated but permitted.
059: * <p>
060: * [RFC1738] In general, URLs are written as follows:
061: * <pre>
062: * <scheme>:<scheme-specific-part>
063: * </pre>
064: * A URL contains the name of the scheme being used (<scheme>) followed
065: * by a colon and then a string (the <scheme-specific-part>) whose
066: * interpretation depends on the scheme.
067: * <p>
068: * While the syntax for the rest of the URL may vary depending on the
069: * particular scheme selected, URL schemes that involve the direct use
070: * of an IP-based protocol to a specified host on the Internet use a
071: * common syntax for the scheme-specific data:
072: * <pre>
073: * //<user>:<password>@<host>:<port>/<url-path>
074: * </pre>
075: * [RFC2732] specifies that an IPv6 address contained inside a URL
076: * must be enclosed in square brackets (to allow distinguishing the
077: * colons that separate IPv6 components from the colons that separate
078: * scheme-specific data.
079: * <p>
080: * @author Amit Kapoor
081: * @author Hemma Prafullchandra
082: * @version 1.9
083: * @see GeneralName
084: * @see GeneralNames
085: * @see GeneralNameInterface
086: */
087: public class URIName implements GeneralNameInterface {
088:
089: //private attributes
090: private String name;
091: private String scheme;
092: private String host;
093: private String remainder;
094: private IPAddressName hostIP;
095: private DNSName hostDNS;
096:
097: /**
098: * Create the URIName object from the passed encoded Der value.
099: *
100: * @param derValue the encoded DER URIName.
101: * @exception IOException on error.
102: */
103: public URIName(DerValue derValue) throws IOException {
104: name = derValue.getIA5String();
105: parseName();
106: }
107:
108: /**
109: * Create the URIName object with the specified name.
110: *
111: * @param name the URIName.
112: * @throws IOException if name is not a proper URIName
113: */
114: public URIName(String name) throws IOException {
115: if (name == null || name.length() == 0) {
116: throw new IOException("URI name must not be null");
117: }
118: this .name = name;
119: parseName();
120: }
121:
122: /**
123: * parse the URIName into scheme, host, and remainder
124: * Host includes only the fully-qualified domain name
125: * or IP address. Remainder includes everything except the
126: * scheme and host.
127: * <p>
128: * Since RFC2459 specifies that email addresses must be specified
129: * using emailaddress= attribute in X500Name or using RFC822Name,
130: * we do not need to support the "mailto:user@company.com" scheme
131: * that has no //.
132: * <p>
133: * Format:, where
134: * <ul>
135: * <li>[] encloses optional elements
136: * <li>'[' and ']' are literal square brackets
137: * <li>{} encloses a choice
138: * <li>| separates elements in a choice
139: * <li><> encloses an element
140: * <p>
141: * <scheme> :// [ <username> @ ] { '['<IPv6 addr>']' | <IPv4 addr> | <DNSName> } [: <port>] [ / [ <directory> ] ]
142: * <p>
143: * @throws IOException if name is not a proper URIName
144: */
145: private void parseName() throws IOException {
146: //parse out <scheme>:
147: int colonAfterSchemeNdx = name.indexOf(':');
148: if (colonAfterSchemeNdx < 0)
149: throw new IOException("Name " + name
150: + " does not include a <scheme>");
151:
152: //Verify presence of // immediately following <scheme>:
153: int slashSlashNdx = name.indexOf("//", colonAfterSchemeNdx);
154: if (slashSlashNdx != colonAfterSchemeNdx + 1)
155: throw new IOException(
156: "name does not include scheme-specific portion starting with host");
157:
158: //look for optional / preceding directory, first parsing past any ] enclosing an IPv6 address
159: //(since an IPv6 address can end with "/ <prefix length>")
160: //This slash (end of name string) delimits end of scheme-specific portion
161: int startSlashSearchNdx = slashSlashNdx + 2;
162: if (startSlashSearchNdx == name.length())
163: throw new IOException("Name " + name
164: + " doesn't include a <host>");
165: int rightSquareBracketNdx = name.indexOf(']',
166: startSlashSearchNdx);
167: if (rightSquareBracketNdx >= 0)
168: startSlashSearchNdx = rightSquareBracketNdx;
169: int endSchemeSpecificNdx = name.indexOf('/',
170: startSlashSearchNdx);
171: if (endSchemeSpecificNdx < 0)
172: endSchemeSpecificNdx = name.length();
173:
174: //parse past any user:password portion
175: int startHostNameNdx = name.indexOf('@', slashSlashNdx + 2) + 1;
176: if (startHostNameNdx <= 0
177: || startHostNameNdx >= endSchemeSpecificNdx)
178: startHostNameNdx = slashSlashNdx + 2;
179:
180: //parse to any :port portion, allowing for [] around IPv6 name
181: int endHostNameNdx = -1;
182: if (name.charAt(startHostNameNdx) == '[') {
183: endHostNameNdx = name.indexOf(']', startHostNameNdx);
184: if (endHostNameNdx < 0)
185: throw new IOException(
186: "Invalid IPv6 address as host: missing ]");
187:
188: if (endHostNameNdx < name.length() - 1) {
189: char nextChar = name.charAt(endHostNameNdx + 1);
190: if (nextChar != ':' && nextChar != '/')
191: throw new IOException(
192: "Invalid host[:port][/] boundary");
193: else
194: endHostNameNdx = endHostNameNdx + 1;
195: } else {
196: endHostNameNdx = endSchemeSpecificNdx;
197: }
198: } else {
199: endHostNameNdx = name.indexOf(':', startHostNameNdx);
200: if (endHostNameNdx < 0
201: || endHostNameNdx >= endSchemeSpecificNdx)
202: endHostNameNdx = endSchemeSpecificNdx;
203: }
204:
205: //extract scheme
206: scheme = name.substring(0, colonAfterSchemeNdx);
207:
208: //extract host
209: host = name.substring(startHostNameNdx, endHostNameNdx);
210:
211: if (host.length() > 0) {
212: //verify host portion is a valid IPAddress or DNS name
213: if (host.charAt(0) == '[') {
214: //Verify host is a valid IPv6 address name
215: String ipV6Host = host.substring(1, host.length() - 1);
216: try {
217: hostIP = new IPAddressName(ipV6Host);
218: } catch (IOException ioe) {
219: throw new IOException(
220: "Host portion is not a valid IPv6 address: "
221: + ioe.getMessage());
222: }
223: } else {
224: try {
225: if (host.charAt(0) == '.') {
226: hostDNS = new DNSName(host.substring(1));
227: } else
228: hostDNS = new DNSName(host);
229: } catch (IOException ioe) {
230: //Not a valid DNS Name; see if it is a valid IPv4 IPAddressName
231: try {
232: hostIP = new IPAddressName(host);
233: } catch (Exception ioe2) {
234: throw new IOException(
235: "Host portion is not a valid "
236: + "DNS name, IPv4 address, or IPv6 address");
237: }
238: }
239: }
240: }
241:
242: //piece together remainder
243: remainder = name.substring(colonAfterSchemeNdx,
244: startHostNameNdx);
245: if (endHostNameNdx < name.length())
246: remainder += name.substring(endHostNameNdx);
247: }
248:
249: /**
250: * Return the type of the GeneralName.
251: */
252: public int getType() {
253: return (GeneralNameInterface.NAME_URI);
254: }
255:
256: /**
257: * Encode the URI name into the DerOutputStream.
258: *
259: * @param out the DER stream to encode the URIName to.
260: * @exception IOException on encoding errors.
261: */
262: public void encode(DerOutputStream out) throws IOException {
263: out.putIA5String(name);
264: }
265:
266: /**
267: * Convert the name into user readable string.
268: */
269: public String toString() {
270: return ("URIName: " + name);
271: }
272:
273: /**
274: * Compares this name with another, for equality.
275: *
276: * @return true iff the names are equivalent
277: * according to RFC2459.
278: */
279: public boolean equals(Object obj) {
280: if (this == obj)
281: return true;
282:
283: if (!(obj instanceof URIName))
284: return false;
285:
286: URIName other = (URIName) obj;
287:
288: if (name.equalsIgnoreCase(other.getName())) {
289:
290: //Compare any remainders with case-sensitive compare
291:
292: String otherRemainder = other.getRemainder();
293:
294: if ((remainder == null) ^ (otherRemainder == null))
295: return false;
296:
297: if (remainder != null && otherRemainder != null)
298: return remainder.equals(otherRemainder);
299:
300: // both are null
301: return true;
302:
303: } else {
304: return false;
305: }
306: }
307:
308: /**
309: * Returns this URI name.
310: */
311: public String getName() {
312: return name;
313: }
314:
315: /**
316: * return the scheme name portion of a URIName
317: *
318: * @param name full URIName
319: * @returns scheme portion of full name
320: */
321: public String getScheme() {
322: return scheme;
323: }
324:
325: /**
326: * return the host name or IP address portion of the URIName
327: *
328: * @param name full URIName
329: * @returns host name or IP address portion of full name
330: */
331: public String getHost() {
332: return host;
333: }
334:
335: /**
336: * return the host object type; if host name is a
337: * DNSName, then this host object does not include any
338: * initial "." on the name.
339: *
340: * @returns host name as DNSName or IPAddressName
341: */
342: public Object getHostObject() {
343: if (hostIP != null)
344: return hostIP;
345: else
346: return hostDNS;
347: }
348:
349: /**
350: * return the remainder (not scheme name or host part) of the URIName
351: *
352: * @param name full URIName
353: * @returns remainder portion of full name
354: */
355: public String getRemainder() {
356: return remainder;
357: }
358:
359: /**
360: * Returns the hash code value for this object.
361: *
362: * @return a hash code value for this object.
363: */
364: public int hashCode() {
365: return name.toUpperCase().hashCode();
366: }
367:
368: /**
369: * Return type of constraint inputName places on this name:<ul>
370: * <li>NAME_DIFF_TYPE = -1: input name is different type from name (i.e. does not constrain).
371: * <li>NAME_MATCH = 0: input name matches name.
372: * <li>NAME_NARROWS = 1: input name narrows name (is lower in the naming subtree)
373: * <li>NAME_WIDENS = 2: input name widens name (is higher in the naming subtree)
374: * <li>NAME_SAME_TYPE = 3: input name does not match or narrow name, but is same type.
375: * </ul>.
376: * These results are used in checking NameConstraints during
377: * certification path verification.
378: * <p>
379: * RFC2459: For URIs, the constraint applies to the host part of the name. The
380: * constraint may specify a host or a domain. Examples would be
381: * "foo.bar.com"; and ".xyz.com". When the the constraint begins with
382: * a period, it may be expanded with one or more subdomains. That is,
383: * the constraint ".xyz.com" is satisfied by both abc.xyz.com and
384: * abc.def.xyz.com. However, the constraint ".xyz.com" is not satisfied
385: * by "xyz.com". When the constraint does not begin with a period, it
386: * specifies a host.
387: * <p>
388: * @param inputName to be checked for being constrained
389: * @returns constraint type above
390: * @throws UnsupportedOperationException if name is not exact match, but narrowing and widening are
391: * not supported for this name type.
392: */
393: public int constrains(GeneralNameInterface inputName)
394: throws UnsupportedOperationException {
395: int constraintType;
396: if (inputName == null)
397: constraintType = NAME_DIFF_TYPE;
398: else if (inputName.getType() != NAME_URI)
399: constraintType = NAME_DIFF_TYPE;
400: else {
401: String otherScheme = ((URIName) inputName).getScheme();
402: String otherHost = ((URIName) inputName).getHost();
403:
404: if (!(scheme.equalsIgnoreCase(otherScheme)))
405: constraintType = NAME_SAME_TYPE;
406: else if (otherHost.equals(host))
407: constraintType = NAME_MATCH;
408: else if ((((URIName) inputName).getHostObject() instanceof IPAddressName)
409: && hostIP != null) {
410: return hostIP
411: .constrains((IPAddressName) ((URIName) inputName)
412: .getHostObject());
413: } else {
414: //At least one host is not an IPAddressName
415: if (otherHost.charAt(0) == '.' || host.charAt(0) == '.') {
416: //DNSName constraint semantics specify subdomains without an initial
417: //period on the constraint. URIName constraint semantics specify subdomains
418: //only when the constraint host name starts with a period.
419: DNSName hostDNS;
420: DNSName otherDNS;
421: try {
422: if (host.charAt(0) == '.')
423: hostDNS = new DNSName(host.substring(1));
424: else
425: hostDNS = new DNSName(host);
426: if (otherHost.charAt(0) == '.')
427: otherDNS = new DNSName(otherHost
428: .substring(1));
429: else
430: otherDNS = new DNSName(otherHost);
431: constraintType = hostDNS.constrains(otherDNS);
432: } catch (IOException ioe2) {
433: constraintType = NAME_SAME_TYPE;
434: } catch (UnsupportedOperationException uoe) {
435: //At least one of the hosts is not a valid DNS name
436: constraintType = NAME_SAME_TYPE;
437: }
438: } else {
439: constraintType = NAME_SAME_TYPE;
440: }
441: }
442: }
443: return constraintType;
444: }
445:
446: /**
447: * Return subtree depth of this name for purposes of determining
448: * NameConstraints minimum and maximum bounds and for calculating
449: * path lengths in name subtrees.
450: *
451: * @returns distance of name from root
452: * @throws UnsupportedOperationException if not supported for this name type
453: */
454: public int subtreeDepth() throws UnsupportedOperationException {
455: DNSName dnsName = null;
456: try {
457: dnsName = new DNSName(host);
458: } catch (IOException ioe) {
459: throw new UnsupportedOperationException(ioe.getMessage());
460: }
461: int i = dnsName.subtreeDepth();
462: return i;
463: }
464:
465: }
|