< prev index next >

src/java.base/share/classes/sun/security/ssl/AlpnExtension.java

Print this page

        

*** 1,7 **** /* ! * Copyright (c) 2015, 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 --- 1,7 ---- /* ! * 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
*** 24,168 **** */ package sun.security.ssl; import java.io.IOException; ! import java.nio.charset.*; ! import java.util.*; ! import javax.net.ssl.*; ! /* ! * [RFC 7301] ! * This TLS extension facilitates the negotiation of application-layer protocols ! * within the TLS handshake. Clients MAY include an extension of type ! * "application_layer_protocol_negotiation" in the (extended) ClientHello ! * message. The "extension_data" field of this extension SHALL contain a ! * "ProtocolNameList" value: ! * ! * enum { ! * application_layer_protocol_negotiation(16), (65535) ! * } ExtensionType; ! * ! * opaque ProtocolName<1..2^8-1>; * ! * struct { ! * ProtocolName protocol_name_list<2..2^16-1> ! * } ProtocolNameList; ! */ ! final class ALPNExtension extends HelloExtension { ! ! final static int ALPN_HEADER_LENGTH = 1; ! final static int MAX_APPLICATION_PROTOCOL_LENGTH = 255; ! final static int MAX_APPLICATION_PROTOCOL_LIST_LENGTH = 65535; ! private int listLength = 0; // ProtocolNameList length ! private List<String> protocolNames = null; ! ! // constructor for ServerHello ! ALPNExtension(String protocolName) throws SSLException { ! this(new String[]{ protocolName }); ! } ! ! // constructor for ClientHello ! ALPNExtension(String[] protocolNames) throws SSLException { ! super(ExtensionType.EXT_ALPN); ! if (protocolNames.length == 0) { // never null, never empty ! throw new IllegalArgumentException( ! "The list of application protocols cannot be empty"); ! } ! this.protocolNames = Arrays.asList(protocolNames); ! for (String p : protocolNames) { ! int length = p.getBytes(StandardCharsets.UTF_8).length; ! if (length == 0) { throw new SSLProtocolException( ! "Application protocol name is empty"); } ! if (length <= MAX_APPLICATION_PROTOCOL_LENGTH) { ! listLength += length + ALPN_HEADER_LENGTH; ! } else { throw new SSLProtocolException( ! "Application protocol name is too long: " + p); } ! if (listLength > MAX_APPLICATION_PROTOCOL_LIST_LENGTH) { throw new SSLProtocolException( ! "Application protocol name list is too long"); } } } ! // constructor for ServerHello for parsing ALPN extension ! ALPNExtension(HandshakeInStream s, int len) throws IOException { ! super(ExtensionType.EXT_ALPN); ! if (len >= 2) { ! listLength = s.getInt16(); // list length ! if (listLength < 2 || listLength + 2 != len) { ! throw new SSLProtocolException( ! "Invalid " + type + " extension: incorrect list length " + ! "(length=" + listLength + ")"); } - } else { - throw new SSLProtocolException( - "Invalid " + type + " extension: insufficient data " + - "(length=" + len + ")"); } ! int remaining = listLength; ! this.protocolNames = new ArrayList<>(); ! while (remaining > 0) { ! // opaque ProtocolName<1..2^8-1>; // RFC 7301 ! byte[] bytes = s.getBytes8(); ! if (bytes.length == 0) { ! throw new SSLProtocolException("Invalid " + type + ! " extension: empty application protocol name"); } ! String p = ! new String(bytes, StandardCharsets.UTF_8); // app protocol ! protocolNames.add(p); ! remaining -= bytes.length + ALPN_HEADER_LENGTH; } ! if (remaining != 0) { ! throw new SSLProtocolException( ! "Invalid " + type + " extension: extra data " + ! "(length=" + remaining + ")"); } } ! List<String> getPeerAPs() { ! return protocolNames; } ! /* ! * Return the length in bytes, including extension type and length fields. */ @Override ! int length() { ! return 6 + listLength; } @Override ! void send(HandshakeOutStream s) throws IOException { ! s.putInt16(type.id); ! s.putInt16(listLength + 2); // length of extension_data ! s.putInt16(listLength); // length of ProtocolNameList ! for (String p : protocolNames) { ! s.putBytes8(p.getBytes(StandardCharsets.UTF_8)); } } @Override ! public String toString() { ! StringBuilder sb = new StringBuilder(); ! if (protocolNames == null || protocolNames.isEmpty()) { ! sb.append("<empty>"); } else { ! for (String protocolName : protocolNames) { ! sb.append("[" + protocolName + "]"); } } ! return "Extension " + type + ! ", protocol names: " + sb; } } --- 24,496 ---- */ 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; ! /** ! * Pack of the "application_layer_protocol_negotiation" extensions [RFC 7301]. ! */ ! final class AlpnExtension { ! static final HandshakeProducer chNetworkProducer = new CHAlpnProducer(); ! static final ExtensionConsumer chOnLoadConcumer = new CHAlpnConsumer(); ! static final HandshakeAbsence chOnLoadAbsence = new CHAlpnAbsence(); ! ! static final HandshakeProducer shNetworkProducer = new SHAlpnProducer(); ! static final ExtensionConsumer shOnLoadConcumer = new SHAlpnConsumer(); ! static final HandshakeAbsence shOnLoadAbsence = new SHAlpnAbsence(); ! ! // Note: we reuse ServerHello operations for EncryptedExtensions for now. ! // Please be careful about any code or specification changes in the future. ! static final HandshakeProducer eeNetworkProducer = new SHAlpnProducer(); ! static final ExtensionConsumer eeOnLoadConcumer = new SHAlpnConsumer(); ! static final HandshakeAbsence eeOnLoadAbsence = new SHAlpnAbsence(); ! static final SSLStringize alpnStringize = new AlpnStringize(); ! ! /** ! * The "application_layer_protocol_negotiation" extension. * ! * See RFC 7301 for the specification of this extension. ! */ ! 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 { ! // ProtocolName protocol_name_list<2..2^16-1>, RFC 7301. ! 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()) { ! // opaque ProtocolName<1..2^8-1>, RFC 7301. ! 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 AlpnStringize implements SSLStringize { ! @Override ! public String toString(ByteBuffer buffer) { ! try { ! return (new AlpnSpec(buffer)).toString(); ! } catch (IOException ioe) { ! // For debug logging only, so please swallow exceptions. ! return ioe.getMessage(); ! } } } ! /** ! * Network data producer of the extension in a ClientHello ! * handshake message. ! */ ! private static final class CHAlpnProducer implements HandshakeProducer { ! static final int MAX_AP_LENGTH = 255; ! static final int MAX_AP_LIST_LENGTH = 65535; ! ! // Prevent instantiation of this class. ! private CHAlpnProducer() { ! // 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(SSLExtension.CH_ALPN)) { ! if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { ! SSLLogger.info( ! "Ignore client unavailable extension: " + ! SSLExtension.CH_ALPN.name); ! } ! 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; // ProtocolNameList length ! for (String ap : laps) { ! int length = ap.getBytes(StandardCharsets.UTF_8).length; ! if (length == 0) { ! // log the configuration problem ! 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) { ! // opaque ProtocolName<1..2^8-1>, RFC 7301. ! listLength += (length + 1); ! } else { ! // log the configuration problem ! 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) { ! // log the configuration problem ! 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)"); } } ! // ProtocolName protocol_name_list<2..2^16-1>, RFC 7301. ! 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)); } ! // Update the context. ! chc.handshakeExtensions.put(SSLExtension.CH_ALPN, ! new AlpnSpec(chc.sslConfig.applicationProtocols)); ! ! return extData; ! } ! } ! ! /** ! * Network data consumer of the extension in a ClientHello ! * handshake message. */ + private static final class CHAlpnConsumer implements ExtensionConsumer { + // Prevent instantiation of this class. + private CHAlpnConsumer() { + // blank + } + @Override ! public void consume(ConnectionContext context, ! HandshakeMessage message, ByteBuffer buffer) throws IOException { ! // The comsuming happens in server side only. ! ServerHandshakeContext shc = (ServerHandshakeContext)context; ! ! // Is it a supported and enabled extension? ! 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; // ignore the extension ! } ! ! // Is the extension enabled? ! 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; // ignore the extension + } + + // Parse the extension. + AlpnSpec spec; + try { + spec = new AlpnSpec(buffer); + } catch (IOException ioe) { + shc.conContext.fatal(Alert.UNEXPECTED_MESSAGE, ioe); + return; // fatal() always throws, make the compiler happy. + } + + // Update the context. + if (noAPSelector) { // noAlpnProtocols is false + List<String> protocolNames = spec.applicationProtocols; + boolean matched = false; + // Use server application protocol preference order. + 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. + } + } + + /** + * The absence processing if the extension is not present in + * a ClientHello handshake message. + */ + private static final class CHAlpnAbsence implements HandshakeAbsence { @Override ! public void absent(ConnectionContext context, ! HandshakeMessage message) throws IOException { ! // The producing happens in server side only. ! ServerHandshakeContext shc = (ServerHandshakeContext)context; ! // Please don't use the previous negotiated application protocol. ! shc.applicationProtocol = ""; ! shc.conContext.applicationProtocol = ""; } } + /** + * Network data producer of the extension in the ServerHello + * handshake message. + */ + private static final class SHAlpnProducer implements HandshakeProducer { + // Prevent instantiation of this class. + private SHAlpnProducer() { + // blank + } + @Override ! public byte[] produce(ConnectionContext context, ! HandshakeMessage message) throws IOException { ! // The producing happens in client side only. ! ServerHandshakeContext shc = (ServerHandshakeContext)context; ! ! // In response to ALPN request only ! AlpnSpec requestedAlps = ! (AlpnSpec)shc.handshakeExtensions.get(SSLExtension.CH_ALPN); ! if (requestedAlps == null) { ! // Ignore, this extension was not requested and accepted. ! if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { ! SSLLogger.fine( ! "Ignore unavailable extension: " + ! SSLExtension.SH_ALPN.name); ! } ! 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())) { ! // Ignore, no negotiated application layer protocol. ! shc.applicationProtocol = ""; ! shc.conContext.applicationProtocol = ""; ! if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { ! SSLLogger.warning( ! "Ignore, no negotiated application layer protocol"); ! } ! ! return null; } + + // opaque ProtocolName<1..2^8-1>, RFC 7301. + 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]; // 2: list length + ByteBuffer m = ByteBuffer.wrap(extData); + Record.putInt16(m, listLen); + Record.putBytes8(m, + shc.applicationProtocol.getBytes(StandardCharsets.UTF_8)); + + // Update the context. + shc.conContext.applicationProtocol = shc.applicationProtocol; + + // Clean or register the extension + // + // No further use of the request and respond extension any more. + shc.handshakeExtensions.remove(SSLExtension.CH_ALPN); + + return extData; + } + } + + /** + * Network data consumer of the extension in the ServerHello + * handshake message. + */ + private static final class SHAlpnConsumer implements ExtensionConsumer { + // Prevent instantiation of this class. + private SHAlpnConsumer() { + // blank + } + + @Override + public void consume(ConnectionContext context, + HandshakeMessage message, ByteBuffer buffer) throws IOException { + // The producing happens in client side only. + ClientHandshakeContext chc = (ClientHandshakeContext)context; + + // In response to ALPN request only + AlpnSpec requestedAlps = + (AlpnSpec)chc.handshakeExtensions.get(SSLExtension.CH_ALPN); + if (requestedAlps == null) { + chc.conContext.fatal(Alert.UNEXPECTED_MESSAGE, + "Unexpected " + SSLExtension.CH_ALPN.name + " extension"); } ! // Parse the extension. ! AlpnSpec spec; ! try { ! spec = new AlpnSpec(buffer); ! } catch (IOException ioe) { ! chc.conContext.fatal(Alert.UNEXPECTED_MESSAGE, ioe); ! return; // fatal() always throws, make the compiler happy. ! } ! ! // Only one application protocol is allowed. ! if (spec.applicationProtocols.size() != 1) { ! chc.conContext.fatal(Alert.UNEXPECTED_MESSAGE, ! "Invalid " + SSLExtension.CH_ALPN.name + " extension: " + ! "Only one protocol name is allowed in ServerHello message"); ! } ! ! // Update the context. ! chc.applicationProtocol = spec.applicationProtocols.get(0); ! chc.conContext.applicationProtocol = chc.applicationProtocol; ! ! // Clean or register the extension ! // ! // No further use of the request and respond extension any more. ! chc.handshakeExtensions.remove(SSLExtension.CH_ALPN); ! } ! } ! ! /** ! * The absence processing if the extension is not present in ! * the ServerHello handshake message. ! */ ! private static final class SHAlpnAbsence implements HandshakeAbsence { ! @Override ! public void absent(ConnectionContext context, ! HandshakeMessage message) throws IOException { ! // The producing happens in client side only. ! ClientHandshakeContext chc = (ClientHandshakeContext)context; ! ! // Please don't use the previous negotiated application protocol. ! chc.applicationProtocol = ""; ! chc.conContext.applicationProtocol = ""; ! } } }
< prev index next >