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      */
URIName(DerValue derValue)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      */
URIName(String name)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      */
nameConstraint(DerValue value)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 
URIName(URI uri, String host, DNSName hostDNS)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      */
getType()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      */
encode(DerOutputStream out)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      */
toString()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      */
equals(Object obj)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      */
getURI()235     public URI getURI() {
236         return uri;
237     }
238 
239     /**
240      * Returns this URI name.
241      */
getName()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      */
getScheme()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      */
getHost()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      */
getHostObject()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      */
hashCode()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      */
constrains(GeneralNameInterface inputName)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      */
subtreeDepth()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 }
393