1 /* 2 * Copyright (c) 2020, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. 8 * 9 * This code is distributed in the hope that it will be useful, but WITHOUT 10 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 11 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 12 * version 2 for more details (a copy is included in the LICENSE file that 13 * accompanied this code). 14 * 15 * You should have received a copy of the GNU General Public License version 16 * 2 along with this work; if not, write to the Free Software Foundation, 17 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 18 * 19 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 20 * or visit www.oracle.com if you need additional information or have any 21 * questions. 22 */ 23 24 // SunJSSE does not support dynamic system properties, no way to re-use 25 // system properties in samevm/agentvm mode. For further debugging output 26 // set the -Djavax.net.debug=ssl:handshake property on the @run lines. 27 28 /* 29 * @test 30 * @bug 8247630 31 * @summary Use two key share entries 32 * @run main/othervm ClientHelloKeyShares 29 23 33 * @run main/othervm -Djdk.tls.namedGroups=secp384r1,secp521r1,x448,ffdhe2048 ClientHelloKeyShares 24 30 34 * @run main/othervm -Djdk.tls.namedGroups=sect163k1,sect163r1,x25519 ClientHelloKeyShares 29 35 * @run main/othervm -Djdk.tls.namedGroups=sect163k1,sect163r1,secp256r1 ClientHelloKeyShares 23 36 * @run main/othervm -Djdk.tls.namedGroups=sect163k1,sect163r1,ffdhe2048,ffdhe3072,ffdhe4096 ClientHelloKeyShares 256 37 * @run main/othervm -Djdk.tls.namedGroups=sect163k1,ffdhe2048,x25519,secp256r1 ClientHelloKeyShares 256 29 38 * @run main/othervm -Djdk.tls.namedGroups=secp256r1,secp384r1,ffdhe2048,x25519 ClientHelloKeyShares 23 256 39 */ 40 41 import javax.net.ssl.*; 42 import javax.net.ssl.SSLEngineResult.*; 43 import java.nio.ByteBuffer; 44 import java.util.*; 45 46 47 public class ClientHelloKeyShares { 48 49 // Some TLS constants we'll use for testing 50 private static final int TLS_REC_HANDSHAKE = 22; 51 private static final int HELLO_EXT_SUPP_GROUPS = 10; 52 private static final int HELLO_EXT_SUPP_VERS = 43; 53 private static final int HELLO_EXT_KEY_SHARE = 51; 54 private static final int TLS_PROT_VER_13 = 0x0304; 55 private static final int NG_SECP256R1 = 0x0017; 56 private static final int NG_SECP384R1 = 0x0018; 57 private static final int NG_X25519 = 0x001D; 58 private static final int NG_X448 = 0x001E; 59 main(String args[])60 public static void main(String args[]) throws Exception { 61 // Arguments to this test are an abitrary number of integer 62 // values which will be the expected NamedGroup IDs in the key_share 63 // extension. Expected named group assertions may also be affected 64 // by setting the jdk.tls.namedGroups System property. 65 List<Integer> expectedKeyShares = new ArrayList<>(); 66 Arrays.stream(args).forEach(arg -> 67 expectedKeyShares.add(Integer.valueOf(arg))); 68 69 SSLContext sslCtx = SSLContext.getDefault(); 70 SSLEngine engine = sslCtx.createSSLEngine(); 71 engine.setUseClientMode(true); 72 SSLSession session = engine.getSession(); 73 ByteBuffer clientOut = ByteBuffer.wrap("I'm a Client".getBytes()); 74 ByteBuffer cTOs = 75 ByteBuffer.allocateDirect(session.getPacketBufferSize()); 76 77 // Create and check the ClientHello message 78 SSLEngineResult clientResult = engine.wrap(clientOut, cTOs); 79 logResult("client wrap: ", clientResult); 80 if (clientResult.getStatus() != SSLEngineResult.Status.OK) { 81 throw new RuntimeException("Client wrap got status: " + 82 clientResult.getStatus()); 83 } 84 85 cTOs.flip(); 86 System.out.println(dumpHexBytes(cTOs)); 87 checkClientHello(cTOs, expectedKeyShares); 88 } 89 logResult(String str, SSLEngineResult result)90 private static void logResult(String str, SSLEngineResult result) { 91 HandshakeStatus hsStatus = result.getHandshakeStatus(); 92 System.out.println(str + 93 result.getStatus() + "/" + hsStatus + ", " + 94 result.bytesConsumed() + "/" + result.bytesProduced() + 95 " bytes"); 96 if (hsStatus == HandshakeStatus.FINISHED) { 97 System.out.println("\t...ready for application data"); 98 } 99 } 100 101 /** 102 * Dump a ByteBuffer as a hexdump to stdout. The dumping routine will 103 * start at the current position of the buffer and run to its limit. 104 * After completing the dump, the position will be returned to its 105 * starting point. 106 * 107 * @param data the ByteBuffer to dump to stdout. 108 * 109 * @return the hexdump of the byte array. 110 */ dumpHexBytes(ByteBuffer data)111 private static String dumpHexBytes(ByteBuffer data) { 112 StringBuilder sb = new StringBuilder(); 113 if (data != null) { 114 int i = 0; 115 data.mark(); 116 while (data.hasRemaining()) { 117 if (i % 16 == 0 && i != 0) { 118 sb.append("\n"); 119 } 120 sb.append(String.format("%02X ", data.get())); 121 i++; 122 } 123 data.reset(); 124 } 125 126 return sb.toString(); 127 } 128 129 /** 130 * Tests the ClientHello for the presence of the key shares in the supplied 131 * List of key share identifiers. 132 * 133 * @param data the ByteBuffer containing the ClientHello bytes 134 * @param keyShareTypes a List containing the expected key shares 135 * 136 * @throws RuntimeException if there is a deviation between what is expected 137 * and what is supplied. It will also throw this exception if other 138 * basic structural elements of the ClientHello are not found (e.g. TLS 1.3 139 * is not in the list of supported groups, etc.) 140 */ checkClientHello(ByteBuffer data, List<Integer> expectedKeyShares)141 private static void checkClientHello(ByteBuffer data, 142 List<Integer> expectedKeyShares) { 143 Objects.requireNonNull(data); 144 data.mark(); 145 146 // Process the TLS record header 147 int type = Byte.toUnsignedInt(data.get()); 148 int ver_major = Byte.toUnsignedInt(data.get()); 149 int ver_minor = Byte.toUnsignedInt(data.get()); 150 int recLen = Short.toUnsignedInt(data.getShort()); 151 152 // Simple sanity checks 153 if (type != 22) { 154 throw new RuntimeException("Not a handshake: Type = " + type); 155 } else if (recLen > data.remaining()) { 156 throw new RuntimeException("Incomplete record in buffer: " + 157 "Record length = " + recLen + ", Remaining = " + 158 data.remaining()); 159 } 160 161 // Grab the handshake message header. 162 int msgHdr = data.getInt(); 163 int msgType = (msgHdr >> 24) & 0x000000FF; 164 int msgLen = msgHdr & 0x00FFFFFF; 165 166 // More simple sanity checks 167 if (msgType != 1) { 168 throw new RuntimeException("Not a ClientHello: Type = " + msgType); 169 } 170 171 // Skip over the protocol version and client random 172 data.position(data.position() + 34); 173 174 // Jump past the session ID (if there is one) 175 int sessLen = Byte.toUnsignedInt(data.get()); 176 if (sessLen != 0) { 177 data.position(data.position() + sessLen); 178 } 179 180 // Jump past the cipher suites 181 int csLen = Short.toUnsignedInt(data.getShort()); 182 if (csLen != 0) { 183 data.position(data.position() + csLen); 184 } 185 186 // ...and the compression 187 int compLen = Byte.toUnsignedInt(data.get()); 188 if (compLen != 0) { 189 data.position(data.position() + compLen); 190 } 191 192 // Now for the fun part. Go through the extensions and look 193 // for supported_versions (to make sure TLS 1.3 is asserted) and 194 // the expected key shares are present. 195 boolean foundSupVer = false; 196 boolean foundKeyShare = false; 197 int extsLen = Short.toUnsignedInt(data.getShort()); 198 List<Integer> supGrpList = new ArrayList<>(); 199 List<Integer> chKeyShares = new ArrayList<>(); 200 while (data.hasRemaining()) { 201 int extType = Short.toUnsignedInt(data.getShort()); 202 int extLen = Short.toUnsignedInt(data.getShort()); 203 boolean foundTLS13 = false; 204 switch (extType) { 205 case HELLO_EXT_SUPP_GROUPS: 206 int supGrpLen = Short.toUnsignedInt(data.getShort()); 207 for (int remain = supGrpLen; remain > 0; remain -= 2) { 208 supGrpList.add(Short.toUnsignedInt(data.getShort())); 209 } 210 break; 211 case HELLO_EXT_SUPP_VERS: 212 foundSupVer = true; 213 int supVerLen = Byte.toUnsignedInt(data.get()); 214 for (int remain = supVerLen; remain > 0; remain -= 2) { 215 foundTLS13 |= (Short.toUnsignedInt(data.getShort()) == 216 TLS_PROT_VER_13); 217 } 218 219 if (!foundTLS13) { 220 throw new RuntimeException("Missing TLS 1.3 Protocol " + 221 "Version in supported_groups"); 222 } 223 break; 224 case HELLO_EXT_KEY_SHARE: 225 foundKeyShare = true; 226 int ksListLen = Short.toUnsignedInt(data.getShort()); 227 while (ksListLen > 0) { 228 chKeyShares.add(Short.toUnsignedInt(data.getShort())); 229 int ksLen = Short.toUnsignedInt(data.getShort()); 230 data.position(data.position() + ksLen); 231 ksListLen -= (4 + ksLen); 232 } 233 break; 234 default: 235 data.position(data.position() + extLen); 236 break; 237 } 238 } 239 240 // We must have parsed supported_versions, key_share and 241 // supported_groups extensions. 242 if ((foundSupVer && foundKeyShare && !supGrpList.isEmpty()) == false) { 243 throw new RuntimeException("Missing one or more of key_share, " + 244 "supported_versions and/or supported_groups extensions"); 245 } 246 247 // The key share types we expected in the test should match exactly what 248 // was asserted in the client hello 249 if (!expectedKeyShares.equals(chKeyShares)) { 250 StringBuilder sb = new StringBuilder( 251 "Expected and Actual key_share lists differ: "); 252 sb.append("Expected: "); 253 expectedKeyShares.forEach(ng -> sb.append(ng).append(" ")); 254 sb.append(", Actual: "); 255 chKeyShares.forEach(ng -> sb.append(ng).append(" ")); 256 throw new RuntimeException(sb.toString()); 257 } 258 259 // The order of the key shares should match the order of precedence 260 // of the same named groups asserted in the supported_groups extension. 261 // (RFC 8446, 4.2.8) 262 int prevChNg = -1; 263 for (int ng : chKeyShares) { 264 int chNgPos = supGrpList.indexOf(ng); 265 if (chNgPos <= prevChNg) { 266 throw new RuntimeException("Order of precedence violation " + 267 "for NamedGroup " + ng + " between key_share and " + 268 "supported_groups extensions"); 269 } 270 prevChNg = chNgPos; 271 } 272 273 // We should be at the end of the ClientHello 274 data.reset(); 275 } 276 } 277