src/java.base/share/classes/sun/security/ssl/ServerNameExtension.java
branchJDK-8145252-TLS13-branch
changeset 56542 56aaa6cb3693
parent 47216 71c04702a3d5
child 56704 c3ee22c3a0f6
--- a/src/java.base/share/classes/sun/security/ssl/ServerNameExtension.java	Fri May 11 14:55:56 2018 -0700
+++ b/src/java.base/share/classes/sun/security/ssl/ServerNameExtension.java	Fri May 11 15:53:12 2018 -0700
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2006, 2012, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2015, 2018, 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
@@ -26,258 +26,561 @@
 package sun.security.ssl;
 
 import java.io.IOException;
+import java.nio.ByteBuffer;
 import java.nio.charset.StandardCharsets;
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Collections;
+import java.util.LinkedHashMap;
 import java.util.List;
-import java.util.LinkedHashMap;
 import java.util.Map;
-
+import java.util.Objects;
 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;
+import static sun.security.ssl.SSLExtension.CH_SERVER_NAME;
+import static sun.security.ssl.SSLExtension.EE_SERVER_NAME;
+import sun.security.ssl.SSLExtension.ExtensionConsumer;
+import static sun.security.ssl.SSLExtension.SH_SERVER_NAME;
+import sun.security.ssl.SSLExtension.SSLExtensionSpec;
+import sun.security.ssl.SSLHandshake.HandshakeMessage;
 
-/*
- * [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;
+/**
+ * Pack of the "server_name" extensions [RFC 4366/6066].
  */
-final class ServerNameExtension extends HelloExtension {
+final class ServerNameExtension {
+    static final HandshakeProducer chNetworkProducer =
+            new CHServerNameProducer();
+    static final ExtensionConsumer chOnLoadConcumer =
+            new CHServerNameConsumer();
+    static final SSLStringize chStringize =
+            new CHServerNamesStringize();
+
+    static final HandshakeProducer shNetworkProducer =
+            new SHServerNameProducer();
+    static final ExtensionConsumer shOnLoadConcumer =
+            new SHServerNameConsumer();
+    static final SSLStringize shStringize =
+            new SHServerNamesStringize();
 
-    // For backward compatibility, all future data structures associated with
-    // new NameTypes MUST begin with a 16-bit length field.
-    static final 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);
+    static final HandshakeProducer eeNetworkProducer =
+            new EEServerNameProducer();
+    static final ExtensionConsumer eeOnLoadConcumer =
+            new EEServerNameConsumer();
 
-        listLength = 0;
-        sniMap = Collections.<Integer, SNIServerName>emptyMap();
-    }
-
-    // constructor for ClientHello
-    ServerNameExtension(List<SNIServerName> serverNames)
-            throws IOException {
-        super(ExtensionType.EXT_SERVER_NAME);
+    /**
+     * The "server_name" extension.
+     *
+     * See RFC 4366/6066 for the specification of the extension.
+     */
+    static final class CHServerNamesSpec implements SSLExtensionSpec {
+        // For backward compatibility, all future data structures associated
+        // with new NameTypes MUST begin with a 16-bit length field.
+        static final int NAME_HEADER_LENGTH = 3;    //  1: NameType
+                                                    // +2: Name length
+        final List<SNIServerName> serverNames;
 
-        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());
+        private CHServerNamesSpec(List<SNIServerName> serverNames) {
+            this.serverNames =
+                    Collections.<SNIServerName>unmodifiableList(serverNames);
+        }
+
+        private CHServerNamesSpec(ByteBuffer buffer) throws IOException {
+            if (buffer.remaining() < 2) {
+                throw new SSLProtocolException(
+                    "Invalid server_name extension: insufficient data");
             }
 
-            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) {
+            int sniLen = Record.getInt16(buffer);
+            if ((sniLen == 0) || sniLen != buffer.remaining()) {
                 throw new SSLProtocolException(
-                        "Invalid " + type + " extension");
+                    "Invalid server_name extension: incomplete data");
             }
 
-            remains -= 2;
-            sniMap = new LinkedHashMap<>();
-            while (remains > 0) {
-                int code = s.getInt8();       // NameType
+            Map<Integer, SNIServerName> sniMap = new LinkedHashMap<>();
+            while (buffer.hasRemaining()) {
+                int nameType = Record.getInt8(buffer);
+                SNIServerName serverName;
 
                 // 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;
-                        }
+                //
+                // [RFC 6066] The data structure associated with the host_name
+                // NameType is a variable-length vector that begins with a
+                // 16-bit length.  For backward compatibility, all future data
+                // structures associated with new NameTypes MUST begin with a
+                // 16-bit length field.  TLS MAY treat provided server names as
+                // opaque data and pass the names and types to the application.
+                byte[] encoded = Record.getBytes16(buffer);
+                if (nameType == StandardConstants.SNI_HOST_NAME) {
+                    if (encoded.length == 0) {
+                        throw new SSLProtocolException(
+                            "Empty HostName in server_name extension");
+                    }
+
+                    try {
+                        serverName = new SNIHostName(encoded);
+                    } catch (IllegalArgumentException iae) {
+                        SSLProtocolException spe = new SSLProtocolException(
+                            "Illegal server name, type=host_name(" +
+                            nameType + "), name=" +
+                            (new String(encoded, StandardCharsets.UTF_8)) +
+                            ", value={" +
+                            Utilities.toHexString(encoded) + "}");
+                        throw (SSLProtocolException)spe.initCause(iae);
+                    }
+                } else {
+                    try {
+                        serverName = new UnknownServerName(nameType, encoded);
+                    } catch (IllegalArgumentException iae) {
+                        SSLProtocolException spe = new SSLProtocolException(
+                            "Illegal server name, type=(" + nameType +
+                            "), value={" +
+                            Utilities.toHexString(encoded) + "}");
+                        throw (SSLProtocolException)spe.initCause(iae);
+                    }
                 }
+
                 // 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()));
+            this.serverNames = new ArrayList<>(sniMap.values());
         }
 
-        return Collections.<SNIServerName>emptyList();
-    }
+        @Override
+        public String toString() {
+            if (serverNames == null || serverNames.isEmpty()) {
+                return "<no server name indicator specified>";
+            } else {
+                StringBuilder builder = new StringBuilder(512);
+                for (SNIServerName sn : serverNames) {
+                    builder.append(sn.toString());
+                    builder.append("\n");
+                }
 
-    /*
-     * 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 builder.toString();
             }
         }
 
-        return true;
+        private static class UnknownServerName extends SNIServerName {
+            UnknownServerName(int code, byte[] encoded) {
+                super(code, encoded);
+            }
+        }
     }
 
-    /*
-     * 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
+    private static final class CHServerNamesStringize implements SSLStringize {
+        @Override
+        public String toString(ByteBuffer buffer) {
+            try {
+                return (new CHServerNamesSpec(buffer)).toString();
+            } catch (IOException ioe) {
+                // For debug logging only, so please swallow exceptions.
+                return ioe.getMessage();
             }
         }
     }
 
-    @Override
-    public String toString() {
-        StringBuilder sb = new StringBuilder();
-        for (SNIServerName sniName : sniMap.values()) {
-            sb.append("[" + sniName + "]");
+    /**
+     * Network data producer of a "server_name" extension in the
+     * ClientHello handshake message.
+     */
+    private static final
+            class CHServerNameProducer implements HandshakeProducer {
+        // Prevent instantiation of this class.
+        private CHServerNameProducer() {
+            // blank
+        }
+
+        @Override
+        public byte[] produce(ConnectionContext context,
+                HandshakeMessage message) throws IOException {
+            // The producing happens in client side only.
+            ClientHandshakeContext chc = (ClientHandshakeContext)context;
+
+            // Is it a supported and enabled extension?
+            if (!chc.sslConfig.isAvailable(CH_SERVER_NAME)) {
+                if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) {
+                    SSLLogger.warning(
+                        "Ignore unavailable server_name extension");
+                }
+                return null;
+            }
+
+            // Produce the extension.
+            List<SNIServerName> serverNames;
+            if (chc.isResumption && (chc.resumingSession != null)) {
+                serverNames =
+                        chc.resumingSession.getRequestedServerNames();
+            } else {
+                serverNames = chc.sslConfig.serverNames;
+            }   // Shall we use host too?
+
+            // Empty server name list is not allowed in client mode.
+            if ((serverNames != null) && !serverNames.isEmpty()) {
+                int sniLen = 0;
+                for (SNIServerName sniName : serverNames) {
+                    // For backward compatibility, all future data structures
+                    // associated with new NameTypes MUST begin with a 16-bit
+                    // length field.  The header length of server name is 3
+                    // bytes, including 1 byte NameType, and 2 bytes length
+                    // of the name.
+                    sniLen += CHServerNamesSpec.NAME_HEADER_LENGTH;
+                    sniLen += sniName.getEncoded().length;
+                }
+
+                byte[] extData = new byte[sniLen + 2];
+                ByteBuffer m = ByteBuffer.wrap(extData);
+                Record.putInt16(m, sniLen);
+                for (SNIServerName sniName : serverNames) {
+                    Record.putInt8(m, sniName.getType());
+                    Record.putBytes16(m, sniName.getEncoded());
+                }
+
+                // Update the context.
+                chc.requestedServerNames = serverNames;
+                chc.handshakeExtensions.put(CH_SERVER_NAME,
+                        new CHServerNamesSpec(serverNames));
+
+                return extData;
+            }
+
+            if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) {
+                SSLLogger.warning("Unable to indicate server name");
+            }
+            return null;
+        }
+    }
+
+    /**
+     * Network data consumer of a "server_name" extension in the
+     * ClientHello handshake message.
+     */
+    private static final
+            class CHServerNameConsumer implements ExtensionConsumer {
+        // Prevent instantiation of this class.
+        private CHServerNameConsumer() {
+            // blank
         }
 
-        return "Extension " + type + ", server_name: " + sb;
-    }
+        @Override
+        public void consume(ConnectionContext context,
+            HandshakeMessage message, ByteBuffer buffer) throws IOException {
+            // The comsuming happens in server side only.
+            ServerHandshakeContext shc = (ServerHandshakeContext)context;
+
+            // Is it a supported and enabled extension?
+            if (!shc.sslConfig.isAvailable(CH_SERVER_NAME)) {
+                if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) {
+                    SSLLogger.fine(
+                        "Ignore unavailable extension: " + CH_SERVER_NAME.name);
+                }
+                return;     // ignore the extension
+            }
+
+            // Parse the extension.
+            CHServerNamesSpec spec;
+            try {
+                spec = new CHServerNamesSpec(buffer);
+            } catch (IOException ioe) {
+                shc.conContext.fatal(Alert.UNEXPECTED_MESSAGE, ioe);
+                return;     // fatal() always throws, make the compiler happy.
+            }
+
+            // Update the context.
+            shc.handshakeExtensions.put(CH_SERVER_NAME, spec);
 
-    private static class UnknownServerName extends SNIServerName {
-        UnknownServerName(int code, byte[] encoded) {
-            super(code, encoded);
+            // Does the server match the server name request?
+            SNIServerName sni = null;
+            if (!shc.sslConfig.sniMatchers.isEmpty()) {
+                sni = chooseSni(shc.sslConfig.sniMatchers, spec.serverNames);
+                if (sni != null) {
+                    if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) {
+                        SSLLogger.fine(
+                                "server name indication (" +
+                                sni + ") is accepted");
+                    }
+                } else {
+                    // We do not reject client without SNI extension currently.
+                    shc.conContext.fatal(Alert.UNRECOGNIZED_NAME,
+                            "Unrecognized server name indication");
+                }
+            } else {
+                // Note: Servers MAY require clients to send a valid
+                // "server_name" extension and respond to a ClientHello
+                // lacking a "server_name" extension by terminating the
+                // connection with a "missing_extension" alert.
+                //
+                // We do not reject client without SNI extension currently.
+                if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) {
+                    SSLLogger.fine(
+                            "no server name matchers, " +
+                            "ignore server name indication");
+                }
+            }
+
+            // Impact on session resumption.
+            //
+            // Does the resuming session have the same principal?
+            if (shc.isResumption && shc.resumingSession != null) {
+                // A server that implements this extension MUST NOT accept
+                // the request to resume the session if the server_name
+                // extension contains a different name.
+                //
+                // May only need to check that the session SNI is one of
+                // the requested server names.
+                if (!Objects.equals(
+                        sni, shc.resumingSession.serverNameIndication)) {
+                    shc.isResumption = false;
+                    shc.resumingSession = null;
+                    if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) {
+                        SSLLogger.fine(
+                                "abort session resumption, " +
+                                "different server name indication used");
+                    }
+                }
+            }
+
+            shc.requestedServerNames = spec.serverNames;
+            shc.negotiatedServerName = sni;
+        }
+
+        private static SNIServerName chooseSni(Collection<SNIMatcher> matchers,
+                List<SNIServerName> sniNames) {
+            if (sniNames != null && !sniNames.isEmpty()) {
+                for (SNIMatcher matcher : matchers) {
+                    int matcherType = matcher.getType();
+                    for (SNIServerName sniName : sniNames) {
+                        if (sniName.getType() == matcherType) {
+                            if (matcher.matches(sniName)) {
+                                return sniName;
+                            }
+
+                            // no duplicated entry in the server names list.
+                            break;
+                        }
+                    }
+                }
+            }
+
+            return null;
         }
     }
 
+    /**
+     * The "server_name" extension in the ServerHello handshake message.
+     *
+     * The "extension_data" field of this extension shall be empty.
+     */
+    static final class SHServerNamesSpec implements SSLExtensionSpec {
+        static final SHServerNamesSpec DEFAULT = new SHServerNamesSpec();
+
+        private SHServerNamesSpec() {
+            // blank
+        }
+
+        private SHServerNamesSpec(ByteBuffer buffer) throws IOException {
+            if (buffer.remaining() != 0) {
+                throw new SSLProtocolException(
+                    "Invalid ServerHello server_name extension: not empty");
+            }
+        }
+
+        @Override
+        public String toString() {
+            return "<empty extension_data field>";
+        }
+    }
+
+    private static final class SHServerNamesStringize implements SSLStringize {
+        @Override
+        public String toString(ByteBuffer buffer) {
+            try {
+                return (new SHServerNamesSpec(buffer)).toString();
+            } catch (IOException ioe) {
+                // For debug logging only, so please swallow exceptions.
+                return ioe.getMessage();
+            }
+        }
+    }
+
+    /**
+     * Network data producer of a "server_name" extension in the
+     * ServerHello handshake message.
+     */
+    private static final
+            class SHServerNameProducer implements HandshakeProducer {
+        // Prevent instantiation of this class.
+        private SHServerNameProducer() {
+            // blank
+        }
+
+        @Override
+        public byte[] produce(ConnectionContext context,
+                HandshakeMessage message) throws IOException {
+            // The producing happens in server side only.
+            ServerHandshakeContext shc = (ServerHandshakeContext)context;
+
+            // In response to "server_name" extension request only
+            CHServerNamesSpec spec = (CHServerNamesSpec)
+                    shc.handshakeExtensions.get(CH_SERVER_NAME);
+            if (spec == null) {
+                if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) {
+                    SSLLogger.finest(
+                        "Ignore unavailable extension: " + SH_SERVER_NAME.name);
+                }
+                return null;        // ignore the extension
+            }
+
+            // When resuming a session, the server MUST NOT include a
+            // server_name extension in the server hello.
+            if (shc.isResumption || shc.negotiatedServerName == null) {
+                if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) {
+                    SSLLogger.finest(
+                        "No expected server name indication response");
+                }
+                return null;        // ignore the extension
+            }
+
+            // Produce the extension and update the context.
+            shc.handshakeExtensions.put(
+                    SH_SERVER_NAME, SHServerNamesSpec.DEFAULT);
+
+            return (new byte[0]);   // the empty extension_data
+        }
+    }
+
+    /**
+     * Network data consumer of a "server_name" extension in the
+     * ServerHello handshake message.
+     */
+    private static final
+            class SHServerNameConsumer implements ExtensionConsumer {
+        // Prevent instantiation of this class.
+        private SHServerNameConsumer() {
+            // blank
+        }
+
+        @Override
+        public void consume(ConnectionContext context,
+            HandshakeMessage message, ByteBuffer buffer) throws IOException {
+            // The comsuming happens in client side only.
+            ClientHandshakeContext chc = (ClientHandshakeContext)context;
+
+            // In response to "server_name" extension request only
+            CHServerNamesSpec spec = (CHServerNamesSpec)
+                    chc.handshakeExtensions.get(CH_SERVER_NAME);
+            if (spec == null) {
+                chc.conContext.fatal(Alert.UNEXPECTED_MESSAGE,
+                    "Unexpected ServerHello server_name extension");
+            }
+
+            // Parse the extension.
+            if (buffer.remaining() != 0) {
+                chc.conContext.fatal(Alert.UNEXPECTED_MESSAGE,
+                    "Invalid ServerHello server_name extension");
+            }
+
+            // Update the context.
+            chc.handshakeExtensions.put(
+                    SH_SERVER_NAME, SHServerNamesSpec.DEFAULT);
+            // The negotiated server name is unknown in client side. Just
+            // use the first request name as the value is not actually used
+            // in the current implementation.
+            chc.negotiatedServerName = spec.serverNames.get(0);
+        }
+    }
+
+    /**
+     * Network data producer of a "server_name" extension in the
+     * EncryptedExtensions handshake message.
+     */
+    private static final
+            class EEServerNameProducer implements HandshakeProducer {
+        // Prevent instantiation of this class.
+        private EEServerNameProducer() {
+            // blank
+        }
+
+        @Override
+        public byte[] produce(ConnectionContext context,
+                HandshakeMessage message) throws IOException {
+            // The producing happens in server side only.
+            ServerHandshakeContext shc = (ServerHandshakeContext)context;
+
+            // In response to "server_name" extension request only
+            CHServerNamesSpec spec = (CHServerNamesSpec)
+                    shc.handshakeExtensions.get(CH_SERVER_NAME);
+            if (spec == null) {
+                if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) {
+                    SSLLogger.finest(
+                        "Ignore unavailable extension: " + EE_SERVER_NAME.name);
+                }
+                return null;        // ignore the extension
+            }
+
+            // When resuming a session, the server MUST NOT include a
+            // server_name extension in the server hello.
+            if (shc.isResumption || shc.negotiatedServerName == null) {
+                if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) {
+                    SSLLogger.finest(
+                        "No expected server name indication response");
+                }
+                return null;        // ignore the extension
+            }
+
+            // Produce the extension and update the context.
+            shc.handshakeExtensions.put(
+                    EE_SERVER_NAME, SHServerNamesSpec.DEFAULT);
+
+            return (new byte[0]);   // the empty extension_data
+        }
+    }
+
+    /**
+     * Network data consumer of a "server_name" extension in the
+     * EncryptedExtensions handshake message.
+     */
+    private static final
+            class EEServerNameConsumer implements ExtensionConsumer {
+        // Prevent instantiation of this class.
+        private EEServerNameConsumer() {
+            // blank
+        }
+
+        @Override
+        public void consume(ConnectionContext context,
+            HandshakeMessage message, ByteBuffer buffer) throws IOException {
+            // The comsuming happens in client side only.
+            ClientHandshakeContext chc = (ClientHandshakeContext)context;
+
+            // In response to "server_name" extension request only
+            CHServerNamesSpec spec = (CHServerNamesSpec)
+                    chc.handshakeExtensions.get(CH_SERVER_NAME);
+            if (spec == null) {
+                chc.conContext.fatal(Alert.UNEXPECTED_MESSAGE,
+                    "Unexpected EncryptedExtensions server_name extension");
+            }
+
+            // Parse the extension.
+            if (buffer.remaining() != 0) {
+                chc.conContext.fatal(Alert.UNEXPECTED_MESSAGE,
+                    "Invalid EncryptedExtensions server_name extension");
+            }
+
+            // Update the context.
+            chc.handshakeExtensions.put(
+                    EE_SERVER_NAME, SHServerNamesSpec.DEFAULT);
+            // The negotiated server name is unknown in client side. Just
+            // use the first request name as the value is not actually used
+            // in the current implementation.
+            chc.negotiatedServerName = spec.serverNames.get(0);
+        }
+    }
 }