1/*
2 * Copyright (c) 1997, 2011, 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.util.Locale;
30
31import sun.security.util.*;
32
33/**
34 * This class implements the RFC822Name as required by the GeneralNames
35 * ASN.1 object.
36 *
37 * @author Amit Kapoor
38 * @author Hemma Prafullchandra
39 * @see GeneralName
40 * @see GeneralNames
41 * @see GeneralNameInterface
42 */
43public class RFC822Name implements GeneralNameInterface
44{
45    private String name;
46
47    /**
48     * Create the RFC822Name object from the passed encoded Der value.
49     *
50     * @param derValue the encoded DER RFC822Name.
51     * @exception IOException on error.
52     */
53    public RFC822Name(DerValue derValue) throws IOException {
54        name = derValue.getIA5String();
55        parseName(name);
56    }
57
58    /**
59     * Create the RFC822Name object with the specified name.
60     *
61     * @param name the RFC822Name.
62     * @throws IOException on invalid input name
63     */
64    public RFC822Name(String name) throws IOException {
65        parseName(name);
66        this.name = name;
67    }
68
69    /**
70     * Parse an RFC822Name string to see if it is a valid
71     * addr-spec according to IETF RFC822 and RFC2459:
72     * [local-part@]domain
73     * <p>
74     * local-part@ could be empty for an RFC822Name NameConstraint,
75     * but the domain at least must be non-empty.  Case is not
76     * significant.
77     *
78     * @param name the RFC822Name string
79     * @throws IOException if name is not valid
80     */
81    public void parseName(String name) throws IOException {
82        if (name == null || name.length() == 0) {
83            throw new IOException("RFC822Name may not be null or empty");
84        }
85        // See if domain is a valid domain name
86        String domain = name.substring(name.indexOf('@')+1);
87        if (domain.length() == 0) {
88            throw new IOException("RFC822Name may not end with @");
89        } else {
90            //An RFC822 NameConstraint could start with a ., although
91            //a DNSName may not
92            if (domain.startsWith(".")) {
93                if (domain.length() == 1)
94                    throw new IOException("RFC822Name domain may not be just .");
95            }
96        }
97    }
98
99    /**
100     * Return the type of the GeneralName.
101     */
102    public int getType() {
103        return (GeneralNameInterface.NAME_RFC822);
104    }
105
106    /**
107     * Return the actual name value of the GeneralName.
108     */
109    public String getName() {
110        return name;
111    }
112
113    /**
114     * Encode the RFC822 name into the DerOutputStream.
115     *
116     * @param out the DER stream to encode the RFC822Name to.
117     * @exception IOException on encoding errors.
118     */
119    public void encode(DerOutputStream out) throws IOException {
120        out.putIA5String(name);
121    }
122
123    /**
124     * Convert the name into user readable string.
125     */
126    public String toString() {
127        return ("RFC822Name: " + name);
128    }
129
130    /**
131     * Compares this name with another, for equality.
132     *
133     * @return true iff the names are equivalent
134     * according to RFC2459.
135     */
136    public boolean equals(Object obj) {
137        if (this == obj)
138            return true;
139
140        if (!(obj instanceof RFC822Name))
141            return false;
142
143        RFC822Name other = (RFC822Name)obj;
144
145        // RFC2459 mandates that these names are
146        // not case-sensitive
147        return name.equalsIgnoreCase(other.name);
148    }
149
150    /**
151     * Returns the hash code value for this object.
152     *
153     * @return a hash code value for this object.
154     */
155    public int hashCode() {
156        return name.toUpperCase(Locale.ENGLISH).hashCode();
157    }
158
159    /**
160     * Return constraint type:<ul>
161     *   <li>NAME_DIFF_TYPE = -1: input name is different type from name (i.e. does not constrain)
162     *   <li>NAME_MATCH = 0: input name matches name
163     *   <li>NAME_NARROWS = 1: input name narrows name
164     *   <li>NAME_WIDENS = 2: input name widens name
165     *   <li>NAME_SAME_TYPE = 3: input name does not match or narrow name, but is same type
166     * </ul>.  These results are used in checking NameConstraints during
167     * certification path verification.
168     * <p>
169     * [RFC2459]    When the subjectAltName extension contains an Internet mail address,
170     * the address MUST be included as an rfc822Name. The format of an
171     * rfc822Name is an "addr-spec" as defined in RFC 822 [RFC 822]. An
172     * addr-spec has the form "local-part@domain". Note that an addr-spec
173     * has no phrase (such as a common name) before it, has no comment (text
174     * surrounded in parentheses) after it, and is not surrounded by "&lt;" and
175     * "&gt;". Note that while upper and lower case letters are allowed in an
176     * RFC 822 addr-spec, no significance is attached to the case.
177     * <p>
178     * @param inputName to be checked for being constrained
179     * @returns constraint type above
180     * @throws UnsupportedOperationException if name is not exact match, but narrowing and widening are
181     *          not supported for this name type.
182     */
183    public int constrains(GeneralNameInterface inputName) throws UnsupportedOperationException {
184        int constraintType;
185        if (inputName == null)
186            constraintType = NAME_DIFF_TYPE;
187        else if (inputName.getType() != (GeneralNameInterface.NAME_RFC822)) {
188            constraintType = NAME_DIFF_TYPE;
189        } else {
190            //RFC2459 specifies that case is not significant in RFC822Names
191            String inName =
192                (((RFC822Name)inputName).getName()).toLowerCase(Locale.ENGLISH);
193            String thisName = name.toLowerCase(Locale.ENGLISH);
194            if (inName.equals(thisName)) {
195                constraintType = NAME_MATCH;
196            } else if (thisName.endsWith(inName)) {
197                /* if both names contain @, then they had to match exactly */
198                if (inName.indexOf('@') != -1) {
199                    constraintType = NAME_SAME_TYPE;
200                } else if (inName.startsWith(".")) {
201                    constraintType = NAME_WIDENS;
202                } else {
203                    int inNdx = thisName.lastIndexOf(inName);
204                    if (thisName.charAt(inNdx-1) == '@' ) {
205                        constraintType = NAME_WIDENS;
206                    } else {
207                        constraintType = NAME_SAME_TYPE;
208                    }
209                }
210            } else if (inName.endsWith(thisName)) {
211                /* if thisName contains @, then they had to match exactly */
212                if (thisName.indexOf('@') != -1) {
213                    constraintType = NAME_SAME_TYPE;
214                } else if (thisName.startsWith(".")) {
215                    constraintType = NAME_NARROWS;
216                } else {
217                    int ndx = inName.lastIndexOf(thisName);
218                    if (inName.charAt(ndx-1) == '@') {
219                        constraintType = NAME_NARROWS;
220                    } else {
221                        constraintType = NAME_SAME_TYPE;
222                    }
223                }
224            } else {
225                constraintType = NAME_SAME_TYPE;
226            }
227        }
228        return constraintType;
229    }
230
231    /**
232     * Return subtree depth of this name for purposes of determining
233     * NameConstraints minimum and maximum bounds.
234     *
235     * @returns distance of name from root
236     * @throws UnsupportedOperationException if not supported for this name type
237     */
238    public int subtreeDepth() throws UnsupportedOperationException {
239        String subtree=name;
240        int i=1;
241
242        /* strip off name@ portion */
243        int atNdx = subtree.lastIndexOf('@');
244        if (atNdx >= 0) {
245            i++;
246            subtree=subtree.substring(atNdx+1);
247        }
248
249        /* count dots in dnsname, adding one if dnsname preceded by @ */
250        for (; subtree.lastIndexOf('.') >= 0; i++) {
251            subtree=subtree.substring(0,subtree.lastIndexOf('.'));
252        }
253
254        return i;
255    }
256}
257