1 /*
   2  * Copyright (c) 2006, 2012, 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.ssl;
  27 
  28 import java.io.IOException;
  29 import java.nio.charset.StandardCharsets;
  30 import java.util.ArrayList;
  31 import java.util.Collection;
  32 import java.util.Collections;
  33 import java.util.List;
  34 import java.util.LinkedHashMap;
  35 import java.util.Map;
  36 
  37 import javax.net.ssl.SNIHostName;
  38 import javax.net.ssl.SNIMatcher;
  39 import javax.net.ssl.SNIServerName;
  40 import javax.net.ssl.SSLProtocolException;
  41 import javax.net.ssl.StandardConstants;
  42 
  43 /*
  44  * [RFC 4366/6066] To facilitate secure connections to servers that host
  45  * multiple 'virtual' servers at a single underlying network address, clients
  46  * MAY include an extension of type "server_name" in the (extended) client
  47  * hello.  The "extension_data" field of this extension SHALL contain
  48  * "ServerNameList" where:
  49  *
  50  *     struct {
  51  *         NameType name_type;
  52  *         select (name_type) {
  53  *             case host_name: HostName;
  54  *         } name;
  55  *     } ServerName;
  56  *
  57  *     enum {
  58  *         host_name(0), (255)
  59  *     } NameType;
  60  *
  61  *     opaque HostName<1..2^16-1>;
  62  *
  63  *     struct {
  64  *         ServerName server_name_list<1..2^16-1>
  65  *     } ServerNameList;
  66  */
  67 final class ServerNameExtension extends HelloExtension {
  68 
  69     // For backward compatibility, all future data structures associated with
  70     // new NameTypes MUST begin with a 16-bit length field.
  71     final static int NAME_HEADER_LENGTH = 3;    // NameType: 1 byte
  72                                                 // Name length: 2 bytes
  73     private Map<Integer, SNIServerName> sniMap;
  74     private int listLength;     // ServerNameList length
  75 
  76     // constructor for ServerHello
  77     ServerNameExtension() throws IOException {
  78         super(ExtensionType.EXT_SERVER_NAME);
  79 
  80         listLength = 0;
  81         sniMap = Collections.<Integer, SNIServerName>emptyMap();
  82     }
  83 
  84     // constructor for ClientHello
  85     ServerNameExtension(List<SNIServerName> serverNames)
  86             throws IOException {
  87         super(ExtensionType.EXT_SERVER_NAME);
  88 
  89         listLength = 0;
  90         sniMap = new LinkedHashMap<>();
  91         for (SNIServerName serverName : serverNames) {
  92             // check for duplicated server name type
  93             if (sniMap.put(serverName.getType(), serverName) != null) {
  94                 // unlikely to happen, but in case ...
  95                 throw new RuntimeException(
  96                     "Duplicated server name of type " + serverName.getType());
  97             }
  98 
  99             listLength += serverName.getEncoded().length + NAME_HEADER_LENGTH;
 100         }
 101 
 102         // This constructor is used for ClientHello only.  Empty list is
 103         // not allowed in client mode.
 104         if (listLength == 0) {
 105             throw new RuntimeException("The ServerNameList cannot be empty");
 106         }
 107     }
 108 
 109     // constructor for ServerHello for parsing SNI extension
 110     ServerNameExtension(HandshakeInStream s, int len)
 111             throws IOException {
 112         super(ExtensionType.EXT_SERVER_NAME);
 113 
 114         int remains = len;
 115         if (len >= 2) {    // "server_name" extension in ClientHello
 116             listLength = s.getInt16();     // ServerNameList length
 117             if (listLength == 0 || listLength + 2 != len) {
 118                 throw new SSLProtocolException(
 119                         "Invalid " + type + " extension");
 120             }
 121 
 122             remains -= 2;
 123             sniMap = new LinkedHashMap<>();
 124             while (remains > 0) {
 125                 int code = s.getInt8();       // NameType
 126 
 127                 // HostName (length read in getBytes16);
 128                 byte[] encoded = s.getBytes16();
 129                 SNIServerName serverName;
 130                 switch (code) {
 131                     case StandardConstants.SNI_HOST_NAME:
 132                         if (encoded.length == 0) {
 133                             throw new SSLProtocolException(
 134                                 "Empty HostName in server name indication");
 135                         }
 136                         try {
 137                             serverName = new SNIHostName(encoded);
 138                         } catch (IllegalArgumentException iae) {
 139                             SSLProtocolException spe = new SSLProtocolException(
 140                                 "Illegal server name, type=host_name(" +
 141                                 code + "), name=" +
 142                                 (new String(encoded, StandardCharsets.UTF_8)) +
 143                                 ", value=" + Debug.toString(encoded));
 144                             spe.initCause(iae);
 145                             throw spe;
 146                         }
 147                         break;
 148                     default:
 149                         try {
 150                             serverName = new UnknownServerName(code, encoded);
 151                         } catch (IllegalArgumentException iae) {
 152                             SSLProtocolException spe = new SSLProtocolException(
 153                                 "Illegal server name, type=(" + code +
 154                                 "), value=" + Debug.toString(encoded));
 155                             spe.initCause(iae);
 156                             throw spe;
 157                         }
 158                 }
 159                 // check for duplicated server name type
 160                 if (sniMap.put(serverName.getType(), serverName) != null) {
 161                     throw new SSLProtocolException(
 162                             "Duplicated server name of type " +
 163                             serverName.getType());
 164                 }
 165 
 166                 remains -= encoded.length + NAME_HEADER_LENGTH;
 167             }
 168         } else if (len == 0) {     // "server_name" extension in ServerHello
 169             listLength = 0;
 170             sniMap = Collections.<Integer, SNIServerName>emptyMap();
 171         }
 172 
 173         if (remains != 0) {
 174             throw new SSLProtocolException("Invalid server_name extension");
 175         }
 176     }
 177 
 178     List<SNIServerName> getServerNames() {
 179         if (sniMap != null && !sniMap.isEmpty()) {
 180             return Collections.<SNIServerName>unmodifiableList(
 181                                         new ArrayList<>(sniMap.values()));
 182         }
 183 
 184         return Collections.<SNIServerName>emptyList();
 185     }
 186 
 187     /*
 188      * Is the extension recognized by the corresponding matcher?
 189      *
 190      * This method is used to check whether the server name indication can
 191      * be recognized by the server name matchers.
 192      *
 193      * Per RFC 6066, if the server understood the ClientHello extension but
 194      * does not recognize the server name, the server SHOULD take one of two
 195      * actions: either abort the handshake by sending a fatal-level
 196      * unrecognized_name(112) alert or continue the handshake.
 197      *
 198      * If there is an instance of SNIMatcher defined for a particular name
 199      * type, it must be used to perform match operations on the server name.
 200      */
 201     boolean isMatched(Collection<SNIMatcher> matchers) {
 202         if (sniMap != null && !sniMap.isEmpty()) {
 203             for (SNIMatcher matcher : matchers) {
 204                 SNIServerName sniName = sniMap.get(matcher.getType());
 205                 if (sniName != null && (!matcher.matches(sniName))) {
 206                     return false;
 207                 }
 208             }
 209         }
 210 
 211         return true;
 212     }
 213 
 214     /*
 215      * Is the extension is identical to a server name list?
 216      *
 217      * This method is used to check the server name indication during session
 218      * resumption.
 219      *
 220      * Per RFC 6066, when the server is deciding whether or not to accept a
 221      * request to resume a session, the contents of a server_name extension
 222      * MAY be used in the lookup of the session in the session cache.  The
 223      * client SHOULD include the same server_name extension in the session
 224      * resumption request as it did in the full handshake that established
 225      * the session.  A server that implements this extension MUST NOT accept
 226      * the request to resume the session if the server_name extension contains
 227      * a different name.  Instead, it proceeds with a full handshake to
 228      * establish a new session.  When resuming a session, the server MUST NOT
 229      * include a server_name extension in the server hello.
 230      */
 231     boolean isIdentical(List<SNIServerName> other) {
 232         if (other.size() == sniMap.size()) {
 233             for(SNIServerName sniInOther : other) {
 234                 SNIServerName sniName = sniMap.get(sniInOther.getType());
 235                 if (sniName == null || !sniInOther.equals(sniName)) {
 236                     return false;
 237                 }
 238             }
 239 
 240             return true;
 241         }
 242 
 243         return false;
 244     }
 245 
 246     @Override
 247     int length() {
 248         return listLength == 0 ? 4 : 6 + listLength;
 249     }
 250 
 251     @Override
 252     void send(HandshakeOutStream s) throws IOException {
 253         s.putInt16(type.id);
 254         if (listLength == 0) {
 255             s.putInt16(listLength);     // in ServerHello, empty extension_data
 256         } else {
 257             s.putInt16(listLength + 2); // length of extension_data
 258             s.putInt16(listLength);     // length of ServerNameList
 259 
 260             for (SNIServerName sniName : sniMap.values()) {
 261                 s.putInt8(sniName.getType());         // server name type
 262                 s.putBytes16(sniName.getEncoded());   // server name value
 263             }
 264         }
 265     }
 266 
 267     @Override
 268     public String toString() {
 269         StringBuilder sb = new StringBuilder();
 270         sb.append("Extension ").append(type).append(", server_name: ");
 271         for (SNIServerName sniName : sniMap.values()) {
 272             sb.append('[').append(sniName).append(']');
 273         }
 274         return sb.toString();
 275     }
 276 
 277     private static class UnknownServerName extends SNIServerName {
 278         UnknownServerName(int code, byte[] encoded) {
 279             super(code, encoded);
 280         }
 281     }
 282 
 283 }