|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
|
|
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; |
|
|
|
|
|
|
|
*/ |
|
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(); |
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
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; |
|
|
|
final List<SNIServerName> serverNames; |
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
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 |
|
|
|
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); |
|
} |
|
} |
|
|
|
|
|
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) { |
|
|
|
return ioe.getMessage(); |
|
} |
|
} |
|
} |
|
|
|
|
|
|
|
|
|
*/ |
|
private static final |
|
class CHServerNameProducer implements HandshakeProducer { |
|
|
|
private CHServerNameProducer() { |
|
// blank |
|
} |
|
|
|
@Override |
|
public byte[] produce(ConnectionContext context, |
|
HandshakeMessage message) throws IOException { |
|
|
|
ClientHandshakeContext chc = (ClientHandshakeContext)context; |
|
|
|
|
|
if (!chc.sslConfig.isAvailable(CH_SERVER_NAME)) { |
|
if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { |
|
SSLLogger.warning( |
|
"Ignore unavailable server_name extension"); |
|
} |
|
return null; |
|
} |
|
|
|
|
|
List<SNIServerName> serverNames; |
|
if (chc.isResumption && (chc.resumingSession != null)) { |
|
serverNames = |
|
chc.resumingSession.getRequestedServerNames(); |
|
} else { |
|
serverNames = chc.sslConfig.serverNames; |
|
} // Shall we use host too? |
|
|
|
|
|
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 |
|
|
|
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()); |
|
} |
|
|
|
|
|
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; |
|
} |
|
} |
|
|
|
|
|
|
|
|
|
*/ |
|
private static final |
|
class CHServerNameConsumer implements ExtensionConsumer { |
|
|
|
private CHServerNameConsumer() { |
|
// blank |
|
} |
|
|
|
@Override |
|
public void consume(ConnectionContext context, |
|
HandshakeMessage message, ByteBuffer buffer) throws IOException { |
|
|
|
ServerHandshakeContext shc = (ServerHandshakeContext)context; |
|
|
|
|
|
if (!shc.sslConfig.isAvailable(CH_SERVER_NAME)) { |
|
if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { |
|
SSLLogger.fine( |
|
"Ignore unavailable extension: " + CH_SERVER_NAME.name); |
|
} |
|
return; |
|
} |
|
|
|
|
|
CHServerNamesSpec spec; |
|
try { |
|
spec = new CHServerNamesSpec(buffer); |
|
} catch (IOException ioe) { |
|
throw shc.conContext.fatal(Alert.UNEXPECTED_MESSAGE, ioe); |
|
} |
|
|
|
|
|
shc.handshakeExtensions.put(CH_SERVER_NAME, spec); |
|
|
|
|
|
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 { |
|
|
|
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. |
|
// |
|
|
|
if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { |
|
SSLLogger.fine( |
|
"no server name matchers, " + |
|
"ignore server name indication"); |
|
} |
|
} |
|
|
|
// Impact on session resumption. |
|
// |
|
|
|
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 |
|
|
|
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; |
|
} |
|
|
|
|
|
break; |
|
} |
|
} |
|
} |
|
} |
|
|
|
return null; |
|
} |
|
} |
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
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) { |
|
|
|
return ioe.getMessage(); |
|
} |
|
} |
|
} |
|
|
|
|
|
|
|
|
|
*/ |
|
private static final |
|
class SHServerNameProducer implements HandshakeProducer { |
|
|
|
private SHServerNameProducer() { |
|
// blank |
|
} |
|
|
|
@Override |
|
public byte[] produce(ConnectionContext context, |
|
HandshakeMessage message) throws IOException { |
|
|
|
ServerHandshakeContext shc = (ServerHandshakeContext)context; |
|
|
|
|
|
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; |
|
} |
|
|
|
// When resuming a session, the server MUST NOT include a |
|
|
|
if (shc.isResumption || shc.negotiatedServerName == null) { |
|
if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { |
|
SSLLogger.finest( |
|
"No expected server name indication response"); |
|
} |
|
return null; |
|
} |
|
|
|
|
|
shc.handshakeExtensions.put( |
|
SH_SERVER_NAME, SHServerNamesSpec.DEFAULT); |
|
|
|
return (new byte[0]); |
|
} |
|
} |
|
|
|
|
|
|
|
|
|
*/ |
|
private static final |
|
class SHServerNameConsumer implements ExtensionConsumer { |
|
|
|
private SHServerNameConsumer() { |
|
// blank |
|
} |
|
|
|
@Override |
|
public void consume(ConnectionContext context, |
|
HandshakeMessage message, ByteBuffer buffer) throws IOException { |
|
|
|
ClientHandshakeContext chc = (ClientHandshakeContext)context; |
|
|
|
|
|
CHServerNamesSpec spec = (CHServerNamesSpec) |
|
chc.handshakeExtensions.get(CH_SERVER_NAME); |
|
if (spec == null) { |
|
throw chc.conContext.fatal(Alert.UNEXPECTED_MESSAGE, |
|
"Unexpected ServerHello server_name extension"); |
|
} |
|
|
|
|
|
if (buffer.remaining() != 0) { |
|
throw chc.conContext.fatal(Alert.UNEXPECTED_MESSAGE, |
|
"Invalid ServerHello server_name extension"); |
|
} |
|
|
|
|
|
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 |
|
|
|
chc.negotiatedServerName = spec.serverNames.get(0); |
|
} |
|
} |
|
|
|
|
|
|
|
|
|
*/ |
|
private static final |
|
class EEServerNameProducer implements HandshakeProducer { |
|
|
|
private EEServerNameProducer() { |
|
// blank |
|
} |
|
|
|
@Override |
|
public byte[] produce(ConnectionContext context, |
|
HandshakeMessage message) throws IOException { |
|
|
|
ServerHandshakeContext shc = (ServerHandshakeContext)context; |
|
|
|
|
|
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; |
|
} |
|
|
|
// When resuming a session, the server MUST NOT include a |
|
|
|
if (shc.isResumption || shc.negotiatedServerName == null) { |
|
if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { |
|
SSLLogger.finest( |
|
"No expected server name indication response"); |
|
} |
|
return null; |
|
} |
|
|
|
|
|
shc.handshakeExtensions.put( |
|
EE_SERVER_NAME, SHServerNamesSpec.DEFAULT); |
|
|
|
return (new byte[0]); |
|
} |
|
} |
|
|
|
|
|
|
|
|
|
*/ |
|
private static final |
|
class EEServerNameConsumer implements ExtensionConsumer { |
|
|
|
private EEServerNameConsumer() { |
|
// blank |
|
} |
|
|
|
@Override |
|
public void consume(ConnectionContext context, |
|
HandshakeMessage message, ByteBuffer buffer) throws IOException { |
|
|
|
ClientHandshakeContext chc = (ClientHandshakeContext)context; |
|
|
|
|
|
CHServerNamesSpec spec = (CHServerNamesSpec) |
|
chc.handshakeExtensions.get(CH_SERVER_NAME); |
|
if (spec == null) { |
|
throw chc.conContext.fatal(Alert.UNEXPECTED_MESSAGE, |
|
"Unexpected EncryptedExtensions server_name extension"); |
|
} |
|
|
|
|
|
if (buffer.remaining() != 0) { |
|
throw chc.conContext.fatal(Alert.UNEXPECTED_MESSAGE, |
|
"Invalid EncryptedExtensions server_name extension"); |
|
} |
|
|
|
|
|
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 |
|
|
|
chc.negotiatedServerName = spec.serverNames.get(0); |
|
} |
|
} |
|
} |