blob: 9859bf64c3356f4504363fc89d97a50acc6e763b [file] [log] [blame]
/*
* 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].
*/
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.
*/
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.
*/
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
}
@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.
*/
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.
*/
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 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.
*/
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 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);
}
}
}