1/*
2 * Copyright (c) 2002, 2017, 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
24import java.io.*;
25import java.net.*;
26import java.util.*;
27import java.util.concurrent.*;
28
29import java.security.*;
30import java.security.cert.*;
31
32import javax.net.ssl.*;
33
34/**
35 * Test that all ciphersuites work in all versions and all client
36 * authentication types. The way this is setup the server is stateless and
37 * all checking is done on the client side.
38 *
39 * The test is multithreaded to speed it up, especially on multiprocessor
40 * machines. To simplify debugging, run with -DnumThreads=1.
41 *
42 * @author Andreas Sterbenz
43 */
44public class CipherTest {
45
46    // use any available port for the server socket
47    static volatile int serverPort = 0;
48
49    static final int THREADS = Integer.getInteger("numThreads", 4);
50    static final String TEST_SRC = System.getProperty("test.src", ".");
51
52    // assume that if we do not read anything for 20 seconds, something
53    // has gone wrong
54    final static int TIMEOUT = 20 * 1000;
55
56    static KeyStore trustStore, keyStore;
57    static X509ExtendedKeyManager keyManager;
58    static X509TrustManager trustManager;
59    static SecureRandom secureRandom;
60
61    private static PeerFactory peerFactory;
62
63    static final CountDownLatch clientCondition = new CountDownLatch(1);
64
65    static abstract class Server implements Runnable {
66
67        final CipherTest cipherTest;
68
69        Server(CipherTest cipherTest) throws Exception {
70            this.cipherTest = cipherTest;
71        }
72
73        @Override
74        public abstract void run();
75
76        void handleRequest(InputStream in, OutputStream out) throws IOException {
77            boolean newline = false;
78            StringBuilder sb = new StringBuilder();
79            while (true) {
80                int ch = in.read();
81                if (ch < 0) {
82                    throw new EOFException();
83                }
84                sb.append((char)ch);
85                if (ch == '\r') {
86                    // empty
87                } else if (ch == '\n') {
88                    if (newline) {
89                        // 2nd newline in a row, end of request
90                        break;
91                    }
92                    newline = true;
93                } else {
94                    newline = false;
95                }
96            }
97            String request = sb.toString();
98            if (request.startsWith("GET / HTTP/1.") == false) {
99                throw new IOException("Invalid request: " + request);
100            }
101            out.write("HTTP/1.0 200 OK\r\n\r\n".getBytes());
102        }
103
104    }
105
106    public static class TestParameters {
107
108        String cipherSuite;
109        String protocol;
110        String clientAuth;
111
112        TestParameters(String cipherSuite, String protocol,
113                String clientAuth) {
114            this.cipherSuite = cipherSuite;
115            this.protocol = protocol;
116            this.clientAuth = clientAuth;
117        }
118
119        boolean isEnabled() {
120            return TLSCipherStatus.isEnabled(cipherSuite, protocol);
121        }
122
123        @Override
124        public String toString() {
125            String s = cipherSuite + " in " + protocol + " mode";
126            if (clientAuth != null) {
127                s += " with " + clientAuth + " client authentication";
128            }
129            return s;
130        }
131
132        static enum TLSCipherStatus {
133            // cipher suites supported since TLS 1.2
134            CS_01("TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384", 0x0303, 0xFFFF),
135            CS_02("TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384",   0x0303, 0xFFFF),
136            CS_03("TLS_RSA_WITH_AES_256_CBC_SHA256",         0x0303, 0xFFFF),
137            CS_04("TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA384",  0x0303, 0xFFFF),
138            CS_05("TLS_ECDH_RSA_WITH_AES_256_CBC_SHA384",    0x0303, 0xFFFF),
139            CS_06("TLS_DHE_RSA_WITH_AES_256_CBC_SHA256",     0x0303, 0xFFFF),
140            CS_07("TLS_DHE_DSS_WITH_AES_256_CBC_SHA256",     0x0303, 0xFFFF),
141
142            CS_08("TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256", 0x0303, 0xFFFF),
143            CS_09("TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256",   0x0303, 0xFFFF),
144            CS_10("TLS_RSA_WITH_AES_128_CBC_SHA256",         0x0303, 0xFFFF),
145            CS_11("TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA256",  0x0303, 0xFFFF),
146            CS_12("TLS_ECDH_RSA_WITH_AES_128_CBC_SHA256",    0x0303, 0xFFFF),
147            CS_13("TLS_DHE_RSA_WITH_AES_128_CBC_SHA256",     0x0303, 0xFFFF),
148            CS_14("TLS_DHE_DSS_WITH_AES_128_CBC_SHA256",     0x0303, 0xFFFF),
149
150            CS_15("TLS_DH_anon_WITH_AES_256_CBC_SHA256",     0x0303, 0xFFFF),
151            CS_16("TLS_DH_anon_WITH_AES_128_CBC_SHA256",     0x0303, 0xFFFF),
152            CS_17("TLS_RSA_WITH_NULL_SHA256",                0x0303, 0xFFFF),
153
154            CS_20("TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384", 0x0303, 0xFFFF),
155            CS_21("TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256", 0x0303, 0xFFFF),
156            CS_22("TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384",   0x0303, 0xFFFF),
157            CS_23("TLS_RSA_WITH_AES_256_GCM_SHA384",         0x0303, 0xFFFF),
158            CS_24("TLS_ECDH_ECDSA_WITH_AES_256_GCM_SHA384",  0x0303, 0xFFFF),
159            CS_25("TLS_ECDH_RSA_WITH_AES_256_GCM_SHA384",    0x0303, 0xFFFF),
160            CS_26("TLS_DHE_RSA_WITH_AES_256_GCM_SHA384",     0x0303, 0xFFFF),
161            CS_27("TLS_DHE_DSS_WITH_AES_256_GCM_SHA384",     0x0303, 0xFFFF),
162
163            CS_28("TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256",   0x0303, 0xFFFF),
164            CS_29("TLS_RSA_WITH_AES_128_GCM_SHA256",         0x0303, 0xFFFF),
165            CS_30("TLS_ECDH_ECDSA_WITH_AES_128_GCM_SHA256",  0x0303, 0xFFFF),
166            CS_31("TLS_ECDH_RSA_WITH_AES_128_GCM_SHA256",    0x0303, 0xFFFF),
167            CS_32("TLS_DHE_RSA_WITH_AES_128_GCM_SHA256",     0x0303, 0xFFFF),
168            CS_33("TLS_DHE_DSS_WITH_AES_128_GCM_SHA256",     0x0303, 0xFFFF),
169
170            CS_34("TLS_DH_anon_WITH_AES_256_GCM_SHA384",     0x0303, 0xFFFF),
171            CS_35("TLS_DH_anon_WITH_AES_128_GCM_SHA256",     0x0303, 0xFFFF),
172
173            // cipher suites obsoleted since TLS 1.2
174            CS_50("SSL_RSA_WITH_DES_CBC_SHA",                0x0000, 0x0303),
175            CS_51("SSL_DHE_RSA_WITH_DES_CBC_SHA",            0x0000, 0x0303),
176            CS_52("SSL_DHE_DSS_WITH_DES_CBC_SHA",            0x0000, 0x0303),
177            CS_53("SSL_DH_anon_WITH_DES_CBC_SHA",            0x0000, 0x0303),
178            CS_54("TLS_KRB5_WITH_DES_CBC_SHA",               0x0000, 0x0303),
179            CS_55("TLS_KRB5_WITH_DES_CBC_MD5",               0x0000, 0x0303),
180
181            // cipher suites obsoleted since TLS 1.1
182            CS_60("SSL_RSA_EXPORT_WITH_RC4_40_MD5",          0x0000, 0x0302),
183            CS_61("SSL_DH_anon_EXPORT_WITH_RC4_40_MD5",      0x0000, 0x0302),
184            CS_62("SSL_RSA_EXPORT_WITH_DES40_CBC_SHA",       0x0000, 0x0302),
185            CS_63("SSL_DHE_RSA_EXPORT_WITH_DES40_CBC_SHA",   0x0000, 0x0302),
186            CS_64("SSL_DHE_DSS_EXPORT_WITH_DES40_CBC_SHA",   0x0000, 0x0302),
187            CS_65("SSL_DH_anon_EXPORT_WITH_DES40_CBC_SHA",   0x0000, 0x0302),
188            CS_66("TLS_KRB5_EXPORT_WITH_RC4_40_SHA",         0x0000, 0x0302),
189            CS_67("TLS_KRB5_EXPORT_WITH_RC4_40_MD5",         0x0000, 0x0302),
190            CS_68("TLS_KRB5_EXPORT_WITH_DES_CBC_40_SHA",     0x0000, 0x0302),
191            CS_69("TLS_KRB5_EXPORT_WITH_DES_CBC_40_MD5",     0x0000, 0x0302),
192
193            // ignore TLS_EMPTY_RENEGOTIATION_INFO_SCSV always
194            CS_99("TLS_EMPTY_RENEGOTIATION_INFO_SCSV",       0xFFFF, 0x0000);
195
196            // the cipher suite name
197            final String cipherSuite;
198
199            // supported since protocol version
200            final int supportedSince;
201
202            // obsoleted since protocol version
203            final int obsoletedSince;
204
205            TLSCipherStatus(String cipherSuite,
206                    int supportedSince, int obsoletedSince) {
207                this.cipherSuite = cipherSuite;
208                this.supportedSince = supportedSince;
209                this.obsoletedSince = obsoletedSince;
210            }
211
212            static boolean isEnabled(String cipherSuite, String protocol) {
213                int versionNumber = toVersionNumber(protocol);
214
215                if (versionNumber < 0) {
216                    return true;  // unlikely to happen
217                }
218
219                for (TLSCipherStatus status : TLSCipherStatus.values()) {
220                    if (cipherSuite.equals(status.cipherSuite)) {
221                        if ((versionNumber < status.supportedSince) ||
222                            (versionNumber >= status.obsoletedSince)) {
223                            return false;
224                        }
225
226                        return true;
227                    }
228                }
229
230                return true;
231            }
232
233            private static int toVersionNumber(String protocol) {
234                int versionNumber = -1;
235
236                switch (protocol) {
237                    case "SSLv2Hello":
238                        versionNumber = 0x0002;
239                        break;
240                    case "SSLv3":
241                        versionNumber = 0x0300;
242                        break;
243                    case "TLSv1":
244                        versionNumber = 0x0301;
245                        break;
246                    case "TLSv1.1":
247                        versionNumber = 0x0302;
248                        break;
249                    case "TLSv1.2":
250                        versionNumber = 0x0303;
251                        break;
252                    default:
253                        // unlikely to happen
254                }
255
256                return versionNumber;
257            }
258        }
259    }
260
261    private List<TestParameters> tests;
262    private Iterator<TestParameters> testIterator;
263    private SSLSocketFactory factory;
264    private boolean failed;
265
266    private CipherTest(PeerFactory peerFactory) throws IOException {
267        factory = (SSLSocketFactory)SSLSocketFactory.getDefault();
268        SSLSocket socket = (SSLSocket)factory.createSocket();
269        String[] cipherSuites = socket.getSupportedCipherSuites();
270        String[] protocols = socket.getSupportedProtocols();
271        String[] clientAuths = {null, "RSA", "DSA", "ECDSA"};
272        tests = new ArrayList<TestParameters>(
273            cipherSuites.length * protocols.length * clientAuths.length);
274        for (int i = 0; i < cipherSuites.length; i++) {
275            String cipherSuite = cipherSuites[i];
276
277            for (int j = 0; j < protocols.length; j++) {
278                String protocol = protocols[j];
279
280                if (!peerFactory.isSupported(cipherSuite, protocol)) {
281                    continue;
282                }
283
284                for (int k = 0; k < clientAuths.length; k++) {
285                    String clientAuth = clientAuths[k];
286                    if ((clientAuth != null) &&
287                            (cipherSuite.indexOf("DH_anon") != -1)) {
288                        // no client with anonymous ciphersuites
289                        continue;
290                    }
291
292                    tests.add(new TestParameters(cipherSuite, protocol,
293                        clientAuth));
294                }
295            }
296        }
297
298        testIterator = tests.iterator();
299    }
300
301    synchronized void setFailed() {
302        failed = true;
303    }
304
305    public void run() throws Exception {
306        Thread[] threads = new Thread[THREADS];
307        for (int i = 0; i < THREADS; i++) {
308            try {
309                threads[i] = new Thread(peerFactory.newClient(this),
310                    "Client " + i);
311            } catch (Exception e) {
312                e.printStackTrace();
313                return;
314            }
315            threads[i].start();
316        }
317
318        // The client threads are ready.
319        clientCondition.countDown();
320
321        try {
322            for (int i = 0; i < THREADS; i++) {
323                threads[i].join();
324            }
325        } catch (InterruptedException e) {
326            setFailed();
327            e.printStackTrace();
328        }
329        if (failed) {
330            throw new Exception("*** Test '" + peerFactory.getName() +
331                "' failed ***");
332        } else {
333            System.out.println("Test '" + peerFactory.getName() +
334                "' completed successfully");
335        }
336    }
337
338    synchronized TestParameters getTest() {
339        if (failed) {
340            return null;
341        }
342        if (testIterator.hasNext()) {
343            return (TestParameters)testIterator.next();
344        }
345        return null;
346    }
347
348    SSLSocketFactory getFactory() {
349        return factory;
350    }
351
352    static abstract class Client implements Runnable {
353
354        final CipherTest cipherTest;
355
356        Client(CipherTest cipherTest) throws Exception {
357            this.cipherTest = cipherTest;
358        }
359
360        @Override
361        public final void run() {
362            while (true) {
363                TestParameters params = cipherTest.getTest();
364                if (params == null) {
365                    // no more tests
366                    break;
367                }
368                if (params.isEnabled() == false) {
369                    System.out.println("Skipping disabled test " + params);
370                    continue;
371                }
372                try {
373                    runTest(params);
374                    System.out.println("Passed " + params);
375                } catch (SocketTimeoutException ste) {
376                    System.out.println("The client connects to the server timeout, "
377                            + "so ignore the test.");
378                    break;
379                } catch (Exception e) {
380                    cipherTest.setFailed();
381                    System.out.println("** Failed " + params + "**");
382                    e.printStackTrace();
383                }
384            }
385        }
386
387        abstract void runTest(TestParameters params) throws Exception;
388
389        void sendRequest(InputStream in, OutputStream out) throws IOException {
390            out.write("GET / HTTP/1.0\r\n\r\n".getBytes());
391            out.flush();
392            StringBuilder sb = new StringBuilder();
393            while (true) {
394                int ch = in.read();
395                if (ch < 0) {
396                    break;
397                }
398                sb.append((char)ch);
399            }
400            String response = sb.toString();
401            if (response.startsWith("HTTP/1.0 200 ") == false) {
402                throw new IOException("Invalid response: " + response);
403            }
404        }
405
406    }
407
408    // for some reason, ${test.src} has a different value when the
409    // test is called from the script and when it is called directly...
410    static String pathToStores = ".";
411    static String pathToStoresSH = ".";
412    static String keyStoreFile = "keystore";
413    static String trustStoreFile = "truststore";
414    static char[] passwd = "passphrase".toCharArray();
415
416    static File PATH;
417
418    private static KeyStore readKeyStore(String name) throws Exception {
419        File file = new File(PATH, name);
420        KeyStore ks;
421        try (InputStream in = new FileInputStream(file)) {
422            ks = KeyStore.getInstance("JKS");
423            ks.load(in, passwd);
424        }
425        return ks;
426    }
427
428    public static void main(PeerFactory peerFactory, String[] args)
429            throws Exception {
430        long time = System.currentTimeMillis();
431        String relPath;
432        if ((args != null) && (args.length > 0) && args[0].equals("sh")) {
433            relPath = pathToStoresSH;
434        } else {
435            relPath = pathToStores;
436        }
437        PATH = new File(TEST_SRC, relPath);
438        CipherTest.peerFactory = peerFactory;
439        System.out.print(
440            "Initializing test '" + peerFactory.getName() + "'...");
441        secureRandom = new SecureRandom();
442        secureRandom.nextInt();
443        trustStore = readKeyStore(trustStoreFile);
444        keyStore = readKeyStore(keyStoreFile);
445        KeyManagerFactory keyFactory =
446            KeyManagerFactory.getInstance(
447                KeyManagerFactory.getDefaultAlgorithm());
448        keyFactory.init(keyStore, passwd);
449        keyManager = (X509ExtendedKeyManager)keyFactory.getKeyManagers()[0];
450        trustManager = new AlwaysTrustManager();
451
452        CipherTest cipherTest = new CipherTest(peerFactory);
453        Thread serverThread = new Thread(peerFactory.newServer(cipherTest),
454            "Server");
455        serverThread.setDaemon(true);
456        serverThread.start();
457        System.out.println("Done");
458        cipherTest.run();
459        time = System.currentTimeMillis() - time;
460        System.out.println("Done. (" + time + " ms)");
461    }
462
463    static abstract class PeerFactory {
464
465        abstract String getName();
466
467        abstract Client newClient(CipherTest cipherTest) throws Exception;
468
469        abstract Server newServer(CipherTest cipherTest) throws Exception;
470
471        boolean isSupported(String cipherSuite, String protocol) {
472            // skip kerberos cipher suites
473            if (cipherSuite.startsWith("TLS_KRB5")) {
474                System.out.println("Skipping unsupported test for " +
475                                    cipherSuite + " of " + protocol);
476                return false;
477            }
478
479            // skip SSLv2Hello protocol
480            if (protocol.equals("SSLv2Hello")) {
481                System.out.println("Skipping unsupported test for " +
482                                    cipherSuite + " of " + protocol);
483                return false;
484            }
485
486            // ignore exportable cipher suite for TLSv1.1
487            if (protocol.equals("TLSv1.1")) {
488                if (cipherSuite.indexOf("_EXPORT_WITH") != -1) {
489                    System.out.println("Skipping obsoleted test for " +
490                                        cipherSuite + " of " + protocol);
491                    return false;
492                }
493            }
494
495            return true;
496        }
497    }
498
499}
500
501// we currently don't do any chain verification. we assume that works ok
502// and we can speed up the test. we could also just add a plain certificate
503// chain comparision with our trusted certificates.
504class AlwaysTrustManager implements X509TrustManager {
505
506    public AlwaysTrustManager() {
507
508    }
509
510    @Override
511    public void checkClientTrusted(X509Certificate[] chain, String authType)
512            throws CertificateException {
513        // empty
514    }
515
516    @Override
517    public void checkServerTrusted(X509Certificate[] chain, String authType)
518            throws CertificateException {
519        // empty
520    }
521
522    @Override
523    public X509Certificate[] getAcceptedIssuers() {
524        return new X509Certificate[0];
525    }
526}
527
528class MyX509KeyManager extends X509ExtendedKeyManager {
529
530    private final X509ExtendedKeyManager keyManager;
531    private String authType;
532
533    MyX509KeyManager(X509ExtendedKeyManager keyManager) {
534        this.keyManager = keyManager;
535    }
536
537    void setAuthType(String authType) {
538        this.authType = "ECDSA".equals(authType) ? "EC" : authType;
539    }
540
541    @Override
542    public String[] getClientAliases(String keyType, Principal[] issuers) {
543        if (authType == null) {
544            return null;
545        }
546        return keyManager.getClientAliases(authType, issuers);
547    }
548
549    @Override
550    public String chooseClientAlias(String[] keyType, Principal[] issuers,
551            Socket socket) {
552        if (authType == null) {
553            return null;
554        }
555        return keyManager.chooseClientAlias(new String[] {authType},
556            issuers, socket);
557    }
558
559    @Override
560    public String chooseEngineClientAlias(String[] keyType,
561            Principal[] issuers, SSLEngine engine) {
562        if (authType == null) {
563            return null;
564        }
565        return keyManager.chooseEngineClientAlias(new String[] {authType},
566            issuers, engine);
567    }
568
569    @Override
570    public String[] getServerAliases(String keyType, Principal[] issuers) {
571        throw new UnsupportedOperationException("Servers not supported");
572    }
573
574    @Override
575    public String chooseServerAlias(String keyType, Principal[] issuers,
576            Socket socket) {
577        throw new UnsupportedOperationException("Servers not supported");
578    }
579
580    @Override
581    public String chooseEngineServerAlias(String keyType, Principal[] issuers,
582            SSLEngine engine) {
583        throw new UnsupportedOperationException("Servers not supported");
584    }
585
586    @Override
587    public X509Certificate[] getCertificateChain(String alias) {
588        return keyManager.getCertificateChain(alias);
589    }
590
591    @Override
592    public PrivateKey getPrivateKey(String alias) {
593        return keyManager.getPrivateKey(alias);
594    }
595
596}
597
598class DaemonThreadFactory implements ThreadFactory {
599
600    final static ThreadFactory INSTANCE = new DaemonThreadFactory();
601
602    private final static ThreadFactory DEFAULT = Executors.defaultThreadFactory();
603
604    @Override
605    public Thread newThread(Runnable r) {
606        Thread t = DEFAULT.newThread(r);
607        t.setDaemon(true);
608        return t;
609    }
610
611}
612