src/java.base/share/classes/sun/security/ssl/AlpnExtension.java
branchJDK-8145252-TLS13-branch
changeset 56542 56aaa6cb3693
parent 47216 71c04702a3d5
child 56566 a06a7dece503
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/java.base/share/classes/sun/security/ssl/AlpnExtension.java	Fri May 11 15:53:12 2018 -0700
@@ -0,0 +1,496 @@
+/*
+ * 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
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+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 = "";
+        }
+    }
+}