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 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  */
  43 public 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      *
 178      * @param inputName to be checked for being constrained
 179      * @return 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      * @return 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 }