jdk/src/java.base/share/classes/sun/security/ssl/ServerNameExtension.java
changeset 25859 3317bb8137f4
parent 24969 afa6934dd8e8
child 32649 2ee9017c7597
--- /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);
+        }
+    }
+
+}