Path: blob/master/test/jdk/javax/net/ssl/TLSv13/ClientHelloKeyShares.java
41152 views
/*1* Copyright (c) 2020, Oracle and/or its affiliates. All rights reserved.2* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.3*4* This code is free software; you can redistribute it and/or modify it5* under the terms of the GNU General Public License version 2 only, as6* published by the Free Software Foundation.7*8* This code is distributed in the hope that it will be useful, but WITHOUT9* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or10* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License11* version 2 for more details (a copy is included in the LICENSE file that12* accompanied this code).13*14* You should have received a copy of the GNU General Public License version15* 2 along with this work; if not, write to the Free Software Foundation,16* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.17*18* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA19* or visit www.oracle.com if you need additional information or have any20* questions.21*/2223// SunJSSE does not support dynamic system properties, no way to re-use24// system properties in samevm/agentvm mode. For further debugging output25// set the -Djavax.net.debug=ssl:handshake property on the @run lines.2627/*28* @test29* @bug 824763030* @summary Use two key share entries31* @run main/othervm ClientHelloKeyShares 29 2332* @run main/othervm -Djdk.tls.namedGroups=secp384r1,secp521r1,x448,ffdhe2048 ClientHelloKeyShares 24 3033* @run main/othervm -Djdk.tls.namedGroups=sect163k1,sect163r1,x25519 ClientHelloKeyShares 2934* @run main/othervm -Djdk.tls.namedGroups=sect163k1,sect163r1,secp256r1 ClientHelloKeyShares 2335* @run main/othervm -Djdk.tls.namedGroups=sect163k1,sect163r1,ffdhe2048,ffdhe3072,ffdhe4096 ClientHelloKeyShares 25636* @run main/othervm -Djdk.tls.namedGroups=sect163k1,ffdhe2048,x25519,secp256r1 ClientHelloKeyShares 256 2937* @run main/othervm -Djdk.tls.namedGroups=secp256r1,secp384r1,ffdhe2048,x25519 ClientHelloKeyShares 23 25638*/3940import javax.net.ssl.*;41import javax.net.ssl.SSLEngineResult.*;42import java.nio.ByteBuffer;43import java.util.*;444546public class ClientHelloKeyShares {4748// Some TLS constants we'll use for testing49private static final int TLS_REC_HANDSHAKE = 22;50private static final int HELLO_EXT_SUPP_GROUPS = 10;51private static final int HELLO_EXT_SUPP_VERS = 43;52private static final int HELLO_EXT_KEY_SHARE = 51;53private static final int TLS_PROT_VER_13 = 0x0304;54private static final int NG_SECP256R1 = 0x0017;55private static final int NG_SECP384R1 = 0x0018;56private static final int NG_X25519 = 0x001D;57private static final int NG_X448 = 0x001E;5859public static void main(String args[]) throws Exception {60// Arguments to this test are an abitrary number of integer61// values which will be the expected NamedGroup IDs in the key_share62// extension. Expected named group assertions may also be affected63// by setting the jdk.tls.namedGroups System property.64List<Integer> expectedKeyShares = new ArrayList<>();65Arrays.stream(args).forEach(arg ->66expectedKeyShares.add(Integer.valueOf(arg)));6768SSLContext sslCtx = SSLContext.getDefault();69SSLEngine engine = sslCtx.createSSLEngine();70engine.setUseClientMode(true);71SSLSession session = engine.getSession();72ByteBuffer clientOut = ByteBuffer.wrap("I'm a Client".getBytes());73ByteBuffer cTOs =74ByteBuffer.allocateDirect(session.getPacketBufferSize());7576// Create and check the ClientHello message77SSLEngineResult clientResult = engine.wrap(clientOut, cTOs);78logResult("client wrap: ", clientResult);79if (clientResult.getStatus() != SSLEngineResult.Status.OK) {80throw new RuntimeException("Client wrap got status: " +81clientResult.getStatus());82}8384cTOs.flip();85System.out.println(dumpHexBytes(cTOs));86checkClientHello(cTOs, expectedKeyShares);87}8889private static void logResult(String str, SSLEngineResult result) {90HandshakeStatus hsStatus = result.getHandshakeStatus();91System.out.println(str +92result.getStatus() + "/" + hsStatus + ", " +93result.bytesConsumed() + "/" + result.bytesProduced() +94" bytes");95if (hsStatus == HandshakeStatus.FINISHED) {96System.out.println("\t...ready for application data");97}98}99100/**101* Dump a ByteBuffer as a hexdump to stdout. The dumping routine will102* start at the current position of the buffer and run to its limit.103* After completing the dump, the position will be returned to its104* starting point.105*106* @param data the ByteBuffer to dump to stdout.107*108* @return the hexdump of the byte array.109*/110private static String dumpHexBytes(ByteBuffer data) {111StringBuilder sb = new StringBuilder();112if (data != null) {113int i = 0;114data.mark();115while (data.hasRemaining()) {116if (i % 16 == 0 && i != 0) {117sb.append("\n");118}119sb.append(String.format("%02X ", data.get()));120i++;121}122data.reset();123}124125return sb.toString();126}127128/**129* Tests the ClientHello for the presence of the key shares in the supplied130* List of key share identifiers.131*132* @param data the ByteBuffer containing the ClientHello bytes133* @param keyShareTypes a List containing the expected key shares134*135* @throws RuntimeException if there is a deviation between what is expected136* and what is supplied. It will also throw this exception if other137* basic structural elements of the ClientHello are not found (e.g. TLS 1.3138* is not in the list of supported groups, etc.)139*/140private static void checkClientHello(ByteBuffer data,141List<Integer> expectedKeyShares) {142Objects.requireNonNull(data);143data.mark();144145// Process the TLS record header146int type = Byte.toUnsignedInt(data.get());147int ver_major = Byte.toUnsignedInt(data.get());148int ver_minor = Byte.toUnsignedInt(data.get());149int recLen = Short.toUnsignedInt(data.getShort());150151// Simple sanity checks152if (type != 22) {153throw new RuntimeException("Not a handshake: Type = " + type);154} else if (recLen > data.remaining()) {155throw new RuntimeException("Incomplete record in buffer: " +156"Record length = " + recLen + ", Remaining = " +157data.remaining());158}159160// Grab the handshake message header.161int msgHdr = data.getInt();162int msgType = (msgHdr >> 24) & 0x000000FF;163int msgLen = msgHdr & 0x00FFFFFF;164165// More simple sanity checks166if (msgType != 1) {167throw new RuntimeException("Not a ClientHello: Type = " + msgType);168}169170// Skip over the protocol version and client random171data.position(data.position() + 34);172173// Jump past the session ID (if there is one)174int sessLen = Byte.toUnsignedInt(data.get());175if (sessLen != 0) {176data.position(data.position() + sessLen);177}178179// Jump past the cipher suites180int csLen = Short.toUnsignedInt(data.getShort());181if (csLen != 0) {182data.position(data.position() + csLen);183}184185// ...and the compression186int compLen = Byte.toUnsignedInt(data.get());187if (compLen != 0) {188data.position(data.position() + compLen);189}190191// Now for the fun part. Go through the extensions and look192// for supported_versions (to make sure TLS 1.3 is asserted) and193// the expected key shares are present.194boolean foundSupVer = false;195boolean foundKeyShare = false;196int extsLen = Short.toUnsignedInt(data.getShort());197List<Integer> supGrpList = new ArrayList<>();198List<Integer> chKeyShares = new ArrayList<>();199while (data.hasRemaining()) {200int extType = Short.toUnsignedInt(data.getShort());201int extLen = Short.toUnsignedInt(data.getShort());202boolean foundTLS13 = false;203switch (extType) {204case HELLO_EXT_SUPP_GROUPS:205int supGrpLen = Short.toUnsignedInt(data.getShort());206for (int remain = supGrpLen; remain > 0; remain -= 2) {207supGrpList.add(Short.toUnsignedInt(data.getShort()));208}209break;210case HELLO_EXT_SUPP_VERS:211foundSupVer = true;212int supVerLen = Byte.toUnsignedInt(data.get());213for (int remain = supVerLen; remain > 0; remain -= 2) {214foundTLS13 |= (Short.toUnsignedInt(data.getShort()) ==215TLS_PROT_VER_13);216}217218if (!foundTLS13) {219throw new RuntimeException("Missing TLS 1.3 Protocol " +220"Version in supported_groups");221}222break;223case HELLO_EXT_KEY_SHARE:224foundKeyShare = true;225int ksListLen = Short.toUnsignedInt(data.getShort());226while (ksListLen > 0) {227chKeyShares.add(Short.toUnsignedInt(data.getShort()));228int ksLen = Short.toUnsignedInt(data.getShort());229data.position(data.position() + ksLen);230ksListLen -= (4 + ksLen);231}232break;233default:234data.position(data.position() + extLen);235break;236}237}238239// We must have parsed supported_versions, key_share and240// supported_groups extensions.241if ((foundSupVer && foundKeyShare && !supGrpList.isEmpty()) == false) {242throw new RuntimeException("Missing one or more of key_share, " +243"supported_versions and/or supported_groups extensions");244}245246// The key share types we expected in the test should match exactly what247// was asserted in the client hello248if (!expectedKeyShares.equals(chKeyShares)) {249StringBuilder sb = new StringBuilder(250"Expected and Actual key_share lists differ: ");251sb.append("Expected: ");252expectedKeyShares.forEach(ng -> sb.append(ng).append(" "));253sb.append(", Actual: ");254chKeyShares.forEach(ng -> sb.append(ng).append(" "));255throw new RuntimeException(sb.toString());256}257258// The order of the key shares should match the order of precedence259// of the same named groups asserted in the supported_groups extension.260// (RFC 8446, 4.2.8)261int prevChNg = -1;262for (int ng : chKeyShares) {263int chNgPos = supGrpList.indexOf(ng);264if (chNgPos <= prevChNg) {265throw new RuntimeException("Order of precedence violation " +266"for NamedGroup " + ng + " between key_share and " +267"supported_groups extensions");268}269prevChNg = chNgPos;270}271272// We should be at the end of the ClientHello273data.reset();274}275}276277278