/*
 * 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
 * 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.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.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;

Pack of the "server_name" extensions [RFC 4366/6066].
/** * Pack of the "server_name" extensions [RFC 4366/6066]. */
final class ServerNameExtension { static final HandshakeProducer chNetworkProducer = new CHServerNameProducer(); static final ExtensionConsumer chOnLoadConsumer = new CHServerNameConsumer(); static final SSLStringizer chStringizer = new CHServerNamesStringizer(); static final HandshakeProducer shNetworkProducer = new SHServerNameProducer(); static final ExtensionConsumer shOnLoadConsumer = new SHServerNameConsumer(); static final SSLStringizer shStringizer = new SHServerNamesStringizer(); static final HandshakeProducer eeNetworkProducer = new EEServerNameProducer(); static final ExtensionConsumer eeOnLoadConsumer = new EEServerNameConsumer();
The "server_name" extension. See RFC 4366/6066 for the specification of the extension.
/** * 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; /* * Note: For the unmodifiable collection we are creating new * collections as inputs to avoid potential deep nesting of * unmodifiable collections that can cause StackOverflowErrors * (see JDK-6323374). */ private CHServerNamesSpec(List<SNIServerName> serverNames) { this.serverNames = Collections.<SNIServerName>unmodifiableList( new ArrayList<>(serverNames)); } private CHServerNamesSpec(ByteBuffer buffer) throws IOException { if (buffer.remaining() < 2) { throw new SSLProtocolException( "Invalid server_name extension: insufficient data"); } int sniLen = Record.getInt16(buffer); if ((sniLen == 0) || sniLen != buffer.remaining()) { throw new SSLProtocolException( "Invalid server_name extension: incomplete data"); } Map<Integer, SNIServerName> sniMap = new LinkedHashMap<>(); while (buffer.hasRemaining()) { int nameType = Record.getInt8(buffer); SNIServerName serverName; // HostName (length read in getBytes16); // // [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()); } } this.serverNames = new ArrayList<>(sniMap.values()); } @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"); } return builder.toString(); } } private static class UnknownServerName extends SNIServerName { UnknownServerName(int code, byte[] encoded) { super(code, encoded); } } } private static final class CHServerNamesStringizer implements SSLStringizer { @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(); } } }
Network data producer of a "server_name" extension in the ClientHello handshake message.
/** * 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.
/** * 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 } @Override public void consume(ConnectionContext context, HandshakeMessage message, ByteBuffer buffer) throws IOException { // The consuming 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) { throw shc.conContext.fatal(Alert.UNEXPECTED_MESSAGE, ioe); } // Update the context. shc.handshakeExtensions.put(CH_SERVER_NAME, spec); // 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. throw 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.
/** * 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 SHServerNamesStringizer implements SSLStringizer { @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.
/** * 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.
/** * 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 consuming 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) { throw chc.conContext.fatal(Alert.UNEXPECTED_MESSAGE, "Unexpected ServerHello server_name extension"); } // Parse the extension. if (buffer.remaining() != 0) { throw 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.
/** * 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.
/** * 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 consuming 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) { throw chc.conContext.fatal(Alert.UNEXPECTED_MESSAGE, "Unexpected EncryptedExtensions server_name extension"); } // Parse the extension. if (buffer.remaining() != 0) { throw 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); } } }