1 /*
   2  * Copyright (c) 1997, 2013, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.  Oracle designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 
  26 package sun.security.x509;
  27 
  28 import java.io.IOException;
  29 import java.net.URI;
  30 import java.net.URISyntaxException;
  31 
  32 import sun.security.util.*;
  33 
  34 /**
  35  * This class implements the URIName as required by the GeneralNames
  36  * ASN.1 object.
  37  * <p>
  38  * [RFC3280] When the subjectAltName extension contains a URI, the name MUST be
  39  * stored in the uniformResourceIdentifier (an IA5String). The name MUST
  40  * be a non-relative URL, and MUST follow the URL syntax and encoding
  41  * rules specified in [RFC 1738].  The name must include both a scheme
  42  * (e.g., "http" or "ftp") and a scheme-specific-part.  The scheme-
  43  * specific-part must include a fully qualified domain name or IP
  44  * address as the host.
  45  * <p>
  46  * As specified in [RFC 1738], the scheme name is not case-sensitive
  47  * (e.g., "http" is equivalent to "HTTP").  The host part is also not
  48  * case-sensitive, but other components of the scheme-specific-part may
  49  * be case-sensitive. When comparing URIs, conforming implementations
  50  * MUST compare the scheme and host without regard to case, but assume
  51  * the remainder of the scheme-specific-part is case sensitive.
  52  * <p>
  53  * [RFC1738] In general, URLs are written as follows:
  54  * <pre>
  55  * <scheme>:<scheme-specific-part>
  56  * </pre>
  57  * A URL contains the name of the scheme being used (<scheme>) followed
  58  * by a colon and then a string (the <scheme-specific-part>) whose
  59  * interpretation depends on the scheme.
  60  * <p>
  61  * While the syntax for the rest of the URL may vary depending on the
  62  * particular scheme selected, URL schemes that involve the direct use
  63  * of an IP-based protocol to a specified host on the Internet use a
  64  * common syntax for the scheme-specific data:
  65  * <pre>
  66  * //<user>:<password>@<host>:<port>/<url-path>
  67  * </pre>
  68  * [RFC2732] specifies that an IPv6 address contained inside a URL
  69  * must be enclosed in square brackets (to allow distinguishing the
  70  * colons that separate IPv6 components from the colons that separate
  71  * scheme-specific data.
  72  * <p>
  73  * @author Amit Kapoor
  74  * @author Hemma Prafullchandra
  75  * @author Sean Mullan
  76  * @author Steve Hanna
  77  * @see GeneralName
  78  * @see GeneralNames
  79  * @see GeneralNameInterface
  80  */
  81 public class URIName implements GeneralNameInterface {
  82 
  83     // private attributes
  84     private URI uri;
  85     private String host;
  86     private DNSName hostDNS;
  87     private IPAddressName hostIP;
  88 
  89     /**
  90      * Create the URIName object from the passed encoded Der value.
  91      *
  92      * @param derValue the encoded DER URIName.
  93      * @exception IOException on error.
  94      */
  95     public URIName(DerValue derValue) throws IOException {
  96         this(derValue.getIA5String());
  97     }
  98 
  99     /**
 100      * Create the URIName object with the specified name.
 101      *
 102      * @param name the URIName.
 103      * @throws IOException if name is not a proper URIName
 104      */
 105     public URIName(String name) throws IOException {
 106         try {
 107             uri = new URI(name);
 108         } catch (URISyntaxException use) {
 109             throw new IOException("invalid URI name:" + name, use);
 110         }
 111         if (uri.getScheme() == null) {
 112             throw new IOException("URI name must include scheme:" + name);
 113         }
 114 
 115         host = uri.getHost();
 116         // RFC 3280 says that the host should be non-null, but we allow it to
 117         // be null because some widely deployed certificates contain CDP
 118         // extensions with URIs that have no hostname (see bugs 4802236 and
 119         // 5107944).
 120         if (host != null) {
 121             if (host.charAt(0) == '[') {
 122                 // Verify host is a valid IPv6 address name
 123                 String ipV6Host = host.substring(1, host.length()-1);
 124                 try {
 125                     hostIP = new IPAddressName(ipV6Host);
 126                 } catch (IOException ioe) {
 127                     throw new IOException("invalid URI name (host " +
 128                         "portion is not a valid IPv6 address):" + name);
 129                 }
 130             } else {
 131                 try {
 132                     hostDNS = new DNSName(host);
 133                 } catch (IOException ioe) {
 134                     // Not a valid DNS Name; see if it is a valid IPv4
 135                     // IPAddressName
 136                     try {
 137                         hostIP = new IPAddressName(host);
 138                     } catch (Exception ioe2) {
 139                         throw new IOException("invalid URI name (host " +
 140                             "portion is not a valid DNS name, IPv4 address," +
 141                             " or IPv6 address):" + name);
 142                     }
 143                 }
 144             }
 145         }
 146     }
 147 
 148     /**
 149      * Create the URIName object with the specified name constraint. URI
 150      * name constraints syntax is different than SubjectAltNames, etc. See
 151      * 4.2.1.11 of RFC 3280.
 152      *
 153      * @param value the URI name constraint
 154      * @throws IOException if name is not a proper URI name constraint
 155      */
 156     public static URIName nameConstraint(DerValue value) throws IOException {
 157         URI uri;
 158         String name = value.getIA5String();
 159         try {
 160             uri = new URI(name);
 161         } catch (URISyntaxException use) {
 162             throw new IOException("invalid URI name constraint:" + name, use);
 163         }
 164         if (uri.getScheme() == null) {
 165             String host = uri.getSchemeSpecificPart();
 166             try {
 167                 DNSName hostDNS;
 168                 if (host.startsWith(".")) {
 169                     hostDNS = new DNSName(host.substring(1));
 170                 } else {
 171                     hostDNS = new DNSName(host);
 172                 }
 173                 return new URIName(uri, host, hostDNS);
 174             } catch (IOException ioe) {
 175                 throw new IOException("invalid URI name constraint:" + name, ioe);
 176             }
 177         } else {
 178             throw new IOException("invalid URI name constraint (should not " +
 179                 "include scheme):" + name);
 180         }
 181     }
 182 
 183     URIName(URI uri, String host, DNSName hostDNS) {
 184         this.uri = uri;
 185         this.host = host;
 186         this.hostDNS = hostDNS;
 187     }
 188 
 189     /**
 190      * Return the type of the GeneralName.
 191      */
 192     public int getType() {
 193         return GeneralNameInterface.NAME_URI;
 194     }
 195 
 196     /**
 197      * Encode the URI name into the DerOutputStream.
 198      *
 199      * @param out the DER stream to encode the URIName to.
 200      * @exception IOException on encoding errors.
 201      */
 202     public void encode(DerOutputStream out) throws IOException {
 203         out.putIA5String(uri.toASCIIString());
 204     }
 205 
 206     /**
 207      * Convert the name into user readable string.
 208      */
 209     public String toString() {
 210         return "URIName: " + uri.toString();
 211     }
 212 
 213     /**
 214      * Compares this name with another, for equality.
 215      *
 216      * @return true iff the names are equivalent according to RFC2459.
 217      */
 218     public boolean equals(Object obj) {
 219         if (this == obj) {
 220             return true;
 221         }
 222 
 223         if (!(obj instanceof URIName)) {
 224             return false;
 225         }
 226 
 227         URIName other = (URIName) obj;
 228 
 229         return uri.equals(other.getURI());
 230     }
 231 
 232     /**
 233      * Returns the URIName as a java.net.URI object
 234      */
 235     public URI getURI() {
 236         return uri;
 237     }
 238 
 239     /**
 240      * Returns this URI name.
 241      */
 242     public String getName() {
 243         return uri.toString();
 244     }
 245 
 246     /**
 247      * Return the scheme name portion of a URIName
 248      *
 249      * @returns scheme portion of full name
 250      */
 251     public String getScheme() {
 252         return uri.getScheme();
 253     }
 254 
 255     /**
 256      * Return the host name or IP address portion of the URIName
 257      *
 258      * @returns host name or IP address portion of full name
 259      */
 260     public String getHost() {
 261         return host;
 262     }
 263 
 264     /**
 265      * Return the host object type; if host name is a
 266      * DNSName, then this host object does not include any
 267      * initial "." on the name.
 268      *
 269      * @returns host name as DNSName or IPAddressName
 270      */
 271     public Object getHostObject() {
 272         if (hostIP != null) {
 273             return hostIP;
 274         } else {
 275             return hostDNS;
 276         }
 277     }
 278 
 279     /**
 280      * Returns the hash code value for this object.
 281      *
 282      * @return a hash code value for this object.
 283      */
 284     public int hashCode() {
 285         return uri.hashCode();
 286     }
 287 
 288     /**
 289      * Return type of constraint inputName places on this name:<ul>
 290      *   <li>NAME_DIFF_TYPE = -1: input name is different type from name
 291      *       (i.e. does not constrain).
 292      *   <li>NAME_MATCH = 0: input name matches name.
 293      *   <li>NAME_NARROWS = 1: input name narrows name (is lower in the naming
 294      *       subtree)
 295      *   <li>NAME_WIDENS = 2: input name widens name (is higher in the naming
 296      *       subtree)
 297      *   <li>NAME_SAME_TYPE = 3: input name does not match or narrow name, but
 298      *       is same type.
 299      * </ul>.
 300      * These results are used in checking NameConstraints during
 301      * certification path verification.
 302      * <p>
 303      * RFC3280: For URIs, the constraint applies to the host part of the name.
 304      * The constraint may specify a host or a domain.  Examples would be
 305      * "foo.bar.com";  and ".xyz.com".  When the the constraint begins with
 306      * a period, it may be expanded with one or more subdomains.  That is,
 307      * the constraint ".xyz.com" is satisfied by both abc.xyz.com and
 308      * abc.def.xyz.com.  However, the constraint ".xyz.com" is not satisfied
 309      * by "xyz.com".  When the constraint does not begin with a period, it
 310      * specifies a host.
 311      * <p>
 312      * @param inputName to be checked for being constrained
 313      * @returns constraint type above
 314      * @throws UnsupportedOperationException if name is not exact match, but
 315      *  narrowing and widening are not supported for this name type.
 316      */
 317     public int constrains(GeneralNameInterface inputName)
 318         throws UnsupportedOperationException {
 319         int constraintType;
 320         if (inputName == null) {
 321             constraintType = NAME_DIFF_TYPE;
 322         } else if (inputName.getType() != NAME_URI) {
 323             constraintType = NAME_DIFF_TYPE;
 324         } else {
 325             // Assuming from here on that one or both of these is
 326             // actually a URI name constraint (not a URI), so we
 327             // only need to compare the host portion of the name
 328 
 329             String otherHost = ((URIName)inputName).getHost();
 330 
 331             // Quick check for equality
 332             if (otherHost.equalsIgnoreCase(host)) {
 333                 constraintType = NAME_MATCH;
 334             } else {
 335                 Object otherHostObject = ((URIName)inputName).getHostObject();
 336 
 337                 if ((hostDNS == null) ||
 338                     !(otherHostObject instanceof DNSName)) {
 339                     // If one (or both) is an IP address, only same type
 340                     constraintType = NAME_SAME_TYPE;
 341                 } else {
 342                     // Both host portions are DNS names. Are they domains?
 343                     boolean thisDomain = (host.charAt(0) == '.');
 344                     boolean otherDomain = (otherHost.charAt(0) == '.');
 345                     DNSName otherDNS = (DNSName) otherHostObject;
 346 
 347                     // Run DNSName.constrains.
 348                     constraintType = hostDNS.constrains(otherDNS);
 349                     // If neither one is a domain, then they can't
 350                     // widen or narrow. That's just SAME_TYPE.
 351                     if ((!thisDomain && !otherDomain) &&
 352                         ((constraintType == NAME_WIDENS) ||
 353                          (constraintType == NAME_NARROWS))) {
 354                         constraintType = NAME_SAME_TYPE;
 355                     }
 356 
 357                     // If one is a domain and the other isn't,
 358                     // then they can't match. The one that's a
 359                     // domain doesn't include the one that's
 360                     // not a domain.
 361                     if ((thisDomain != otherDomain) &&
 362                         (constraintType == NAME_MATCH)) {
 363                         if (thisDomain) {
 364                             constraintType = NAME_WIDENS;
 365                         } else {
 366                             constraintType = NAME_NARROWS;
 367                         }
 368                     }
 369                 }
 370             }
 371         }
 372         return constraintType;
 373     }
 374 
 375     /**
 376      * Return subtree depth of this name for purposes of determining
 377      * NameConstraints minimum and maximum bounds and for calculating
 378      * path lengths in name subtrees.
 379      *
 380      * @returns distance of name from root
 381      * @throws UnsupportedOperationException if not supported for this name type
 382      */
 383     public int subtreeDepth() throws UnsupportedOperationException {
 384         DNSName dnsName = null;
 385         try {
 386             dnsName = new DNSName(host);
 387         } catch (IOException ioe) {
 388             throw new UnsupportedOperationException(ioe.getMessage());
 389         }
 390         return dnsName.subtreeDepth();
 391     }
 392 }