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 }