jdk/src/java.base/share/classes/sun/security/ssl/ServerNameExtension.java
changeset 25859 3317bb8137f4
parent 24969 afa6934dd8e8
child 32649 2ee9017c7597
equal deleted inserted replaced
25858:836adbf7a2cd 25859:3317bb8137f4
       
     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         for (SNIServerName sniName : sniMap.values()) {
       
   271             sb.append("[" + sniName + "]");
       
   272         }
       
   273 
       
   274         return "Extension " + type + ", server_name: " + sb;
       
   275     }
       
   276 
       
   277     private static class UnknownServerName extends SNIServerName {
       
   278         UnknownServerName(int code, byte[] encoded) {
       
   279             super(code, encoded);
       
   280         }
       
   281     }
       
   282 
       
   283 }