package sun.security.ssl;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.charset.Charset;
import java.security.AccessController;
import java.security.PrivilegedAction;
import java.security.Security;
import java.util.Arrays;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import javax.net.ssl.SSLEngine;
import javax.net.ssl.SSLProtocolException;
import javax.net.ssl.SSLSocket;
import sun.security.ssl.SSLExtension.ExtensionConsumer;
import sun.security.ssl.SSLExtension.SSLExtensionSpec;
import sun.security.ssl.SSLHandshake.HandshakeMessage;
final class AlpnExtension {
static final HandshakeProducer chNetworkProducer = new CHAlpnProducer();
static final ExtensionConsumer chOnLoadConsumer = new CHAlpnConsumer();
static final HandshakeAbsence chOnLoadAbsence = new CHAlpnAbsence();
static final HandshakeProducer shNetworkProducer = new SHAlpnProducer();
static final ExtensionConsumer shOnLoadConsumer = new SHAlpnConsumer();
static final HandshakeAbsence shOnLoadAbsence = new SHAlpnAbsence();
static final HandshakeProducer eeNetworkProducer = new SHAlpnProducer();
static final ExtensionConsumer eeOnLoadConsumer = new SHAlpnConsumer();
static final HandshakeAbsence eeOnLoadAbsence = new SHAlpnAbsence();
static final SSLStringizer alpnStringizer = new AlpnStringizer();
static final Charset alpnCharset;
static {
String alpnCharsetString = AccessController.doPrivileged(
(PrivilegedAction<String>) ()
-> Security.getProperty("jdk.tls.alpnCharset"));
if ((alpnCharsetString == null)
|| (alpnCharsetString.length() == 0)) {
alpnCharsetString = "ISO_8859_1";
}
alpnCharset = Charset.forName(alpnCharsetString);
}
static final class AlpnSpec implements SSLExtensionSpec {
final List<String> applicationProtocols;
private AlpnSpec(String[] applicationProtocols) {
this.applicationProtocols = Collections.unmodifiableList(
Arrays.asList(applicationProtocols));
}
private AlpnSpec(HandshakeContext hc,
ByteBuffer buffer) throws IOException {
if (buffer.remaining() < 2) {
throw hc.conContext.fatal(Alert.DECODE_ERROR,
new SSLProtocolException(
"Invalid application_layer_protocol_negotiation: " +
"insufficient data (length=" + buffer.remaining() + ")"));
}
int listLen = Record.getInt16(buffer);
if (listLen < 2 || listLen != buffer.remaining()) {
throw hc.conContext.fatal(Alert.DECODE_ERROR,
new SSLProtocolException(
"Invalid application_layer_protocol_negotiation: " +
"incorrect list length (length=" + listLen + ")"));
}
List<String> protocolNames = new LinkedList<>();
while (buffer.hasRemaining()) {
byte[] bytes = Record.getBytes8(buffer);
if (bytes.length == 0) {
throw hc.conContext.fatal(Alert.DECODE_ERROR,
new SSLProtocolException(
"Invalid application_layer_protocol_negotiation " +
"extension: empty application protocol name"));
}
String appProtocol = new String(bytes, alpnCharset);
protocolNames.add(appProtocol);
}
this.applicationProtocols =
Collections.unmodifiableList(protocolNames);
}
@Override
public String toString() {
return applicationProtocols.toString();
}
}
private static final class AlpnStringizer implements SSLStringizer {
@Override
public String toString(HandshakeContext hc, ByteBuffer buffer) {
try {
return (new AlpnSpec(hc, buffer)).toString();
} catch (IOException ioe) {
return ioe.getMessage();
}
}
}
private static final class CHAlpnProducer implements HandshakeProducer {
static final int MAX_AP_LENGTH = 255;
static final int MAX_AP_LIST_LENGTH = 65535;
private CHAlpnProducer() {
}
@Override
public byte[] produce(ConnectionContext context,
HandshakeMessage message) throws IOException {
ClientHandshakeContext chc = (ClientHandshakeContext)context;
if (!chc.sslConfig.isAvailable(SSLExtension.CH_ALPN)) {
if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) {
SSLLogger.info(
"Ignore client unavailable extension: " +
SSLExtension.CH_ALPN.name);
}
chc.applicationProtocol = "";
chc.conContext.applicationProtocol = "";
return null;
}
String[] laps = chc.sslConfig.applicationProtocols;
if ((laps == null) || (laps.length == 0)) {
if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) {
SSLLogger.info(
"No available application protocols");
}
return null;
}
int listLength = 0;
for (String ap : laps) {
int length = ap.getBytes(alpnCharset).length;
if (length == 0) {
if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) {
SSLLogger.severe(
"Application protocol name cannot be empty");
}
throw chc.conContext.fatal(Alert.ILLEGAL_PARAMETER,
"Application protocol name cannot be empty");
}
if (length <= MAX_AP_LENGTH) {
listLength += (length + 1);
} else {
if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) {
SSLLogger.severe(
"Application protocol name (" + ap +
") exceeds the size limit (" +
MAX_AP_LENGTH + " bytes)");
}
throw chc.conContext.fatal(Alert.ILLEGAL_PARAMETER,
"Application protocol name (" + ap +
") exceeds the size limit (" +
MAX_AP_LENGTH + " bytes)");
}
if (listLength > MAX_AP_LIST_LENGTH) {
if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) {
SSLLogger.severe(
"The configured application protocols (" +
Arrays.toString(laps) +
") exceed the size limit (" +
MAX_AP_LIST_LENGTH + " bytes)");
}
throw chc.conContext.fatal(Alert.ILLEGAL_PARAMETER,
"The configured application protocols (" +
Arrays.toString(laps) +
") exceed the size limit (" +
MAX_AP_LIST_LENGTH + " bytes)");
}
}
byte[] extData = new byte[listLength + 2];
ByteBuffer m = ByteBuffer.wrap(extData);
Record.putInt16(m, listLength);
for (String ap : laps) {
Record.putBytes8(m, ap.getBytes(alpnCharset));
}
chc.handshakeExtensions.put(SSLExtension.CH_ALPN,
new AlpnSpec(chc.sslConfig.applicationProtocols));
return extData;
}
}
private static final class CHAlpnConsumer implements ExtensionConsumer {
private CHAlpnConsumer() {
}
@Override
public void consume(ConnectionContext context,
HandshakeMessage message, ByteBuffer buffer) throws IOException {
ServerHandshakeContext shc = (ServerHandshakeContext)context;
if (!shc.sslConfig.isAvailable(SSLExtension.CH_ALPN)) {
shc.applicationProtocol = "";
shc.conContext.applicationProtocol = "";
if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) {
SSLLogger.info(
"Ignore server unavailable extension: " +
SSLExtension.CH_ALPN.name);
}
return;
}
boolean noAPSelector;
if (shc.conContext.transport instanceof SSLEngine) {
noAPSelector = (shc.sslConfig.engineAPSelector == null);
} else {
noAPSelector = (shc.sslConfig.socketAPSelector == null);
}
boolean noAlpnProtocols =
shc.sslConfig.applicationProtocols == null ||
shc.sslConfig.applicationProtocols.length == 0;
if (noAPSelector && noAlpnProtocols) {
shc.applicationProtocol = "";
shc.conContext.applicationProtocol = "";
if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) {
SSLLogger.fine(
"Ignore server unenabled extension: " +
SSLExtension.CH_ALPN.name);
}
return;
}
AlpnSpec spec = new AlpnSpec(shc, buffer);
if (noAPSelector) {
List<String> protocolNames = spec.applicationProtocols;
boolean matched = false;
for (String ap : shc.sslConfig.applicationProtocols) {
if (protocolNames.contains(ap)) {
shc.applicationProtocol = ap;
shc.conContext.applicationProtocol = ap;
matched = true;
break;
}
}
if (!matched) {
throw shc.conContext.fatal(Alert.NO_APPLICATION_PROTOCOL,
"No matching application layer protocol values");
}
}
shc.handshakeExtensions.put(SSLExtension.CH_ALPN, spec);
}
}
private static final class CHAlpnAbsence implements HandshakeAbsence {
@Override
public void absent(ConnectionContext context,
HandshakeMessage message) throws IOException {
ServerHandshakeContext shc = (ServerHandshakeContext)context;
shc.applicationProtocol = "";
shc.conContext.applicationProtocol = "";
}
}
private static final class SHAlpnProducer implements HandshakeProducer {
private SHAlpnProducer() {
}
@Override
public byte[] produce(ConnectionContext context,
HandshakeMessage message) throws IOException {
ServerHandshakeContext shc = (ServerHandshakeContext)context;
AlpnSpec requestedAlps =
(AlpnSpec)shc.handshakeExtensions.get(SSLExtension.CH_ALPN);
if (requestedAlps == null) {
if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) {
SSLLogger.fine(
"Ignore unavailable extension: " +
SSLExtension.SH_ALPN.name);
}
shc.applicationProtocol = "";
shc.conContext.applicationProtocol = "";
return null;
}
List<String> alps = requestedAlps.applicationProtocols;
if (shc.conContext.transport instanceof SSLEngine) {
if (shc.sslConfig.engineAPSelector != null) {
SSLEngine engine = (SSLEngine)shc.conContext.transport;
shc.applicationProtocol =
shc.sslConfig.engineAPSelector.apply(engine, alps);
if ((shc.applicationProtocol == null) ||
(!shc.applicationProtocol.isEmpty() &&
!alps.contains(shc.applicationProtocol))) {
throw shc.conContext.fatal(
Alert.NO_APPLICATION_PROTOCOL,
"No matching application layer protocol values");
}
}
} else {
if (shc.sslConfig.socketAPSelector != null) {
SSLSocket socket = (SSLSocket)shc.conContext.transport;
shc.applicationProtocol =
shc.sslConfig.socketAPSelector.apply(socket, alps);
if ((shc.applicationProtocol == null) ||
(!shc.applicationProtocol.isEmpty() &&
!alps.contains(shc.applicationProtocol))) {
throw shc.conContext.fatal(
Alert.NO_APPLICATION_PROTOCOL,
"No matching application layer protocol values");
}
}
}
if ((shc.applicationProtocol == null) ||
(shc.applicationProtocol.isEmpty())) {
shc.applicationProtocol = "";
shc.conContext.applicationProtocol = "";
if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) {
SSLLogger.warning(
"Ignore, no negotiated application layer protocol");
}
return null;
}
byte[] bytes = shc.applicationProtocol.getBytes(alpnCharset);
int listLen = bytes.length + 1;
byte[] extData = new byte[listLen + 2];
ByteBuffer m = ByteBuffer.wrap(extData);
Record.putInt16(m, listLen);
Record.putBytes8(m, bytes);
shc.conContext.applicationProtocol = shc.applicationProtocol;
shc.handshakeExtensions.remove(SSLExtension.CH_ALPN);
return extData;
}
}
private static final class SHAlpnConsumer implements ExtensionConsumer {
private SHAlpnConsumer() {
}
@Override
public void consume(ConnectionContext context,
HandshakeMessage message, ByteBuffer buffer) throws IOException {
ClientHandshakeContext chc = (ClientHandshakeContext)context;
AlpnSpec requestedAlps =
(AlpnSpec)chc.handshakeExtensions.get(SSLExtension.CH_ALPN);
if (requestedAlps == null ||
requestedAlps.applicationProtocols == null ||
requestedAlps.applicationProtocols.isEmpty()) {
throw chc.conContext.fatal(Alert.UNEXPECTED_MESSAGE,
"Unexpected " + SSLExtension.CH_ALPN.name + " extension");
}
AlpnSpec spec = new AlpnSpec(chc, buffer);
if (spec.applicationProtocols.size() != 1) {
throw chc.conContext.fatal(Alert.UNEXPECTED_MESSAGE,
"Invalid " + SSLExtension.CH_ALPN.name + " extension: " +
"Only one application protocol name " +
"is allowed in ServerHello message");
}
if (!requestedAlps.applicationProtocols.containsAll(
spec.applicationProtocols)) {
throw chc.conContext.fatal(Alert.UNEXPECTED_MESSAGE,
"Invalid " + SSLExtension.CH_ALPN.name + " extension: " +
"Only client specified application protocol " +
"is allowed in ServerHello message");
}
chc.applicationProtocol = spec.applicationProtocols.get(0);
chc.conContext.applicationProtocol = chc.applicationProtocol;
chc.handshakeExtensions.remove(SSLExtension.CH_ALPN);
}
}
private static final class SHAlpnAbsence implements HandshakeAbsence {
@Override
public void absent(ConnectionContext context,
HandshakeMessage message) throws IOException {
ClientHandshakeContext chc = (ClientHandshakeContext)context;
chc.applicationProtocol = "";
chc.conContext.applicationProtocol = "";
}
}
}