/* * Copyright (c) 2006, 2012, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Oracle designates this * particular file as subject to the "Classpath" exception as provided * by Oracle in the LICENSE file that accompanied this code. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ package sun.security.ssl; import java.io.IOException; import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.List; import java.util.LinkedHashMap; import java.util.Map; import javax.net.ssl.SNIHostName; import javax.net.ssl.SNIMatcher; import javax.net.ssl.SNIServerName; import javax.net.ssl.SSLProtocolException; import javax.net.ssl.StandardConstants; /* * [RFC 4366/6066] To facilitate secure connections to servers that host * multiple 'virtual' servers at a single underlying network address, clients * MAY include an extension of type "server_name" in the (extended) client * hello. The "extension_data" field of this extension SHALL contain * "ServerNameList" where: * * struct { * NameType name_type; * select (name_type) { * case host_name: HostName; * } name; * } ServerName; * * enum { * host_name(0), (255) * } NameType; * * opaque HostName<1..2^16-1>; * * struct { * ServerName server_name_list<1..2^16-1> * } ServerNameList; */ final class ServerNameExtension extends HelloExtension { // For backward compatibility, all future data structures associated with // new NameTypes MUST begin with a 16-bit length field. final static int NAME_HEADER_LENGTH = 3; // NameType: 1 byte // Name length: 2 bytes private Map sniMap; private int listLength; // ServerNameList length // constructor for ServerHello ServerNameExtension() throws IOException { super(ExtensionType.EXT_SERVER_NAME); listLength = 0; sniMap = Collections.emptyMap(); } // constructor for ClientHello ServerNameExtension(List serverNames) throws IOException { super(ExtensionType.EXT_SERVER_NAME); listLength = 0; sniMap = new LinkedHashMap<>(); for (SNIServerName serverName : serverNames) { // check for duplicated server name type if (sniMap.put(serverName.getType(), serverName) != null) { // unlikely to happen, but in case ... throw new RuntimeException( "Duplicated server name of type " + serverName.getType()); } listLength += serverName.getEncoded().length + NAME_HEADER_LENGTH; } // This constructor is used for ClientHello only. Empty list is // not allowed in client mode. if (listLength == 0) { throw new RuntimeException("The ServerNameList cannot be empty"); } } // constructor for ServerHello for parsing SNI extension ServerNameExtension(HandshakeInStream s, int len) throws IOException { super(ExtensionType.EXT_SERVER_NAME); int remains = len; if (len >= 2) { // "server_name" extension in ClientHello listLength = s.getInt16(); // ServerNameList length if (listLength == 0 || listLength + 2 != len) { throw new SSLProtocolException( "Invalid " + type + " extension"); } remains -= 2; sniMap = new LinkedHashMap<>(); while (remains > 0) { int code = s.getInt8(); // NameType // HostName (length read in getBytes16); byte[] encoded = s.getBytes16(); SNIServerName serverName; switch (code) { case StandardConstants.SNI_HOST_NAME: if (encoded.length == 0) { throw new SSLProtocolException( "Empty HostName in server name indication"); } try { serverName = new SNIHostName(encoded); } catch (IllegalArgumentException iae) { SSLProtocolException spe = new SSLProtocolException( "Illegal server name, type=host_name(" + code + "), name=" + (new String(encoded, StandardCharsets.UTF_8)) + ", value=" + Debug.toString(encoded)); spe.initCause(iae); throw spe; } break; default: try { serverName = new UnknownServerName(code, encoded); } catch (IllegalArgumentException iae) { SSLProtocolException spe = new SSLProtocolException( "Illegal server name, type=(" + code + "), value=" + Debug.toString(encoded)); spe.initCause(iae); throw spe; } } // check for duplicated server name type if (sniMap.put(serverName.getType(), serverName) != null) { throw new SSLProtocolException( "Duplicated server name of type " + serverName.getType()); } remains -= encoded.length + NAME_HEADER_LENGTH; } } else if (len == 0) { // "server_name" extension in ServerHello listLength = 0; sniMap = Collections.emptyMap(); } if (remains != 0) { throw new SSLProtocolException("Invalid server_name extension"); } } List getServerNames() { if (sniMap != null && !sniMap.isEmpty()) { return Collections.unmodifiableList( new ArrayList<>(sniMap.values())); } return Collections.emptyList(); } /* * Is the extension recognized by the corresponding matcher? * * This method is used to check whether the server name indication can * be recognized by the server name matchers. * * Per RFC 6066, if the server understood the ClientHello extension but * does not recognize the server name, the server SHOULD take one of two * actions: either abort the handshake by sending a fatal-level * unrecognized_name(112) alert or continue the handshake. * * If there is an instance of SNIMatcher defined for a particular name * type, it must be used to perform match operations on the server name. */ boolean isMatched(Collection matchers) { if (sniMap != null && !sniMap.isEmpty()) { for (SNIMatcher matcher : matchers) { SNIServerName sniName = sniMap.get(matcher.getType()); if (sniName != null && (!matcher.matches(sniName))) { return false; } } } return true; } /* * Is the extension is identical to a server name list? * * This method is used to check the server name indication during session * resumption. * * Per RFC 6066, when the server is deciding whether or not to accept a * request to resume a session, the contents of a server_name extension * MAY be used in the lookup of the session in the session cache. The * client SHOULD include the same server_name extension in the session * resumption request as it did in the full handshake that established * the session. A server that implements this extension MUST NOT accept * the request to resume the session if the server_name extension contains * a different name. Instead, it proceeds with a full handshake to * establish a new session. When resuming a session, the server MUST NOT * include a server_name extension in the server hello. */ boolean isIdentical(List other) { if (other.size() == sniMap.size()) { for(SNIServerName sniInOther : other) { SNIServerName sniName = sniMap.get(sniInOther.getType()); if (sniName == null || !sniInOther.equals(sniName)) { return false; } } return true; } return false; } @Override int length() { return listLength == 0 ? 4 : 6 + listLength; } @Override void send(HandshakeOutStream s) throws IOException { s.putInt16(type.id); if (listLength == 0) { s.putInt16(listLength); // in ServerHello, empty extension_data } else { s.putInt16(listLength + 2); // length of extension_data s.putInt16(listLength); // length of ServerNameList for (SNIServerName sniName : sniMap.values()) { s.putInt8(sniName.getType()); // server name type s.putBytes16(sniName.getEncoded()); // server name value } } } @Override public String toString() { StringBuilder sb = new StringBuilder(); sb.append("Extension ").append(type).append(", server_name: "); for (SNIServerName sniName : sniMap.values()) { sb.append('[').append(sniName).append(']'); } return sb.toString(); } private static class UnknownServerName extends SNIServerName { UnknownServerName(int code, byte[] encoded) { super(code, encoded); } } }