/* * @(#)URIName.java 1.17 06/10/10 * * Copyright 1990-2008 Sun Microsystems, Inc. All Rights Reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License version * 2 only, as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License version 2 for more details (a copy is * included at /legal/license.txt). * * You should have received a copy of the GNU General Public License * version 2 along with this work; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA * * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa * Clara, CA 95054 or visit www.sun.com if you need additional * information or have any questions. * */ package sun.security.x509; import java.io.IOException; import sun.security.util.*; /** * This class implements the URIName as required by the GeneralNames * ASN.1 object. * <p> * [RFC2459] When the subjectAltName extension contains a URI, the name MUST be * stored in the uniformResourceIdentifier (an IA5String). The name MUST * be a non-relative URL, and MUST follow the URL syntax and encoding * rules specified in [RFC 1738]. The name must include both a scheme * (e.g., "http" or "ftp") and a scheme-specific-part. The scheme- * specific-part must include a fully qualified domain name or IP * address as the host. * <p> * As specified in [RFC 1738], the scheme name is not case-sensitive * (e.g., "http" is equivalent to "HTTP"). The host part is also not * case-sensitive, but other components of the scheme-specific-part may * be case-sensitive. When comparing URIs, conforming implementations * MUST compare the scheme and host without regard to case, but assume * the remainder of the scheme-specific-part is case sensitive. * <p> * [RFC2459] Conforming implementations generating new certificates with * electronic mail addresses MUST use the rfc822Name in the subject * alternative name field (see sec. 4.2.1.7) to describe such * identities. Simultaneous inclusion of the EmailAddress attribute in * the subject distinguished name to support legacy implementations is * deprecated but permitted. * <p> * [RFC1738] In general, URLs are written as follows: * <pre> * <scheme>:<scheme-specific-part> * </pre> * A URL contains the name of the scheme being used (<scheme>) followed * by a colon and then a string (the <scheme-specific-part>) whose * interpretation depends on the scheme. * <p> * While the syntax for the rest of the URL may vary depending on the * particular scheme selected, URL schemes that involve the direct use * of an IP-based protocol to a specified host on the Internet use a * common syntax for the scheme-specific data: * <pre> * //<user>:<password>@<host>:<port>/<url-path> * </pre> * [RFC2732] specifies that an IPv6 address contained inside a URL * must be enclosed in square brackets (to allow distinguishing the * colons that separate IPv6 components from the colons that separate * scheme-specific data. * <p> * @author Amit Kapoor * @author Hemma Prafullchandra * @version 1.9 * @see GeneralName * @see GeneralNames * @see GeneralNameInterface */ public class URIName implements GeneralNameInterface { //private attributes private String name; private String scheme; private String host; private String remainder; private IPAddressName hostIP; private DNSName hostDNS; /** * Create the URIName object from the passed encoded Der value. * * @param derValue the encoded DER URIName. * @exception IOException on error. */ public URIName(DerValue derValue) throws IOException { name = derValue.getIA5String(); parseName(); } /** * Create the URIName object with the specified name. * * @param name the URIName. * @throws IOException if name is not a proper URIName */ public URIName(String name) throws IOException { if (name == null || name.length() == 0) { throw new IOException("URI name must not be null"); } this.name = name; parseName(); } /** * parse the URIName into scheme, host, and remainder * Host includes only the fully-qualified domain name * or IP address. Remainder includes everything except the * scheme and host. * <p> * Since RFC2459 specifies that email addresses must be specified * using emailaddress= attribute in X500Name or using RFC822Name, * we do not need to support the "mailto:user@company.com" scheme * that has no //. * <p> * Format:, where * <ul> * <li>[] encloses optional elements * <li>'[' and ']' are literal square brackets * <li>{} encloses a choice * <li>| separates elements in a choice * <li><> encloses an element * <p> * <scheme> :// [ <username> @ ] { '['<IPv6 addr>']' | <IPv4 addr> | <DNSName> } [: <port>] [ / [ <directory> ] ] * <p> * @throws IOException if name is not a proper URIName */ private void parseName() throws IOException { //parse out <scheme>: int colonAfterSchemeNdx = name.indexOf(':'); if (colonAfterSchemeNdx < 0) throw new IOException("Name " + name + " does not include a <scheme>"); //Verify presence of // immediately following <scheme>: int slashSlashNdx = name.indexOf("//", colonAfterSchemeNdx); if (slashSlashNdx != colonAfterSchemeNdx + 1) throw new IOException("name does not include scheme-specific portion starting with host"); //look for optional / preceding directory, first parsing past any ] enclosing an IPv6 address //(since an IPv6 address can end with "/ <prefix length>") //This slash (end of name string) delimits end of scheme-specific portion int startSlashSearchNdx = slashSlashNdx + 2; if (startSlashSearchNdx == name.length()) throw new IOException("Name " + name + " doesn't include a <host>"); int rightSquareBracketNdx = name.indexOf(']', startSlashSearchNdx); if (rightSquareBracketNdx >= 0) startSlashSearchNdx = rightSquareBracketNdx; int endSchemeSpecificNdx = name.indexOf('/', startSlashSearchNdx); if (endSchemeSpecificNdx < 0) endSchemeSpecificNdx = name.length(); //parse past any user:password portion int startHostNameNdx = name.indexOf('@', slashSlashNdx+2) + 1; if (startHostNameNdx <= 0 || startHostNameNdx >= endSchemeSpecificNdx) startHostNameNdx = slashSlashNdx + 2; //parse to any :port portion, allowing for [] around IPv6 name int endHostNameNdx = -1; if (name.charAt(startHostNameNdx) == '[') { endHostNameNdx = name.indexOf(']', startHostNameNdx); if (endHostNameNdx < 0) throw new IOException("Invalid IPv6 address as host: missing ]"); if (endHostNameNdx < name.length() -1) { char nextChar = name.charAt(endHostNameNdx + 1); if (nextChar != ':' && nextChar != '/') throw new IOException("Invalid host[:port][/] boundary"); else endHostNameNdx = endHostNameNdx + 1; } else { endHostNameNdx = endSchemeSpecificNdx; } } else { endHostNameNdx = name.indexOf(':', startHostNameNdx); if (endHostNameNdx < 0 || endHostNameNdx >= endSchemeSpecificNdx) endHostNameNdx = endSchemeSpecificNdx; } //extract scheme scheme = name.substring(0, colonAfterSchemeNdx); //extract host host = name.substring(startHostNameNdx, endHostNameNdx); if (host.length() > 0) { //verify host portion is a valid IPAddress or DNS name if (host.charAt(0) == '[') { //Verify host is a valid IPv6 address name String ipV6Host = host.substring(1, host.length()-1); try { hostIP = new IPAddressName(ipV6Host); } catch (IOException ioe) { throw new IOException ("Host portion is not a valid IPv6 address: " + ioe.getMessage()); } } else { try { if (host.charAt(0) == '.') { hostDNS = new DNSName(host.substring(1)); } else hostDNS = new DNSName(host); } catch (IOException ioe) { //Not a valid DNS Name; see if it is a valid IPv4 IPAddressName try { hostIP = new IPAddressName(host); } catch (Exception ioe2) { throw new IOException("Host portion is not a valid " + "DNS name, IPv4 address, or IPv6 address"); } } } } //piece together remainder remainder = name.substring(colonAfterSchemeNdx, startHostNameNdx); if (endHostNameNdx < name.length()) remainder += name.substring(endHostNameNdx); } /** * Return the type of the GeneralName. */ public int getType() { return (GeneralNameInterface.NAME_URI); } /** * Encode the URI name into the DerOutputStream. * * @param out the DER stream to encode the URIName to. * @exception IOException on encoding errors. */ public void encode(DerOutputStream out) throws IOException { out.putIA5String(name); } /** * Convert the name into user readable string. */ public String toString() { return ("URIName: " + name); } /** * Compares this name with another, for equality. * * @return true iff the names are equivalent * according to RFC2459. */ public boolean equals(Object obj) { if (this == obj) return true; if (!(obj instanceof URIName)) return false; URIName other = (URIName)obj; if (name.equalsIgnoreCase(other.getName())) { //Compare any remainders with case-sensitive compare String otherRemainder = other.getRemainder(); if ((remainder == null) ^ (otherRemainder == null)) return false; if (remainder != null && otherRemainder != null) return remainder.equals(otherRemainder); // both are null return true; } else { return false; } } /** * Returns this URI name. */ public String getName() { return name; } /** * return the scheme name portion of a URIName * * @param name full URIName * @returns scheme portion of full name */ public String getScheme() { return scheme; } /** * return the host name or IP address portion of the URIName * * @param name full URIName * @returns host name or IP address portion of full name */ public String getHost() { return host; } /** * return the host object type; if host name is a * DNSName, then this host object does not include any * initial "." on the name. * * @returns host name as DNSName or IPAddressName */ public Object getHostObject() { if (hostIP != null) return hostIP; else return hostDNS; } /** * return the remainder (not scheme name or host part) of the URIName * * @param name full URIName * @returns remainder portion of full name */ public String getRemainder() { return remainder; } /** * Returns the hash code value for this object. * * @return a hash code value for this object. */ public int hashCode() { return name.toUpperCase().hashCode(); } /** * Return type of constraint inputName places on this name:<ul> * <li>NAME_DIFF_TYPE = -1: input name is different type from name (i.e. does not constrain). * <li>NAME_MATCH = 0: input name matches name. * <li>NAME_NARROWS = 1: input name narrows name (is lower in the naming subtree) * <li>NAME_WIDENS = 2: input name widens name (is higher in the naming subtree) * <li>NAME_SAME_TYPE = 3: input name does not match or narrow name, but is same type. * </ul>. * These results are used in checking NameConstraints during * certification path verification. * <p> * RFC2459: For URIs, the constraint applies to the host part of the name. The * constraint may specify a host or a domain. Examples would be * "foo.bar.com"; and ".xyz.com". When the the constraint begins with * a period, it may be expanded with one or more subdomains. That is, * the constraint ".xyz.com" is satisfied by both abc.xyz.com and * abc.def.xyz.com. However, the constraint ".xyz.com" is not satisfied * by "xyz.com". When the constraint does not begin with a period, it * specifies a host. * <p> * @param inputName to be checked for being constrained * @returns constraint type above * @throws UnsupportedOperationException if name is not exact match, but narrowing and widening are * not supported for this name type. */ public int constrains(GeneralNameInterface inputName) throws UnsupportedOperationException { int constraintType; if (inputName == null) constraintType = NAME_DIFF_TYPE; else if (inputName.getType() != NAME_URI) constraintType = NAME_DIFF_TYPE; else { String otherScheme = ((URIName)inputName).getScheme(); String otherHost = ((URIName)inputName).getHost(); if (!(scheme.equalsIgnoreCase(otherScheme))) constraintType = NAME_SAME_TYPE; else if (otherHost.equals(host)) constraintType = NAME_MATCH; else if ((((URIName)inputName).getHostObject() instanceof IPAddressName) && hostIP != null) { return hostIP.constrains((IPAddressName)((URIName)inputName).getHostObject()); } else { //At least one host is not an IPAddressName if (otherHost.charAt(0) == '.' || host.charAt(0) == '.') { //DNSName constraint semantics specify subdomains without an initial //period on the constraint. URIName constraint semantics specify subdomains //only when the constraint host name starts with a period. DNSName hostDNS; DNSName otherDNS; try { if (host.charAt(0) == '.') hostDNS = new DNSName(host.substring(1)); else hostDNS = new DNSName(host); if (otherHost.charAt(0) == '.') otherDNS = new DNSName(otherHost.substring(1)); else otherDNS = new DNSName(otherHost); constraintType = hostDNS.constrains(otherDNS); } catch (IOException ioe2) { constraintType = NAME_SAME_TYPE; } catch (UnsupportedOperationException uoe) { //At least one of the hosts is not a valid DNS name constraintType = NAME_SAME_TYPE; } } else { constraintType = NAME_SAME_TYPE; } } } return constraintType; } /** * Return subtree depth of this name for purposes of determining * NameConstraints minimum and maximum bounds and for calculating * path lengths in name subtrees. * * @returns distance of name from root * @throws UnsupportedOperationException if not supported for this name type */ public int subtreeDepth() throws UnsupportedOperationException { DNSName dnsName = null; try { dnsName = new DNSName(host); } catch (IOException ioe) { throw new UnsupportedOperationException(ioe.getMessage()); } int i = dnsName.subtreeDepth(); return i; } }