--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/src/java.base/share/classes/sun/security/ssl/ServerNameExtension.java Sun Aug 17 15:54:13 2014 +0100
@@ -0,0 +1,283 @@
+/*
+ * 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<Integer, SNIServerName> sniMap;
+ private int listLength; // ServerNameList length
+
+ // constructor for ServerHello
+ ServerNameExtension() throws IOException {
+ super(ExtensionType.EXT_SERVER_NAME);
+
+ listLength = 0;
+ sniMap = Collections.<Integer, SNIServerName>emptyMap();
+ }
+
+ // constructor for ClientHello
+ ServerNameExtension(List<SNIServerName> 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.<Integer, SNIServerName>emptyMap();
+ }
+
+ if (remains != 0) {
+ throw new SSLProtocolException("Invalid server_name extension");
+ }
+ }
+
+ List<SNIServerName> getServerNames() {
+ if (sniMap != null && !sniMap.isEmpty()) {
+ return Collections.<SNIServerName>unmodifiableList(
+ new ArrayList<>(sniMap.values()));
+ }
+
+ return Collections.<SNIServerName>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<SNIMatcher> 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<SNIServerName> 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();
+ for (SNIServerName sniName : sniMap.values()) {
+ sb.append("[" + sniName + "]");
+ }
+
+ return "Extension " + type + ", server_name: " + sb;
+ }
+
+ private static class UnknownServerName extends SNIServerName {
+ UnknownServerName(int code, byte[] encoded) {
+ super(code, encoded);
+ }
+ }
+
+}