1/*
2 * Copyright (c) 1997, 2014, 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
26package sun.security.x509;
27
28import java.io.IOException;
29import java.net.URI;
30import java.net.URISyntaxException;
31
32import sun.security.util.*;
33
34/**
35 * This class implements the URIName as required by the GeneralNames
36 * ASN.1 object.
37 * <p>
38 * [RFC5280] 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 3986].  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 3986], 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 * {@code <scheme>:<scheme-specific-part>}
56 * </pre>
57 * A URL contains the name of the scheme being used ({@code <scheme>}) followed
58 * by a colon and then a string (the {@code <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 * {@code //<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 *
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 */
81public 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 5280 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.10 of RFC 5280.
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     * @return 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     * @return 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     * @return 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     * RFC5280: 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 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     *
312     * @param inputName to be checked for being constrained
313     * @return 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     * @return 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}
393