|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
|
|
package sun.security.ssl; |
|
|
|
import java.io.IOException; |
|
import java.nio.ByteBuffer; |
|
import java.nio.charset.StandardCharsets; |
|
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(); |
|
|
|
// Note: we reuse ServerHello operations for EncryptedExtensions for now. |
|
|
|
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 class AlpnSpec implements SSLExtensionSpec { |
|
final List<String> applicationProtocols; |
|
|
|
private AlpnSpec(String[] applicationProtocols) { |
|
this.applicationProtocols = Collections.unmodifiableList( |
|
Arrays.asList(applicationProtocols)); |
|
} |
|
|
|
private AlpnSpec(ByteBuffer buffer) throws IOException { |
|
|
|
if (buffer.remaining() < 2) { |
|
throw new SSLProtocolException( |
|
"Invalid application_layer_protocol_negotiation: " + |
|
"insufficient data (length=" + buffer.remaining() + ")"); |
|
} |
|
|
|
int listLen = Record.getInt16(buffer); |
|
if (listLen < 2 || listLen != buffer.remaining()) { |
|
throw 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 new SSLProtocolException( |
|
"Invalid application_layer_protocol_negotiation " + |
|
"extension: empty application protocol name"); |
|
} |
|
|
|
String appProtocol = new String(bytes, StandardCharsets.UTF_8); |
|
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(ByteBuffer buffer) { |
|
try { |
|
return (new AlpnSpec(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() { |
|
// blank |
|
} |
|
|
|
@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; |
|
} |
|
|
|
// Produce the extension. |
|
int listLength = 0; |
|
for (String ap : laps) { |
|
int length = ap.getBytes(StandardCharsets.UTF_8).length; |
|
if (length == 0) { |
|
|
|
if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { |
|
SSLLogger.severe( |
|
"Application protocol name cannot be empty"); |
|
} |
|
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)"); |
|
} |
|
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)"); |
|
} |
|
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(StandardCharsets.UTF_8)); |
|
} |
|
|
|
|
|
chc.handshakeExtensions.put(SSLExtension.CH_ALPN, |
|
new AlpnSpec(chc.sslConfig.applicationProtocols)); |
|
|
|
return extData; |
|
} |
|
} |
|
|
|
|
|
|
|
|
|
*/ |
|
private static final class CHAlpnConsumer implements ExtensionConsumer { |
|
|
|
private CHAlpnConsumer() { |
|
// blank |
|
} |
|
|
|
@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; |
|
try { |
|
spec = new AlpnSpec(buffer); |
|
} catch (IOException ioe) { |
|
shc.conContext.fatal(Alert.UNEXPECTED_MESSAGE, ioe); |
|
return; |
|
} |
|
|
|
|
|
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) { |
|
shc.conContext.fatal(Alert.NO_APPLICATION_PROTOCOL, |
|
"No matching application layer protocol values"); |
|
} |
|
} // Otherwise, applicationProtocol will be set by the |
|
// application selector callback later. |
|
|
|
shc.handshakeExtensions.put(SSLExtension.CH_ALPN, spec); |
|
|
|
// No impact on session resumption. |
|
// |
|
// [RFC 7301] Unlike many other TLS extensions, this extension |
|
// does not establish properties of the session, only of the |
|
// connection. When session resumption or session tickets are |
|
// used, the previous contents of this extension are irrelevant, |
|
// and only the values in the new handshake messages are |
|
// considered. |
|
} |
|
} |
|
|
|
|
|
|
|
|
|
*/ |
|
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() { |
|
// blank |
|
} |
|
|
|
@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))) { |
|
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))) { |
|
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; |
|
} |
|
|
|
|
|
int listLen = shc.applicationProtocol.length() + 1; |
|
// 1: length byte |
|
// ProtocolName protocol_name_list<2..2^16-1>, RFC 7301. |
|
byte[] extData = new byte[listLen + 2]; |
|
ByteBuffer m = ByteBuffer.wrap(extData); |
|
Record.putInt16(m, listLen); |
|
Record.putBytes8(m, |
|
shc.applicationProtocol.getBytes(StandardCharsets.UTF_8)); |
|
|
|
|
|
shc.conContext.applicationProtocol = shc.applicationProtocol; |
|
|
|
// Clean or register the extension |
|
// |
|
|
|
shc.handshakeExtensions.remove(SSLExtension.CH_ALPN); |
|
|
|
return extData; |
|
} |
|
} |
|
|
|
|
|
|
|
|
|
*/ |
|
private static final class SHAlpnConsumer implements ExtensionConsumer { |
|
|
|
private SHAlpnConsumer() { |
|
// blank |
|
} |
|
|
|
@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()) { |
|
chc.conContext.fatal(Alert.UNEXPECTED_MESSAGE, |
|
"Unexpected " + SSLExtension.CH_ALPN.name + " extension"); |
|
} |
|
|
|
|
|
AlpnSpec spec; |
|
try { |
|
spec = new AlpnSpec(buffer); |
|
} catch (IOException ioe) { |
|
chc.conContext.fatal(Alert.UNEXPECTED_MESSAGE, ioe); |
|
return; |
|
} |
|
|
|
|
|
if (spec.applicationProtocols.size() != 1) { |
|
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)) { |
|
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; |
|
|
|
// Clean or register the extension |
|
// |
|
|
|
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 = ""; |
|
} |
|
} |
|
} |