/* * Copyright (c) 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.*; import javax.net.ssl.SSLProtocolException; import sun.security.ssl.SSLExtension.ExtensionConsumer; import sun.security.ssl.SSLExtension.SSLExtensionSpec; import sun.security.ssl.SSLHandshake.HandshakeMessage; /** * Pack of the "psk_key_exchange_modes" extensions. */ final class PskKeyExchangeModesExtension { static final HandshakeProducer chNetworkProducer = new PskKeyExchangeModesProducer(); static final ExtensionConsumer chOnLoadConsumer = new PskKeyExchangeModesConsumer(); static final HandshakeAbsence chOnLoadAbsence = new PskKeyExchangeModesOnLoadAbsence(); static final HandshakeAbsence chOnTradeAbsence = new PskKeyExchangeModesOnTradeAbsence(); static final SSLStringizer pkemStringizer = new PskKeyExchangeModesStringizer(); enum PskKeyExchangeMode { PSK_KE ((byte)0, "psk_ke"), PSK_DHE_KE ((byte)1, "psk_dhe_ke"); final byte id; final String name; PskKeyExchangeMode(byte id, String name) { this.id = id; this.name = name; } static PskKeyExchangeMode valueOf(byte id) { for(PskKeyExchangeMode pkem : values()) { if (pkem.id == id) { return pkem; } } return null; } static String nameOf(byte id) { for (PskKeyExchangeMode pkem : PskKeyExchangeMode.values()) { if (pkem.id == id) { return pkem.name; } } return ""; } } static final class PskKeyExchangeModesSpec implements SSLExtensionSpec { private static final PskKeyExchangeModesSpec DEFAULT = new PskKeyExchangeModesSpec(new byte[] { PskKeyExchangeMode.PSK_DHE_KE.id}); final byte[] modes; PskKeyExchangeModesSpec(byte[] modes) { this.modes = modes; } PskKeyExchangeModesSpec(ByteBuffer m) throws IOException { if (m.remaining() < 2) { throw new SSLProtocolException( "Invalid psk_key_exchange_modes extension: " + "insufficient data"); } this.modes = Record.getBytes8(m); } boolean contains(PskKeyExchangeMode mode) { if (modes != null) { for (byte m : modes) { if (mode.id == m) { return true; } } } return false; } @Override public String toString() { MessageFormat messageFormat = new MessageFormat( "\"ke_modes\": '['{0}']'", Locale.ENGLISH); if (modes == null || modes.length == 0) { Object[] messageFields = { "" }; return messageFormat.format(messageFields); } else { StringBuilder builder = new StringBuilder(64); boolean isFirst = true; for (byte mode : modes) { if (isFirst) { isFirst = false; } else { builder.append(", "); } builder.append(PskKeyExchangeMode.nameOf(mode)); } Object[] messageFields = { builder.toString() }; return messageFormat.format(messageFields); } } } private static final class PskKeyExchangeModesStringizer implements SSLStringizer { @Override public String toString(ByteBuffer buffer) { try { return (new PskKeyExchangeModesSpec(buffer)).toString(); } catch (IOException ioe) { // For debug logging only, so please swallow exceptions. return ioe.getMessage(); } } } /** * Network data consumer of a "psk_key_exchange_modes" extension in * the ClientHello handshake message. */ private static final class PskKeyExchangeModesConsumer implements ExtensionConsumer { // Prevent instantiation of this class. private PskKeyExchangeModesConsumer() { // 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( SSLExtension.PSK_KEY_EXCHANGE_MODES)) { if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine( "Ignore unavailable psk_key_exchange_modes extension"); } // No session resumption is allowed. if (shc.isResumption && shc.resumingSession != null) { shc.isResumption = false; shc.resumingSession = null; } return; // ignore the extension } // Parse the extension. PskKeyExchangeModesSpec spec; try { spec = new PskKeyExchangeModesSpec(buffer); } catch (IOException ioe) { throw shc.conContext.fatal(Alert.UNEXPECTED_MESSAGE, ioe); } // Update the context. shc.handshakeExtensions.put( SSLExtension.PSK_KEY_EXCHANGE_MODES, spec); // Impact on session resumption. // // Do the requested modes support session resumption? if (shc.isResumption) { // resumingSession may not be set // Note: psk_dhe_ke is the only supported mode now. If the // psk_ke mode is supported in the future, may need an update // here. if (!spec.contains(PskKeyExchangeMode.PSK_DHE_KE)) { shc.isResumption = false; shc.resumingSession = null; if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine( "abort session resumption, " + "no supported psk_dhe_ke PSK key exchange mode"); } } } } } /** * Network data producer of a "psk_key_exchange_modes" extension in the * ClientHello handshake message. */ private static final class PskKeyExchangeModesProducer implements HandshakeProducer { // Prevent instantiation of this class. private PskKeyExchangeModesProducer() { // 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.PSK_KEY_EXCHANGE_MODES)) { if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { SSLLogger.warning( "Ignore unavailable psk_key_exchange_modes extension"); } return null; } byte[] extData = new byte[] {0x01, 0x01}; // psk_dhe_ke // Update the context. chc.handshakeExtensions.put( SSLExtension.PSK_KEY_EXCHANGE_MODES, PskKeyExchangeModesSpec.DEFAULT); return extData; } } /** * The absence processing if a "psk_key_exchange_modes" extension is * not present in the ClientHello handshake message. */ private static final class PskKeyExchangeModesOnLoadAbsence implements HandshakeAbsence { // Prevent instantiation of this class. private PskKeyExchangeModesOnLoadAbsence() { // blank } @Override public void absent(ConnectionContext context, HandshakeMessage message) throws IOException { // The consuming happens in server side only. ServerHandshakeContext shc = (ServerHandshakeContext)context; // No session resumptio is allowed. if (shc.isResumption) { // resumingSession may not be set shc.isResumption = false; shc.resumingSession = null; if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine( "abort session resumption, " + "no supported psk_dhe_ke PSK key exchange mode"); } } } } /** * The absence processing if a "signature_algorithms" extension is * not present in the ClientHello handshake message. */ private static final class PskKeyExchangeModesOnTradeAbsence implements HandshakeAbsence { // Prevent instantiation of this class. private PskKeyExchangeModesOnTradeAbsence() { // blank } @Override public void absent(ConnectionContext context, HandshakeMessage message) throws IOException { // The consuming happens in server side only. ServerHandshakeContext shc = (ServerHandshakeContext)context; // A client MUST provide a "psk_key_exchange_modes" extension if // it offers a "pre_shared_key" extension. If clients offer // "pre_shared_key" without a "psk_key_exchange_modes" extension, // servers MUST abort the handshake. SSLExtensionSpec spec = shc.handshakeExtensions.get(SSLExtension.CH_PRE_SHARED_KEY); if (spec != null) { throw shc.conContext.fatal(Alert.HANDSHAKE_FAILURE, "pre_shared_key key extension is offered " + "without a psk_key_exchange_modes extension"); } } } }