src/java.base/share/classes/sun/security/ssl/RenegoInfoExtension.java
changeset 50768 68fa3d4026ea
parent 47216 71c04702a3d5
child 53064 103ed9569fc8
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/java.base/share/classes/sun/security/ssl/RenegoInfoExtension.java	Mon Jun 25 13:41:39 2018 -0700
@@ -0,0 +1,558 @@
+/*
+ * 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.text.MessageFormat;
+import java.util.Arrays;
+import java.util.Locale;
+import javax.net.ssl.SSLProtocolException;
+import sun.security.ssl.ClientHello.ClientHelloMessage;
+import static sun.security.ssl.SSLExtension.CH_RENEGOTIATION_INFO;
+import sun.security.ssl.SSLExtension.ExtensionConsumer;
+import static sun.security.ssl.SSLExtension.SH_RENEGOTIATION_INFO;
+import sun.security.ssl.SSLExtension.SSLExtensionSpec;
+import sun.security.ssl.SSLHandshake.HandshakeMessage;
+
+/**
+ * Pack of the "renegotiation_info" extensions [RFC 5746].
+ */
+final class RenegoInfoExtension {
+    static final HandshakeProducer chNetworkProducer =
+            new CHRenegotiationInfoProducer();
+    static final ExtensionConsumer chOnLoadConsumer =
+            new CHRenegotiationInfoConsumer();
+    static final HandshakeAbsence chOnLoadAbsence =
+            new CHRenegotiationInfoAbsence();
+
+    static final HandshakeProducer shNetworkProducer =
+            new SHRenegotiationInfoProducer();
+    static final ExtensionConsumer shOnLoadConsumer =
+            new SHRenegotiationInfoConsumer();
+    static final HandshakeAbsence shOnLoadAbsence =
+            new SHRenegotiationInfoAbsence();
+
+    static final SSLStringizer rniStringizer =
+            new RenegotiationInfoStringizer();
+
+    /**
+     * The "renegotiation_info" extension.
+     */
+    static final class RenegotiationInfoSpec implements SSLExtensionSpec {
+        // A nominal object that does not holding any real renegotiation info.
+        static final RenegotiationInfoSpec NOMINAL =
+                new RenegotiationInfoSpec(new byte[0]);
+
+        private final byte[] renegotiatedConnection;
+
+        private RenegotiationInfoSpec(byte[] renegotiatedConnection) {
+            this.renegotiatedConnection = Arrays.copyOf(
+                    renegotiatedConnection, renegotiatedConnection.length);
+        }
+
+        private RenegotiationInfoSpec(ByteBuffer m) throws IOException {
+            // Parse the extension.
+            if (!m.hasRemaining() || m.remaining() < 1) {
+                throw new SSLProtocolException(
+                    "Invalid renegotiation_info extension data: " +
+                    "insufficient data");
+            }
+            this.renegotiatedConnection = Record.getBytes8(m);
+        }
+
+        @Override
+        public String toString() {
+            MessageFormat messageFormat = new MessageFormat(
+                "\"renegotiated connection\": '['{0}']'", Locale.ENGLISH);
+            if (renegotiatedConnection.length == 0) {
+                Object[] messageFields = {
+                        "<no renegotiated connection>"
+                    };
+                return messageFormat.format(messageFields);
+            } else {
+                Object[] messageFields = {
+                        Utilities.toHexString(renegotiatedConnection)
+                    };
+                return messageFormat.format(messageFields);
+            }
+        }
+    }
+
+    private static final
+            class RenegotiationInfoStringizer implements SSLStringizer {
+        @Override
+        public String toString(ByteBuffer buffer) {
+            try {
+                return (new RenegotiationInfoSpec(buffer)).toString();
+            } catch (IOException ioe) {
+                // For debug logging only, so please swallow exceptions.
+                return ioe.getMessage();
+            }
+        }
+    }
+
+    /**
+     * Network data producer of a "renegotiation_info" extension in
+     * the ClientHello handshake message.
+     */
+    private static final
+            class CHRenegotiationInfoProducer implements HandshakeProducer {
+        // Prevent instantiation of this class.
+        private CHRenegotiationInfoProducer() {
+            // 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(CH_RENEGOTIATION_INFO)) {
+                if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) {
+                    SSLLogger.fine(
+                            "Ignore unavailable renegotiation_info extension");
+                }
+
+                return null;
+            }
+
+            if (!chc.conContext.isNegotiated) {
+                if (chc.activeCipherSuites.contains(
+                        CipherSuite.TLS_EMPTY_RENEGOTIATION_INFO_SCSV)) {
+                    // Using the the TLS_EMPTY_RENEGOTIATION_INFO_SCSV instead.
+                    return null;
+                }
+
+                // initial handshaking.
+                //
+                // If this is the initial handshake for a connection, then the
+                // "renegotiated_connection" field is of zero length in both
+                // the ClientHello and the ServerHello. [RFC 5746]
+                byte[] extData = new byte[] { 0x00 };
+                chc.handshakeExtensions.put(
+                        CH_RENEGOTIATION_INFO, RenegotiationInfoSpec.NOMINAL);
+
+                return extData;
+            } else if (chc.conContext.secureRenegotiation) {
+                // secure renegotiation
+                //
+                // For ClientHello handshake message in renegotiation, this
+                // field contains the "client_verify_data".
+                byte[] extData =
+                        new byte[chc.conContext.clientVerifyData.length + 1];
+                ByteBuffer m = ByteBuffer.wrap(extData);
+                Record.putBytes8(m, chc.conContext.clientVerifyData);
+
+                // The conContext.clientVerifyData will be used for further
+                // processing, so it does not matter to save whatever in the
+                // RenegotiationInfoSpec object.
+                chc.handshakeExtensions.put(
+                        CH_RENEGOTIATION_INFO, RenegotiationInfoSpec.NOMINAL);
+
+                return extData;
+            } else {    // not secure renegotiation
+                if (HandshakeContext.allowUnsafeRenegotiation) {
+                    if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) {
+                        SSLLogger.warning("Using insecure renegotiation");
+                    }
+
+                    return null;
+                } else {
+                    // terminate the session.
+                    chc.conContext.fatal(Alert.HANDSHAKE_FAILURE,
+                            "insecure renegotiation is not allowed");
+                }
+            }
+
+            return null;
+        }
+    }
+
+    /**
+     * Network data producer of a "renegotiation_info" extension in
+     * the ServerHello handshake message.
+     */
+    private static final
+            class CHRenegotiationInfoConsumer implements ExtensionConsumer {
+        // Prevent instantiation of this class.
+        private CHRenegotiationInfoConsumer() {
+            // blank
+        }
+
+        @Override
+        public void consume(ConnectionContext context,
+            HandshakeMessage message, ByteBuffer buffer) throws IOException {
+
+            // The consuming happens in server side only.
+            ServerHandshakeContext shc = (ServerHandshakeContext)context;
+
+            // Is it a supported and enabled extension?
+            if (!shc.sslConfig.isAvailable(CH_RENEGOTIATION_INFO)) {
+                if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) {
+                    SSLLogger.fine("Ignore unavailable extension: " +
+                            CH_RENEGOTIATION_INFO.name);
+                }
+                return;     // ignore the extension
+            }
+
+            // Parse the extension.
+            RenegotiationInfoSpec spec;
+            try {
+                spec = new RenegotiationInfoSpec(buffer);
+            } catch (IOException ioe) {
+                shc.conContext.fatal(Alert.UNEXPECTED_MESSAGE, ioe);
+                return;     // fatal() always throws, make the compiler happy.
+            }
+
+            if (!shc.conContext.isNegotiated) {
+                // initial handshaking.
+                if (spec.renegotiatedConnection.length != 0) {
+                    shc.conContext.fatal(Alert.UNEXPECTED_MESSAGE,
+                        "Invalid renegotiation_info extension data: not empty");
+                }
+                shc.conContext.secureRenegotiation = true;
+            } else {
+                if (!shc.conContext.secureRenegotiation) {
+                    // Unexpected RI extension for insecure renegotiation,
+                    // abort the handshake with a fatal handshake_failure alert.
+                    shc.conContext.fatal(Alert.HANDSHAKE_FAILURE,
+                            "The renegotiation_info is present in a insecure " +
+                            "renegotiation");
+                } else {
+                    // verify the client_verify_data value
+                    if (!Arrays.equals(shc.conContext.clientVerifyData,
+                            spec.renegotiatedConnection)) {
+                        shc.conContext.fatal(Alert.UNEXPECTED_MESSAGE,
+                            "Invalid renegotiation_info extension data: " +
+                            "incorrect verify data in ClientHello");
+                    }
+                }
+            }
+
+            // Update the context.
+            //
+            // The conContext.clientVerifyData will be used for further
+            // processing, so it does not matter to save whatever in the
+            // RenegotiationInfoSpec object.
+            shc.handshakeExtensions.put(
+                    CH_RENEGOTIATION_INFO, RenegotiationInfoSpec.NOMINAL);
+
+            // No impact on session resumption.
+        }
+    }
+
+    /**
+     * The absence processing if a "renegotiation_info" extension is
+     * not present in the ClientHello handshake message.
+     */
+    private static final
+            class CHRenegotiationInfoAbsence implements HandshakeAbsence {
+        @Override
+        public void absent(ConnectionContext context,
+                HandshakeMessage message) throws IOException {
+            // The producing happens in server side only.
+            ServerHandshakeContext shc = (ServerHandshakeContext)context;
+            ClientHelloMessage clientHello = (ClientHelloMessage)message;
+
+            if (!shc.conContext.isNegotiated) {
+                // initial handshaking.
+                for (int id : clientHello.cipherSuiteIds) {
+                    if (id ==
+                            CipherSuite.TLS_EMPTY_RENEGOTIATION_INFO_SCSV.id) {
+                        if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) {
+                            SSLLogger.finest(
+                                "Safe renegotiation, using the SCSV signgling");
+                        }
+                        shc.conContext.secureRenegotiation = true;
+                        return;
+                    }
+                }
+
+                if (!HandshakeContext.allowLegacyHelloMessages) {
+                    shc.conContext.fatal(Alert.HANDSHAKE_FAILURE,
+                        "Failed to negotiate the use of secure renegotiation");
+                }   // otherwise, allow legacy hello message
+
+                if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) {
+                    SSLLogger.warning("Warning: No renegotiation " +
+                        "indication in ClientHello, allow legacy ClientHello");
+                }
+
+                shc.conContext.secureRenegotiation = false;
+            } else if (shc.conContext.secureRenegotiation) {
+                // Require secure renegotiation, terminate the connection.
+                shc.conContext.fatal(Alert.HANDSHAKE_FAILURE,
+                        "Inconsistent secure renegotiation indication");
+            } else {    // renegotiation, not secure
+                if (HandshakeContext.allowUnsafeRenegotiation) {
+                    if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) {
+                        SSLLogger.warning("Using insecure renegotiation");
+                    }
+                } else {
+                    // Unsafe renegotiation should have been aborted in
+                    // ealier processes.
+                    if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) {
+                        SSLLogger.fine("Terminate insecure renegotiation");
+                    }
+                    shc.conContext.fatal(Alert.HANDSHAKE_FAILURE,
+                        "Unsafe renegotiation is not allowed");
+                }
+            }
+        }
+    }
+
+    /**
+     * Network data producer of a "renegotiation_info" extension in
+     * the ServerHello handshake message.
+     */
+    private static final
+            class SHRenegotiationInfoProducer implements HandshakeProducer {
+        // Prevent instantiation of this class.
+        private SHRenegotiationInfoProducer() {
+            // blank
+        }
+
+        @Override
+        public byte[] produce(ConnectionContext context,
+                HandshakeMessage message) throws IOException {
+            // The producing happens in server side only.
+            ServerHandshakeContext shc = (ServerHandshakeContext)context;
+
+            // In response to "renegotiation_info" extension request only.
+            RenegotiationInfoSpec requestedSpec = (RenegotiationInfoSpec)
+                    shc.handshakeExtensions.get(CH_RENEGOTIATION_INFO);
+            if (requestedSpec == null && !shc.conContext.secureRenegotiation) {
+                // Ignore, no renegotiation_info extension or SCSV signgling
+                // requested.
+                if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) {
+                    SSLLogger.finest(
+                        "Ignore unavailable renegotiation_info extension");
+                }
+                return null;        // ignore the extension
+            }
+
+            if (!shc.conContext.secureRenegotiation) {
+                // Ignore, no secure renegotiation is negotiated.
+                if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) {
+                    SSLLogger.finest(
+                        "No secure renegotiation has been negotiated");
+                }
+                return null;        // ignore the extension
+            }
+
+            if (!shc.conContext.isNegotiated) {
+                // initial handshaking.
+                //
+                // If this is the initial handshake for a connection, then the
+                // "renegotiated_connection" field is of zero length in both
+                // the ClientHello and the ServerHello. [RFC 5746]
+                byte[] extData = new byte[] { 0x00 };
+
+                // The conContext.client/serverVerifyData will be used for
+                // further processing, so it does not matter to save whatever
+                // in the RenegotiationInfoSpec object.
+                shc.handshakeExtensions.put(
+                        SH_RENEGOTIATION_INFO, RenegotiationInfoSpec.NOMINAL);
+
+                return extData;
+            } else {
+                // secure renegotiation
+                //
+                // For secure renegotiation, the server MUST include a
+                // "renegotiation_info" extension containing the saved
+                // client_verify_data and server_verify_data in the ServerHello.
+                int infoLen = shc.conContext.clientVerifyData.length +
+                              shc.conContext.serverVerifyData.length;
+                byte[] extData = new byte[infoLen + 1];
+                ByteBuffer m = ByteBuffer.wrap(extData);
+                Record.putInt8(m, infoLen);
+                m.put(shc.conContext.clientVerifyData);
+                m.put(shc.conContext.serverVerifyData);
+
+                // The conContext.client/serverVerifyData will be used for
+                // further processing, so it does not matter to save whatever
+                // in the RenegotiationInfoSpec object.
+                shc.handshakeExtensions.put(
+                        SH_RENEGOTIATION_INFO, RenegotiationInfoSpec.NOMINAL);
+
+                return extData;
+            }
+        }
+    }
+
+    /**
+     * Network data consumer of a "renegotiation_info" extension in
+     * the ServerHello handshake message.
+     */
+    private static final
+            class SHRenegotiationInfoConsumer implements ExtensionConsumer {
+        // Prevent instantiation of this class.
+        private SHRenegotiationInfoConsumer() {
+            // 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 the client renegotiation_info extension request
+            // or SCSV signling, which is mandatory for ClientHello message.
+            RenegotiationInfoSpec requestedSpec = (RenegotiationInfoSpec)
+                    chc.handshakeExtensions.get(CH_RENEGOTIATION_INFO);
+            if (requestedSpec == null &&
+                    !chc.activeCipherSuites.contains(
+                            CipherSuite.TLS_EMPTY_RENEGOTIATION_INFO_SCSV)) {
+                chc.conContext.fatal(Alert.INTERNAL_ERROR,
+                    "Missing renegotiation_info and SCSV detected in " +
+                    "ClientHello");
+            }
+
+            // Parse the extension.
+            RenegotiationInfoSpec spec;
+            try {
+                spec = new RenegotiationInfoSpec(buffer);
+            } catch (IOException ioe) {
+                chc.conContext.fatal(Alert.UNEXPECTED_MESSAGE, ioe);
+                return;     // fatal() always throws, make the compiler happy.
+            }
+
+
+            if (!chc.conContext.isNegotiated) {     // initial handshake
+                // If the extension is present, set the secure_renegotiation
+                // flag to TRUE.  The client MUST then verify that the
+                // length of the "renegotiated_connection" field is zero,
+                // and if it is not, MUST abort the handshake (by sending
+                // a fatal handshake_failure alert). [RFC 5746]
+                if (spec.renegotiatedConnection.length != 0) {
+                    chc.conContext.fatal(Alert.HANDSHAKE_FAILURE,
+                        "Invalid renegotiation_info in ServerHello: " +
+                        "not empty renegotiated_connection");
+                }
+
+                chc.conContext.secureRenegotiation = true;
+            } else {        // renegotiation
+                // The client MUST then verify that the first half of the
+                // "renegotiated_connection" field is equal to the saved
+                // client_verify_data value, and the second half is equal to the
+                // saved server_verify_data value.  If they are not, the client
+                // MUST abort the handshake. [RFC 5746]
+                int infoLen = chc.conContext.clientVerifyData.length +
+                              chc.conContext.serverVerifyData.length;
+                if (spec.renegotiatedConnection.length != infoLen) {
+                    chc.conContext.fatal(Alert.HANDSHAKE_FAILURE,
+                        "Invalid renegotiation_info in ServerHello: " +
+                        "invalid renegotiated_connection length (" +
+                        spec.renegotiatedConnection.length + ")");
+                }
+
+                byte[] cvd = chc.conContext.clientVerifyData;
+                if (!Arrays.equals(spec.renegotiatedConnection,
+                        0, cvd.length, cvd, 0, cvd.length)) {
+                    chc.conContext.fatal(Alert.HANDSHAKE_FAILURE,
+                        "Invalid renegotiation_info in ServerHello: " +
+                        "unmatched client_verify_data value");
+                }
+                byte[] svd = chc.conContext.serverVerifyData;
+                if (!Arrays.equals(spec.renegotiatedConnection,
+                        cvd.length, infoLen, svd, 0, svd.length)) {
+                    chc.conContext.fatal(Alert.HANDSHAKE_FAILURE,
+                        "Invalid renegotiation_info in ServerHello: " +
+                        "unmatched server_verify_data value");
+                }
+            }
+
+            // Update the context.
+            chc.handshakeExtensions.put(
+                    SH_RENEGOTIATION_INFO, RenegotiationInfoSpec.NOMINAL);
+
+            // No impact on session resumption.
+        }
+    }
+
+    /**
+     * The absence processing if a "renegotiation_info" extension is
+     * not present in the ServerHello handshake message.
+     */
+    private static final
+            class SHRenegotiationInfoAbsence implements HandshakeAbsence {
+        @Override
+        public void absent(ConnectionContext context,
+                HandshakeMessage message) throws IOException {
+            // The producing happens in client side only.
+            ClientHandshakeContext chc = (ClientHandshakeContext)context;
+
+            // In response to the client renegotiation_info extension request
+            // or SCSV signling, which is mandatory for ClientHello message.
+            RenegotiationInfoSpec requestedSpec = (RenegotiationInfoSpec)
+                    chc.handshakeExtensions.get(CH_RENEGOTIATION_INFO);
+            if (requestedSpec == null &&
+                    !chc.activeCipherSuites.contains(
+                            CipherSuite.TLS_EMPTY_RENEGOTIATION_INFO_SCSV)) {
+                chc.conContext.fatal(Alert.INTERNAL_ERROR,
+                    "Missing renegotiation_info and SCSV detected in " +
+                    "ClientHello");
+            }
+
+            if (!chc.conContext.isNegotiated) {
+                // initial handshaking.
+                if (!HandshakeContext.allowLegacyHelloMessages) {
+                    chc.conContext.fatal(Alert.HANDSHAKE_FAILURE,
+                        "Failed to negotiate the use of secure renegotiation");
+                }   // otherwise, allow legacy hello message
+
+                if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) {
+                    SSLLogger.warning("Warning: No renegotiation " +
+                        "indication in ServerHello, allow legacy ServerHello");
+                }
+
+                chc.conContext.secureRenegotiation = false;
+            } else if (chc.conContext.secureRenegotiation) {
+                // Require secure renegotiation, terminate the connection.
+                chc.conContext.fatal(Alert.HANDSHAKE_FAILURE,
+                        "Inconsistent secure renegotiation indication");
+            } else {    // renegotiation, not secure
+                if (HandshakeContext.allowUnsafeRenegotiation) {
+                    if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) {
+                        SSLLogger.warning("Using insecure renegotiation");
+                    }
+                } else {
+                    // Unsafe renegotiation should have been aborted in
+                    // ealier processes.
+                    if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) {
+                        SSLLogger.fine("Terminate insecure renegotiation");
+                    }
+                    chc.conContext.fatal(Alert.HANDSHAKE_FAILURE,
+                        "Unsafe renegotiation is not allowed");
+                }
+            }
+        }
+    }
+}