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