--- old/src/java.base/share/classes/sun/security/ssl/HelloExtensions.java 2018-05-11 15:09:58.073456600 -0700 +++ /dev/null 2018-05-11 10:42:23.849000000 -0700 @@ -1,163 +0,0 @@ -/* - * Copyright (c) 2006, 2017, 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.io.PrintStream; -import java.util.*; -import javax.net.ssl.*; - -/** - * This file contains all the classes relevant to TLS Extensions for the - * ClientHello and ServerHello messages. The extension mechanism and - * several extensions are defined in RFC 6066. Additional extensions are - * defined in the ECC RFC 4492 and the ALPN extension is defined in RFC 7301. - * - * Currently, only the two ECC extensions are fully supported. - * - * The classes contained in this file are: - * . HelloExtensions: a List of extensions as used in the client hello - * and server hello messages. - * . ExtensionType: an enum style class for the extension type - * . HelloExtension: abstract base class for all extensions. All subclasses - * must be immutable. - * - * . UnknownExtension: used to represent all parsed extensions that we do not - * explicitly support. - * . ServerNameExtension: the server_name extension. - * . SignatureAlgorithmsExtension: the signature_algorithms extension. - * . SupportedGroupsExtension: the supported groups extension. - * . EllipticPointFormatsExtension: the ECC supported point formats - * (compressed/uncompressed) extension. - * . ALPNExtension: the application_layer_protocol_negotiation extension. - * - * @since 1.6 - * @author Andreas Sterbenz - */ -final class HelloExtensions { - - private List extensions; - private int encodedLength; - - HelloExtensions() { - extensions = Collections.emptyList(); - } - - HelloExtensions(HandshakeInStream s) throws IOException { - int len = s.getInt16(); - extensions = new ArrayList(); - encodedLength = len + 2; - while (len > 0) { - int type = s.getInt16(); - int extlen = s.getInt16(); - ExtensionType extType = ExtensionType.get(type); - HelloExtension extension; - if (extType == ExtensionType.EXT_SERVER_NAME) { - extension = new ServerNameExtension(s, extlen); - } else if (extType == ExtensionType.EXT_SIGNATURE_ALGORITHMS) { - extension = new SignatureAlgorithmsExtension(s, extlen); - } else if (extType == ExtensionType.EXT_SUPPORTED_GROUPS) { - extension = new SupportedGroupsExtension(s, extlen); - } else if (extType == ExtensionType.EXT_EC_POINT_FORMATS) { - extension = new EllipticPointFormatsExtension(s, extlen); - } else if (extType == ExtensionType.EXT_RENEGOTIATION_INFO) { - extension = new RenegotiationInfoExtension(s, extlen); - } else if (extType == ExtensionType.EXT_ALPN) { - extension = new ALPNExtension(s, extlen); - } else if (extType == ExtensionType.EXT_MAX_FRAGMENT_LENGTH) { - extension = new MaxFragmentLengthExtension(s, extlen); - } else if (extType == ExtensionType.EXT_STATUS_REQUEST) { - extension = new CertStatusReqExtension(s, extlen); - } else if (extType == ExtensionType.EXT_STATUS_REQUEST_V2) { - extension = new CertStatusReqListV2Extension(s, extlen); - } else if (extType == ExtensionType.EXT_EXTENDED_MASTER_SECRET) { - extension = new ExtendedMasterSecretExtension(s, extlen); - } else { - extension = new UnknownExtension(s, extlen, extType); - } - extensions.add(extension); - len -= extlen + 4; - } - if (len != 0) { - throw new SSLProtocolException( - "Error parsing extensions: extra data"); - } - } - - // Return the List of extensions. Must not be modified by the caller. - List list() { - return extensions; - } - - void add(HelloExtension ext) { - if (extensions.isEmpty()) { - extensions = new ArrayList(); - } - extensions.add(ext); - encodedLength = -1; - } - - HelloExtension get(ExtensionType type) { - for (HelloExtension ext : extensions) { - if (ext.type == type) { - return ext; - } - } - return null; - } - - int length() { - if (encodedLength >= 0) { - return encodedLength; - } - if (extensions.isEmpty()) { - encodedLength = 0; - } else { - encodedLength = 2; - for (HelloExtension ext : extensions) { - encodedLength += ext.length(); - } - } - return encodedLength; - } - - void send(HandshakeOutStream s) throws IOException { - int length = length(); - if (length == 0) { - return; - } - s.putInt16(length - 2); - for (HelloExtension ext : extensions) { - ext.send(s); - } - } - - void print(PrintStream s) throws IOException { - for (HelloExtension ext : extensions) { - s.println(ext.toString()); - } - } -} --- /dev/null 2018-05-11 10:42:23.849000000 -0700 +++ new/src/java.base/share/classes/sun/security/ssl/SSLExtensions.java 2018-05-11 15:09:57.349557200 -0700 @@ -0,0 +1,360 @@ +/* + * 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 sun.security.ssl.SSLHandshake.HandshakeMessage; +import sun.security.util.HexDumpEncoder; + +/** + * SSL/(D)TLS extensions in a handshake message. + */ +final class SSLExtensions { + private final HandshakeMessage handshakeMessage; + private Map extMap = new LinkedHashMap<>(); + private int encodedLength; + + // Extension map for debug logging + private final Map logMap = + SSLLogger.isOn ? null : new LinkedHashMap<>(); + + SSLExtensions(HandshakeMessage handshakeMessage) { + this.handshakeMessage = handshakeMessage; + this.encodedLength = 2; // 2: the length of the extensions. + } + + SSLExtensions(HandshakeMessage hm, + ByteBuffer m, SSLExtension[] extensions) throws IOException { + this.handshakeMessage = hm; + + int len = Record.getInt16(m); + encodedLength = len + 2; // 2: the length of the extensions. + while (len > 0) { + int extId = Record.getInt16(m); + int extLen = Record.getInt16(m); + if (extLen > m.remaining()) { + hm.handshakeContext.conContext.fatal(Alert.ILLEGAL_PARAMETER, + "Error parsing extension (" + extId + + "): no sufficient data"); + } + + SSLHandshake handshakeType = hm.handshakeType(); + if (SSLExtension.isConsumable(extId) && + SSLExtension.valueOf(handshakeType, extId) == null) { + hm.handshakeContext.conContext.fatal( + Alert.UNSUPPORTED_EXTENSION, + "extension (" + extId + + ") should not be presented in " + handshakeType.name); + } + + boolean isSupported = false; + for (SSLExtension extension : extensions) { + if ((extension.id != extId) || + (extension.onLoadConcumer == null)) { + continue; + } + + if (extension.handshakeType != handshakeType) { + hm.handshakeContext.conContext.fatal( + Alert.UNSUPPORTED_EXTENSION, + "extension (" + extId + ") should not be " + + "presented in " + handshakeType.name); + } + + byte[] extData = new byte[extLen]; + m.get(extData); + extMap.put(extension, extData); + if (logMap != null) { + logMap.put(extId, extData); + } + + isSupported = true; + break; + } + + if (!isSupported) { + if (logMap != null) { + // cache the extension for debug logging + byte[] extData = new byte[extLen]; + m.get(extData); + logMap.put(extId, extData); + + if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + SSLLogger.fine( + "Ignore unknown or unsupported extension", + toString(extId, extData)); + } + } else { + // ignore the extension + int pos = m.position() + extLen; + m.position(pos); + } + } + + len -= extLen + 4; + } + } + + byte[] get(SSLExtension ext) { + return extMap.get(ext); + } + + /** + * Consume the specified extensions. + */ + void consumeOnLoad(HandshakeContext context, + SSLExtension[] extensions) throws IOException { + for (SSLExtension extension : extensions) { + if (context.negotiatedProtocol != null && + !extension.isAvailable(context.negotiatedProtocol)) { + if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + SSLLogger.fine( + "Ignore unsupported extension: " + extension.name); + } + continue; + // context.conContext.fatal(Alert.UNSUPPORTED_EXTENSION, + // context.negotiatedProtocol + " does not support " + + // extension + " extension"); + } + + if (!extMap.containsKey(extension)) { + if (extension.onLoadAbsence != null) { + extension.absent(context, handshakeMessage); + } else if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + SSLLogger.fine( + "Ignore unavailable extension: " + extension.name); + } + continue; + } + + + if (extension.onLoadConcumer == null) { + if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + SSLLogger.warning( + "Ignore unsupported extension: " + extension.name); + } + continue; + } + + ByteBuffer m = ByteBuffer.wrap(extMap.get(extension)); + extension.consumeOnLoad(context, handshakeMessage, m); + + if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + SSLLogger.fine("Consumed extension: " + extension.name); + } + } + } + + /** + * Consider impact of the specified extensions. + */ + void consumeOnTrade(HandshakeContext context, + SSLExtension[] extensions) throws IOException { + for (SSLExtension extension : extensions) { + if (!extMap.containsKey(extension)) { + // No impact could be expected, so just ignore the absence. + continue; + } + + if (extension.onTradeConsumer == null) { + if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + SSLLogger.warning( + "Ignore impact of unsupported extension: " + + extension.name); + } + continue; + } + + extension.consumeOnTrade(context, handshakeMessage); + if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + SSLLogger.fine("Populated with extension: " + extension.name); + } + } + } + + /** + * Produce extension values for the specified extensions. + */ + void produce(HandshakeContext context, + SSLExtension[] extensions) throws IOException { + for (SSLExtension extension : extensions) { + if (extMap.containsKey(extension)) { + if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + SSLLogger.fine( + "Ignore, duplicated extension: " + + extension.name); + } + continue; + } + + if (extension.networkProducer == null) { + if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + SSLLogger.warning( + "Ignore, no extension producer defined: " + + extension.name); + } + continue; + } + + byte[] encoded = extension.produce(context, handshakeMessage); + if (encoded != null) { + extMap.put(extension, encoded); + encodedLength += encoded.length + 4; // extension_type (2) + // extension_data length(2) + } else if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + // The extension is not available in the context. + SSLLogger.fine( + "Ignore, context unavailable extension: " + + extension.name); + } + } + } + + /** + * Produce extension values for the specified extensions, replacing if + * there is an existing extension value for a specified extension. + */ + void reproduce(HandshakeContext context, + SSLExtension[] extensions) throws IOException { + for (SSLExtension extension : extensions) { + if (extension.networkProducer == null) { + if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + SSLLogger.warning( + "Ignore, no extension producer defined: " + + extension.name); + } + continue; + } + + byte[] encoded = extension.produce(context, handshakeMessage); + if (encoded != null) { + if (extMap.containsKey(extension)) { + byte[] old = extMap.replace(extension, encoded); + if (old != null) { + encodedLength -= old.length + 4; + } + encodedLength += encoded.length + 4; + } else { + extMap.put(extension, encoded); + encodedLength += encoded.length + 4; + // extension_type (2) + // extension_data length(2) + } + } else if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + // The extension is not available in the context. + SSLLogger.fine( + "Ignore, context unavailable extension: " + + extension.name); + } + } + } + + // Note that TLS 1.3 may use empty extensions. Please consider it while + // using this method. + int length() { + if (extMap.isEmpty()) { + return 0; + } else { + return encodedLength; + } + } + + // Note that TLS 1.3 may use empty extensions. Please consider it while + // using this method. + void send(HandshakeOutStream hos) throws IOException { + int extsLen = length(); + if (extsLen == 0) { + return; + } + hos.putInt16(extsLen - 2); + // extensions must be sent in the order they appear in the enum + for (SSLExtension ext : SSLExtension.values()) { + byte[] extData = extMap.get(ext); + if (extData != null) { + hos.putInt16(ext.id); + hos.putBytes16(extData); + } + } + } + + @Override + public String toString() { + if (extMap.isEmpty() && (logMap == null || logMap.isEmpty())) { + return ""; + } else { + StringBuilder builder = new StringBuilder(512); + if (logMap != null) { + for (Map.Entry en : logMap.entrySet()) { + SSLExtension ext = SSLExtension.valueOf( + handshakeMessage.handshakeType(), en.getKey()); + if (builder.length() != 0) { + builder.append(",\n"); + } + if (ext != null) { + builder.append( + ext.toString(ByteBuffer.wrap(en.getValue()))); + } else { + builder.append(toString(en.getKey(), en.getValue())); + } + } + + return builder.toString(); + } else { + for (Map.Entry en : extMap.entrySet()) { + if (builder.length() != 0) { + builder.append(",\n"); + } + builder.append( + en.getKey().toString(ByteBuffer.wrap(en.getValue()))); + } + + return builder.toString(); + } + } + } + + private static String toString(int extId, byte[] extData) { + MessageFormat messageFormat = new MessageFormat( + "\"unknown extension ({0})\": '{'\n" + + "{1}\n" + + "'}'", + Locale.ENGLISH); + + HexDumpEncoder hexEncoder = new HexDumpEncoder(); + String encoded = hexEncoder.encodeBuffer(extData); + + Object[] messageFields = { + extId, + Utilities.indent(encoded) + }; + + return messageFormat.format(messageFields); + } +}