| 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 */  | 
 | 
 | 
 | 
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");  | 
 | 
                    }  | 
 | 
 | 
 | 
                    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(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) { | 
 | 
                throw shc.conContext.fatal(Alert.UNEXPECTED_MESSAGE, ioe);  | 
 | 
            }  | 
 | 
 | 
 | 
              | 
 | 
            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");  | 
 | 
                }  | 
 | 
            }   // 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))) { | 
 | 
                        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;  | 
 | 
            }  | 
 | 
 | 
 | 
              | 
 | 
            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()) { | 
 | 
                throw chc.conContext.fatal(Alert.UNEXPECTED_MESSAGE,  | 
 | 
                    "Unexpected " + SSLExtension.CH_ALPN.name + " extension");  | 
 | 
            }  | 
 | 
 | 
 | 
              | 
 | 
            AlpnSpec spec;  | 
 | 
            try { | 
 | 
                spec = new AlpnSpec(buffer);  | 
 | 
            } catch (IOException ioe) { | 
 | 
                throw chc.conContext.fatal(Alert.UNEXPECTED_MESSAGE, ioe);  | 
 | 
            }  | 
 | 
 | 
 | 
              | 
 | 
            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;  | 
 | 
 | 
 | 
            // 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 = "";  | 
 | 
        }  | 
 | 
    }  | 
 | 
}  |