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 
  26 package sun.security.x509;
  27 
  28 import java.io.IOException;
  29 import java.util.Locale;
  30 
  31 import sun.security.util.*;
  32 
  33 /**
  34  * This class implements the DNSName as required by the GeneralNames
  35  * ASN.1 object.
  36  * <p>
  37  * [RFC2459] When the subjectAltName extension contains a domain name service
  38  * label, the domain name MUST be stored in the dNSName (an IA5String).
  39  * The name MUST be in the "preferred name syntax," as specified by RFC
  40  * 1034 [RFC 1034]. Note that while upper and lower case letters are
  41  * allowed in domain names, no signifigance is attached to the case.  In
  42  * addition, while the string " " is a legal domain name, subjectAltName
  43  * extensions with a dNSName " " are not permitted.  Finally, the use of
  44  * the DNS representation for Internet mail addresses (wpolk.nist.gov
  45  * instead of wpolk@nist.gov) is not permitted; such identities are to
  46  * be encoded as rfc822Name.
  47  * <p>
  48  * @author Amit Kapoor
  49  * @author Hemma Prafullchandra
  50  */
  51 public class DNSName implements GeneralNameInterface {
  52     private String name;
  53 
  54     private static final String alpha = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
  55     private static final String digitsAndHyphen = "0123456789-";
  56     private static final String alphaDigitsAndHyphen = alpha + digitsAndHyphen;
  57 
  58     /**
  59      * Create the DNSName object from the passed encoded Der value.
  60      *
  61      * @param derValue the encoded DER DNSName.
  62      * @exception IOException on error.
  63      */
  64     public DNSName(DerValue derValue) throws IOException {
  65         name = derValue.getIA5String();
  66     }
  67 
  68     /**
  69      * Create the DNSName object with the specified name.
  70      *
  71      * @param name the DNSName.
  72      * @throws IOException if the name is not a valid DNSName subjectAltName
  73      */
  74     public DNSName(String name) throws IOException {
  75         if (name == null || name.length() == 0)
  76             throw new IOException("DNS name must not be null");
  77         if (name.indexOf(' ') != -1)
  78             throw new IOException("DNS names or NameConstraints with blank components are not permitted");
  79         if (name.charAt(0) == '.' || name.charAt(name.length() -1) == '.')
  80             throw new IOException("DNS names or NameConstraints may not begin or end with a .");
  81         //Name will consist of label components separated by "."
  82         //startIndex is the index of the first character of a component
  83         //endIndex is the index of the last character of a component plus 1
  84         for (int endIndex,startIndex=0; startIndex < name.length(); startIndex = endIndex+1) {
  85             endIndex = name.indexOf('.', startIndex);
  86             if (endIndex < 0) {
  87                 endIndex = name.length();
  88             }
  89             if ((endIndex-startIndex) < 1)
  90                 throw new IOException("DNSName SubjectAltNames with empty components are not permitted");
  91 
  92             //DNSName components must begin with a letter A-Z or a-z
  93             if (alpha.indexOf(name.charAt(startIndex)) < 0)
  94                 throw new IOException("DNSName components must begin with a letter");
  95             //nonStartIndex: index for characters in the component beyond the first one
  96             for (int nonStartIndex=startIndex+1; nonStartIndex < endIndex; nonStartIndex++) {
  97                 char x = name.charAt(nonStartIndex);
  98                 if ((alphaDigitsAndHyphen).indexOf(x) < 0)
  99                     throw new IOException("DNSName components must consist of letters, digits, and hyphens");
 100             }
 101         }
 102         this.name = name;
 103     }
 104 
 105     /**
 106      * Return the type of the GeneralName.
 107      */
 108     public int getType() {
 109         return (GeneralNameInterface.NAME_DNS);
 110     }
 111 
 112     /**
 113      * Return the actual name value of the GeneralName.
 114      */
 115     public String getName() {
 116         return name;
 117     }
 118 
 119     /**
 120      * Encode the DNS name into the DerOutputStream.
 121      *
 122      * @param out the DER stream to encode the DNSName to.
 123      * @exception IOException on encoding errors.
 124      */
 125     public void encode(DerOutputStream out) throws IOException {
 126         out.putIA5String(name);
 127     }
 128 
 129     /**
 130      * Convert the name into user readable string.
 131      */
 132     public String toString() {
 133         return ("DNSName: " + name);
 134     }
 135 
 136     /**
 137      * Compares this name with another, for equality.
 138      *
 139      * @return true iff the names are equivalent
 140      * according to RFC2459.
 141      */
 142     public boolean equals(Object obj) {
 143         if (this == obj)
 144             return true;
 145 
 146         if (!(obj instanceof DNSName))
 147             return false;
 148 
 149         DNSName other = (DNSName)obj;
 150 
 151         // RFC2459 mandates that these names are
 152         // not case-sensitive
 153         return name.equalsIgnoreCase(other.name);
 154     }
 155 
 156     /**
 157      * Returns the hash code value for this object.
 158      *
 159      * @return a hash code value for this object.
 160      */
 161     public int hashCode() {
 162         return name.toUpperCase(Locale.ENGLISH).hashCode();
 163     }
 164 
 165     /**
 166      * Return type of constraint inputName places on this name:<ul>
 167      *   <li>NAME_DIFF_TYPE = -1: input name is different type from name (i.e. does not constrain).
 168      *   <li>NAME_MATCH = 0: input name matches name.
 169      *   <li>NAME_NARROWS = 1: input name narrows name (is lower in the naming subtree)
 170      *   <li>NAME_WIDENS = 2: input name widens name (is higher in the naming subtree)
 171      *   <li>NAME_SAME_TYPE = 3: input name does not match or narrow name, but is same type.
 172      * </ul>.  These results are used in checking NameConstraints during
 173      * certification path verification.
 174      * <p>
 175      * RFC2459: DNS name restrictions are expressed as foo.bar.com. Any subdomain
 176      * satisfies the name constraint. For example, www.foo.bar.com would
 177      * satisfy the constraint but bigfoo.bar.com would not.
 178      * <p>
 179      * draft-ietf-pkix-new-part1-00.txt:  DNS name restrictions are expressed as foo.bar.com.
 180      * Any DNS name that
 181      * can be constructed by simply adding to the left hand side of the name
 182      * satisfies the name constraint. For example, www.foo.bar.com would
 183      * satisfy the constraint but foo1.bar.com would not.
 184      * <p>
 185      * RFC1034: By convention, domain names can be stored with arbitrary case, but
 186      * domain name comparisons for all present domain functions are done in a
 187      * case-insensitive manner, assuming an ASCII character set, and a high
 188      * order zero bit.
 189      * <p>
 190      * @param inputName to be checked for being constrained
 191      * @returns constraint type above
 192      * @throws UnsupportedOperationException if name is not exact match, but narrowing and widening are
 193      *          not supported for this name type.
 194      */
 195     public int constrains(GeneralNameInterface inputName) throws UnsupportedOperationException {
 196         int constraintType;
 197         if (inputName == null)
 198             constraintType = NAME_DIFF_TYPE;
 199         else if (inputName.getType() != NAME_DNS)
 200             constraintType = NAME_DIFF_TYPE;
 201         else {
 202             String inName =
 203                 (((DNSName)inputName).getName()).toLowerCase(Locale.ENGLISH);
 204             String thisName = name.toLowerCase(Locale.ENGLISH);
 205             if (inName.equals(thisName))
 206                 constraintType = NAME_MATCH;
 207             else if (thisName.endsWith(inName)) {
 208                 int inNdx = thisName.lastIndexOf(inName);
 209                 if (thisName.charAt(inNdx-1) == '.' )
 210                     constraintType = NAME_WIDENS;
 211                 else
 212                     constraintType = NAME_SAME_TYPE;
 213             } else if (inName.endsWith(thisName)) {
 214                 int ndx = inName.lastIndexOf(thisName);
 215                 if (inName.charAt(ndx-1) == '.' )
 216                     constraintType = NAME_NARROWS;
 217                 else
 218                     constraintType = NAME_SAME_TYPE;
 219             } else {
 220                 constraintType = NAME_SAME_TYPE;
 221             }
 222         }
 223         return constraintType;
 224     }
 225 
 226     /**
 227      * Return subtree depth of this name for purposes of determining
 228      * NameConstraints minimum and maximum bounds and for calculating
 229      * path lengths in name subtrees.
 230      *
 231      * @returns distance of name from root
 232      * @throws UnsupportedOperationException if not supported for this name type
 233      */
 234     public int subtreeDepth() throws UnsupportedOperationException {
 235         // subtree depth is always at least 1
 236         int sum = 1;
 237 
 238         // count dots
 239         for (int i = name.indexOf('.'); i >= 0; i = name.indexOf('.', i + 1)) {
 240             ++sum;
 241         }
 242 
 243         return sum;
 244     }
 245 
 246 }