1 /* 2 * Copyright (c) 2018, 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 /* 25 * @test 26 * @bug 8208350 27 * @summary Disable all DES cipher suites 28 * @run main/othervm NoDesRC4CiphSuite 29 */ 30 31 /* 32 * SunJSSE does not support dynamic system properties, no way to re-use 33 * system properties in samevm/agentvm mode. 34 */ 35 36 import java.security.Security; 37 import javax.net.ssl.*; 38 import javax.net.ssl.SSLEngineResult.HandshakeStatus; 39 import java.io.IOException; 40 import java.nio.ByteBuffer; 41 import java.security.GeneralSecurityException; 42 import java.util.List; 43 import java.util.ArrayList; 44 import java.util.Arrays; 45 46 public class NoDesRC4CiphSuite { 47 48 private static final boolean DEBUG = false; 49 50 private static final byte RECTYPE_HS = 0x16; 51 private static final byte HSMSG_CLIHELLO = 0x01; 52 53 // These are some groups of Cipher Suites by names and IDs 54 private static final List<Integer> DES_CS_LIST = Arrays.asList( 55 0x0009, 0x0015, 0x0012, 0x001A, 0x0008, 0x0014, 0x0011, 0x0019 56 ); 57 private static final String[] DES_CS_LIST_NAMES = new String[] { 58 "SSL_RSA_WITH_DES_CBC_SHA", 59 "SSL_DHE_RSA_WITH_DES_CBC_SHA", 60 "SSL_DHE_DSS_WITH_DES_CBC_SHA", 61 "SSL_DH_anon_WITH_DES_CBC_SHA", 62 "SSL_RSA_EXPORT_WITH_DES40_CBC_SHA", 63 "SSL_DHE_RSA_EXPORT_WITH_DES40_CBC_SHA", 64 "SSL_DHE_DSS_EXPORT_WITH_DES40_CBC_SHA", 65 "SSL_DH_anon_EXPORT_WITH_DES40_CBC_SHA" 66 }; 67 private static final List<Integer> RC4_CS_LIST = Arrays.asList( 68 0xC007, 0xC011, 0x0005, 0xC002, 0xC00C, 0x0004, 0xC016, 0x0018, 69 0x0003, 0x0017 70 ); 71 private static final String[] RC4_CS_LIST_NAMES = new String[] { 72 "TLS_ECDHE_ECDSA_WITH_RC4_128_SHA", 73 "TLS_ECDHE_RSA_WITH_RC4_128_SHA", 74 "SSL_RSA_WITH_RC4_128_SHA", 75 "TLS_ECDH_ECDSA_WITH_RC4_128_SHA", 76 "TLS_ECDH_RSA_WITH_RC4_128_SHA", 77 "SSL_RSA_WITH_RC4_128_MD5", 78 "TLS_ECDH_anon_WITH_RC4_128_SHA", 79 "SSL_DH_anon_WITH_RC4_128_MD5", 80 "SSL_RSA_EXPORT_WITH_RC4_40_MD5", 81 "SSL_DH_anon_EXPORT_WITH_RC4_40_MD5" 82 }; 83 84 private static final ByteBuffer CLIOUTBUF = 85 ByteBuffer.wrap("Client Side".getBytes()); 86 main(String[] args)87 public static void main(String[] args) throws Exception { 88 boolean allGood = true; 89 String disAlg = Security.getProperty("jdk.tls.disabledAlgorithms"); 90 System.err.println("Disabled Algs: " + disAlg); 91 92 // Disabled DES tests 93 allGood &= testDefaultCase(DES_CS_LIST); 94 allGood &= testEngAddDisabled(DES_CS_LIST_NAMES, DES_CS_LIST); 95 allGood &= testEngOnlyDisabled(DES_CS_LIST_NAMES); 96 97 // Disabled RC4 tests 98 allGood &= testDefaultCase(RC4_CS_LIST); 99 allGood &= testEngAddDisabled(RC4_CS_LIST_NAMES, RC4_CS_LIST); 100 allGood &= testEngOnlyDisabled(RC4_CS_LIST_NAMES); 101 102 if (allGood) { 103 System.err.println("All tests passed"); 104 } else { 105 throw new RuntimeException("One or more tests failed"); 106 } 107 } 108 109 /** 110 * Create an engine with the default set of cipher suites enabled and make 111 * sure none of the disabled suites are present in the client hello. 112 * 113 * @param disabledSuiteIds the {@code List} of disabled cipher suite IDs 114 * to be checked for. 115 * 116 * @return true if the test passed (No disabled suites), false otherwise 117 */ testDefaultCase(List<Integer> disabledSuiteIds)118 private static boolean testDefaultCase(List<Integer> disabledSuiteIds) 119 throws Exception { 120 System.err.println("\nTest: Default SSLEngine suite set"); 121 SSLEngine ssle = makeEngine(); 122 if (DEBUG) { 123 listCiphers("Suite set upon creation", ssle); 124 } 125 SSLEngineResult clientResult; 126 ByteBuffer cTOs = makeClientBuf(ssle); 127 clientResult = ssle.wrap(CLIOUTBUF, cTOs); 128 if (DEBUG) { 129 dumpResult("ClientHello: ", clientResult); 130 } 131 cTOs.flip(); 132 boolean foundSuite = areSuitesPresentCH(cTOs, disabledSuiteIds); 133 if (foundSuite) { 134 System.err.println("FAIL: Found disabled suites!"); 135 return false; 136 } else { 137 System.err.println("PASS: No disabled suites found."); 138 return true; 139 } 140 } 141 142 /** 143 * Create an engine and set only disabled cipher suites. 144 * The engine should not create the client hello message since the only 145 * available suites to assert in the client hello are disabled ones. 146 * 147 * @param disabledSuiteNames an array of cipher suite names that 148 * should be disabled cipher suites. 149 * 150 * @return true if the engine throws SSLHandshakeException during client 151 * hello creation, false otherwise. 152 */ testEngOnlyDisabled(String[] disabledSuiteNames)153 private static boolean testEngOnlyDisabled(String[] disabledSuiteNames) 154 throws Exception { 155 System.err.println( 156 "\nTest: SSLEngine configured with only disabled suites"); 157 try { 158 SSLEngine ssle = makeEngine(); 159 ssle.setEnabledCipherSuites(disabledSuiteNames); 160 if (DEBUG) { 161 listCiphers("Suite set upon creation", ssle); 162 } 163 SSLEngineResult clientResult; 164 ByteBuffer cTOs = makeClientBuf(ssle); 165 clientResult = ssle.wrap(CLIOUTBUF, cTOs); 166 if (DEBUG) { 167 dumpResult("ClientHello: ", clientResult); 168 } 169 cTOs.flip(); 170 } catch (SSLHandshakeException shse) { 171 System.err.println("PASS: Caught expected exception: " + shse); 172 return true; 173 } 174 System.err.println("FAIL: Expected SSLHandshakeException not thrown"); 175 return false; 176 } 177 178 /** 179 * Create an engine and add some disabled suites to the default 180 * set of cipher suites. Make sure none of the disabled suites show up 181 * in the client hello even though they were explicitly added. 182 * 183 * @param disabledSuiteNames an array of cipher suite names that 184 * should be disabled cipher suites. 185 * @param disabledIds the {@code List} of disabled cipher suite IDs 186 * to be checked for. 187 * 188 * @return true if the test passed (No disabled suites), false otherwise 189 */ testEngAddDisabled(String[] disabledNames, List<Integer> disabledIds)190 private static boolean testEngAddDisabled(String[] disabledNames, 191 List<Integer> disabledIds) throws Exception { 192 System.err.println("\nTest: SSLEngine with disabled suites added"); 193 SSLEngine ssle = makeEngine(); 194 195 // Add disabled suites to the existing engine's set of enabled suites 196 String[] initialSuites = ssle.getEnabledCipherSuites(); 197 String[] plusDisSuites = Arrays.copyOf(initialSuites, 198 initialSuites.length + disabledNames.length); 199 System.arraycopy(disabledNames, 0, plusDisSuites, 200 initialSuites.length, disabledNames.length); 201 ssle.setEnabledCipherSuites(plusDisSuites); 202 203 if (DEBUG) { 204 listCiphers("Suite set upon creation", ssle); 205 } 206 SSLEngineResult clientResult; 207 ByteBuffer cTOs = makeClientBuf(ssle); 208 clientResult = ssle.wrap(CLIOUTBUF, cTOs); 209 if (DEBUG) { 210 dumpResult("ClientHello: ", clientResult); 211 } 212 cTOs.flip(); 213 boolean foundDisabled = areSuitesPresentCH(cTOs, disabledIds); 214 if (foundDisabled) { 215 System.err.println("FAIL: Found disabled suites!"); 216 return false; 217 } else { 218 System.err.println("PASS: No disabled suites found."); 219 return true; 220 } 221 } 222 makeEngine()223 private static SSLEngine makeEngine() throws GeneralSecurityException { 224 SSLContext ctx = SSLContext.getInstance("TLSv1.2"); 225 ctx.init(null, null, null); 226 return ctx.createSSLEngine(); 227 } 228 makeClientBuf(SSLEngine ssle)229 private static ByteBuffer makeClientBuf(SSLEngine ssle) { 230 ssle.setUseClientMode(true); 231 ssle.setNeedClientAuth(false); 232 SSLSession sess = ssle.getSession(); 233 ByteBuffer cTOs = ByteBuffer.allocateDirect(sess.getPacketBufferSize()); 234 return cTOs; 235 } 236 listCiphers(String prefix, SSLEngine ssle)237 private static void listCiphers(String prefix, SSLEngine ssle) { 238 System.err.println(prefix + "\n---------------"); 239 String[] suites = ssle.getEnabledCipherSuites(); 240 for (String suite : suites) { 241 System.err.println(suite); 242 } 243 System.err.println("---------------"); 244 } 245 246 /** 247 * Walk a TLS 1.2 or earlier ClientHello looking for any of the suites 248 * in the suiteIdList. 249 * 250 * @param clientHello a ByteBuffer containing the ClientHello message as 251 * a complete TLS record. The position of the buffer should be 252 * at the first byte of the TLS record header. 253 * @param suiteIdList a List of integer values corresponding to 254 * TLS cipher suite identifiers. 255 * 256 * @return true if at least one of the suites in {@code suiteIdList} 257 * is found in the ClientHello's cipher suite list 258 * 259 * @throws IOException if the data in the {@code clientHello} 260 * buffer is not a TLS handshake message or is not a client hello. 261 */ areSuitesPresentCH(ByteBuffer clientHello, List<Integer> suiteIdList)262 private static boolean areSuitesPresentCH(ByteBuffer clientHello, 263 List<Integer> suiteIdList) throws IOException { 264 byte val; 265 266 // Process the TLS Record 267 val = clientHello.get(); 268 if (val != RECTYPE_HS) { 269 throw new IOException( 270 "Not a handshake record, type = " + val); 271 } 272 273 // Just skip over the version and length 274 clientHello.position(clientHello.position() + 4); 275 276 // Check the handshake message type 277 val = clientHello.get(); 278 if (val != HSMSG_CLIHELLO) { 279 throw new IOException( 280 "Not a ClientHello handshake message, type = " + val); 281 } 282 283 // Skip over the length 284 clientHello.position(clientHello.position() + 3); 285 286 // Skip over the protocol version (2) and random (32); 287 clientHello.position(clientHello.position() + 34); 288 289 // Skip past the session ID (variable length <= 32) 290 int len = Byte.toUnsignedInt(clientHello.get()); 291 if (len > 32) { 292 throw new IOException("Session ID is too large, len = " + len); 293 } 294 clientHello.position(clientHello.position() + len); 295 296 // Finally, we are at the cipher suites. Walk the list and place them 297 // into a List. 298 int csLen = Short.toUnsignedInt(clientHello.getShort()); 299 if (csLen % 2 != 0) { 300 throw new IOException("CipherSuite length is invalid, len = " + 301 csLen); 302 } 303 int csCount = csLen / 2; 304 List<Integer> csSuiteList = new ArrayList<>(csCount); 305 log("Found following suite IDs in hello:"); 306 for (int i = 0; i < csCount; i++) { 307 int curSuite = Short.toUnsignedInt(clientHello.getShort()); 308 log(String.format("Suite ID: 0x%04x", curSuite)); 309 csSuiteList.add(curSuite); 310 } 311 312 // Now check to see if any of the suites passed in match what is in 313 // the suite list. 314 boolean foundMatch = false; 315 for (Integer cs : suiteIdList) { 316 if (csSuiteList.contains(cs)) { 317 System.err.format("Found match for suite ID 0x%04x\n", cs); 318 foundMatch = true; 319 break; 320 } 321 } 322 323 // We don't care about the rest of the ClientHello message. 324 // Rewind and return whether we found a match or not. 325 clientHello.rewind(); 326 return foundMatch; 327 } 328 dumpResult(String str, SSLEngineResult result)329 private static void dumpResult(String str, SSLEngineResult result) { 330 System.err.println("The format of the SSLEngineResult is: \n" + 331 "\t\"getStatus() / getHandshakeStatus()\" +\n" + 332 "\t\"bytesConsumed() / bytesProduced()\"\n"); 333 HandshakeStatus hsStatus = result.getHandshakeStatus(); 334 System.err.println(str + result.getStatus() + "/" + hsStatus + ", " + 335 result.bytesConsumed() + "/" + result.bytesProduced() + " bytes"); 336 if (hsStatus == HandshakeStatus.FINISHED) { 337 System.err.println("\t...ready for application data"); 338 } 339 } 340 log(String str)341 private static void log(String str) { 342 if (DEBUG) { 343 System.err.println(str); 344 } 345 } 346 } 347