/*
 * 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].
/** * 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.
/** * 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.
/** * 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. throw chc.conContext.fatal(Alert.HANDSHAKE_FAILURE, "insecure renegotiation is not allowed"); } } } }
Network data producer of a "renegotiation_info" extension in the ServerHello handshake message.
/** * 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) { throw shc.conContext.fatal(Alert.UNEXPECTED_MESSAGE, ioe); } if (!shc.conContext.isNegotiated) { // initial handshaking. if (spec.renegotiatedConnection.length != 0) { throw 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. throw 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)) { throw 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.
/** * 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) { throw 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. throw 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"); } throw shc.conContext.fatal(Alert.HANDSHAKE_FAILURE, "Unsafe renegotiation is not allowed"); } } } }
Network data producer of a "renegotiation_info" extension in the ServerHello handshake message.
/** * 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.
/** * 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)) { throw 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) { throw chc.conContext.fatal(Alert.UNEXPECTED_MESSAGE, ioe); } 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) { throw 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) { throw chc.conContext.fatal(Alert.HANDSHAKE_FAILURE, "Invalid renegotiation_info in ServerHello: " + "invalid renegotiated_connection length (" + spec.renegotiatedConnection.length + ")"); } byte[] cvd = chc.conContext.clientVerifyData; if (!Utilities.equals(spec.renegotiatedConnection, 0, cvd.length, cvd, 0, cvd.length)) { throw chc.conContext.fatal(Alert.HANDSHAKE_FAILURE, "Invalid renegotiation_info in ServerHello: " + "unmatched client_verify_data value"); } byte[] svd = chc.conContext.serverVerifyData; if (!Utilities.equals(spec.renegotiatedConnection, cvd.length, infoLen, svd, 0, svd.length)) { throw 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.
/** * 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)) { throw chc.conContext.fatal(Alert.INTERNAL_ERROR, "Missing renegotiation_info and SCSV detected in " + "ClientHello"); } if (!chc.conContext.isNegotiated) { // initial handshaking. if (!HandshakeContext.allowLegacyHelloMessages) { throw 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. throw 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"); } throw chc.conContext.fatal(Alert.HANDSHAKE_FAILURE, "Unsafe renegotiation is not allowed"); } } } } }